A Magically Delicious Wordprocessor with OMeta

I've always wanted a magic wordprocessor. Something that helps me organize my thoughts and build ideas organically, rather than spend all of my time worrying about formatting. Something for the internet / cloud age. Given that Microsoft Word hasn't fundamentally changed in over a decade (or possibly two), we aren't likely to get such software from them. Instead, I decided to play around with some ideas using my new favorite programming tool: OMeta.

OMeta

OMeta's story starts with compilers. Compilers are built of many phases: lexing, parsing, applying optimizations, then generating machine code. OMeta's creator, Alessandro Warth, realized that these are all essentially the same thing, pattern matching. The only difference is whether you are matching a stream of characters, tokens, or parts of a tree. Alex created a single language that lets you match over objects (ie: anything) to implement all parts of the compiler using a single tool.

OMeta is that single tool: a domain specific programming language for creating pattern matchers. More properly, it is a language extension that can be laid on top of an existing host language. I am using OmetaJS, an extension to JavaScript. The wonderful thing about OMeta is that it's easier to use and extend than dedicated parser generators like Antlr or Yacc because you always have the host language available to do whatever computation you need.

Editor Demo

As my first experiment with OMeta, and new wordprocessors, I created a simple Markdown editor. You type raw text into the left side of the screen and see your properly styled text on the right side. But this editor has a trick up it's sleeve. You can type simple math expressions inside curly braces the editor will evaluate it for you.

Play with the editor here

Here's what the OMeta code looks like:


ometa Foo {
    //white space
    toEOL = (~seq('\n') char)*:t '\n' -> t.join(""),
    
    //headers
    h1 = "#"   ' ' toEOL:t -> tag("h1",t),
    h2 = "##"  ' ' toEOL:t -> tag("h2",t),
    h3 = "###" ' ' toEOL:t -> tag("h3",t),

    
    //paragraph
    paraend   = seq('\n\n'),
    para      = (expr|strong|em| (~paraend char))+:t -> tag("p",t.join("")),
    text   =      (~seq('\n\n') char)*:t      -> t.join(""),
    strong = "**" (~seq('**')   char)*:t "**" -> tag("strong",t.join("")),
    em     = "*"  (~seq('*')    char)*:t "*"  -> tag("em",t.join("")),
    expr    = "{"  exp:t "}"  -> tag("b",t),
    
    //code block
    codeblock = fromTo("```\n", "```\n"):t -> tag("pre",tag("code",esc(t))),
    
    //inline expressions
    num = :n -> parseInt(n),
    term = num,
    expadd = term:a "+" term:b -> (a+b),
    expmul = term:a "*" term:b -> (a*b),
    expsub = term:a "-" term:b -> (a-b),
    expdiv = term:a "/" term:b -> (a/b),
    exp = (expmul|expdiv|expadd|expsub):e -> (" "+e+"") ,
    
    //pull it all together
    line = space* (h3|h2|h1|codeblock|para):t space* -> t,
    top = line*,
    
    END
};

This editor only understands simple two term arithmetic but it could easily be expanded with variables and more complex functions.