OM

OM stands for "Only Macros". Everything that happens in OM happens because of a macro being evaluated. Because of this, there is no distinction between code and data in OM. OM was inspired by LISP and Wolfram and is similar to ///.

Language overview
An OM program is just an OM expression. Macros are evaluated on it until no macros match anything in it. The result is returned as output.

Expressions
Textually, an expression on OM is any string with proper bracket matching. A bracket can be escaped with ` to make it not count as a bracket. The following are valid expressions: abd (cool stuff [ {}{}{(((`]`]`]`]`}`}`)`))))}]) expr The following are not: ( []] (`) ([)]

Internally, an OM expression is represented as a list of nodes. Each node has a string value, a type, and a list of child nodes. There are six types:

Macros
A macro is defined like this: form -> product-form The form and the product form will both be individual nodes. If the form or the product-form are a PAREN node, their children will be the form or product-form. This macro will replace any expression (not in a PAREN node) matching the form and replace it with a corresponding expression matching the product-form. For example, (a b a) -> (b a b) will replace any instance of a NORMAL node with value "a" followed by one with "b" with one with "b" and one with "a", so a b a c a d a becomes b a b c a d a Notice that (a b a) does not match any three nodes where the first and last are the same. (a b a) refers to specifically a node with value "a", one with "b", etc.

Suppose you did want to match any three nodes where the first and the last are the same. You could do that with CAPTURE nodes. It would look like this: (~a ~b ~a swap-inner) -> (~b ~a ~b) That macro would match any three nodes where the first and the last are the same. For examples, cat dog cat swap-inner would become dog cat dog Also, moo moo moo swap-inner would become, after the macro is applied to it, moo moo moo Captures with different names do not have to match different nodes, even though captures with the same name do have to match the same node.

Conditional macros are only evaluated when a certain condition holds. This condition is represented as an OM expression that should evaluate to a NORMAL node with "True" or "False." Conditional macros are defined like this: (~a) -> (~b) if (~c) For example, (abs ~i) -> (0 - ~i) if (~i < 0) (abs ~i) -> (~i) if (~i >= 0) (-, = are all built-in macros).

Precedence
Nodes in (square and curly) brackets are evaluated before nodes outside brackets (expressions in PARENs are not evaluated at all), and macros with longer forms are evaluated before ones with shorter forms. A longer, older macro is evaluated before a shorter, newer macro. Whenever a macro is evaluated, evaluation starts all over again from the earliest-evaluated macros. It is essential to remember all five of these rules of precedence to code in OM.

Built-in macros
There are several macros built-in to OM. Without them it would be impossible to make useful programs. Here they are:

Code samples
Although there are no built-in conditionals in OM, it is easy to make one using two macros: (cond ~a True ~b) -> (unw ~a) (cond ~a False ~b) -> (unw ~b) It is also possible to make a while loop: (~a while ~b) -> (cond ([unw ~a] ~a while ~b) unw ([bool [unw ~b]]) ) A foreach loop takes a bit more doing: (~a FOR ~o IN ~l WITH ~i) -> ([~i -> 0] [ ( ~o -> [ind [~i] ~l [unw ~a]] [~i -> ~i] + 1) while ([~i] < [len [~l]])] ) (~a for ~o in ~l) -> (~a FOR ~o IN ~l WITH [loc i (i)]) The "loc i (i)" evaluates to an "i" with a unique id that is passed to the FOR macro as the index variable. It's possible to define variables of nodes: (get ~obj ~var) -> (~obj has ~var) -> (False) (set ~obj ~var ~val) -> ( (get ~obj ~var) -> (~val) (~obj has ~var) -> (True) ) (del ~obj ~var) -> ( (get ~obj ~var) -> (~obj has ~var) -> (False) ) The "is" example: (~a is ~b) -> (False) (~a is ~a) -> (True)

Computational class
OM is Turing-complete by reduction from brainfuck: LB -> `[ RB -> `] (match-rb ~ind ~l) -> (ind -1 {loc num (loc i (loc char ([num -> 1] [i -> [~ind]] [ ([i -> i] + 1 [char -> [ind [i] ~l]] [cond (num -> num] + 1]) [[char] is [LB] ] ] [cond (num -> [[num] - 1]) [[char] is [RB] ] ]) while ([num != 0]) ] [i] ) ) ) } ) (match-lb ~ind ~l) -> (ind -1 {loc num (loc i (loc char ([num -> -1] [i -> [~ind [ ([i -> i] - 1 [char -> [ind [i] ~l]] [cond (num -> num] + 1]) [[char] is [LB] ]  ] [cond (num -> [[num] - 1]) [[char] is [RB] ] ]) while ([num != 0]) ] [i] ) ) ) } ) (WIPE-TAPE) -> ( (get tape ~a) -> (0.0)) (ENS-VAL) -> (cond  [tape has [ptr (set tape [ptr] 0.0)) (PTR-R) -> ([ptr -> ptr] + 1 ENS-VAL) (PTR-L) -> ([ptr -> ptr] - 1 ENS-VAL) (INC) -> (set tape [ptr] get tape [ptr + 1]) (DEC) -> (set tape [ptr] get tape [ptr - 1]) (CHR-O) -> (pr [char [get tape [ptr]]]) (CHR-I) -> (set tape [ptr] [ord [ind 0 [expd [inp]]]]) (L-LP) -> (cond (PROG-I -> [match-rb [PROG-I] [prog]]) get tape [ptr is 0.0] ) (R-LP) -> (cond (PROG-I -> [match-lb [PROG-I] [prog]]) [not get tape [ptr is 0.0]] ) (DO-BF (>) ) -> (PTR-R) (DO-BF (<) ) -> (PTR-L) (DO-BF (+) ) -> (INC) (DO-BF (-) ) -> (DEC) (DO-BF (.) ) -> (CHR-O) (DO-BF ) -> (CHR-I) (DO-BF (`[) ) -> (L-LP) (DO-BF (`]) ) -> (R-LP) (run-bf ~prog) -> (loc c ([ptr -> 0.0] [ENS-VAL] [WIPE-TAPE] [PROG-I -> 0] [prog -> (~prog)] [ ( [c -> ] [DO-BF [c]] [PROG-I -> PROG-I] + 1 ) while ([PROG-I] < [len [prog]]) ] ) ) (rbf) -> (run-bf [expd [inp]])

External resources

 * OM interpreter in Python