150 lines
6.1 KiB
Plaintext
150 lines
6.1 KiB
Plaintext
|
OMeta/JS is a new version of OMeta, a language for pattern-directed metaprogramming first described in
|
||
|
|
||
|
Alessandro Warth and Ian Piumarta, "OMeta: An Object-Oriented Language for Pattern-Matching," in Proceedings of the Dynamic
|
||
|
Languages Symposium, 2007. (Available at http://www.cs.ucla.edu/~awarth/papers/dls07.pdf)
|
||
|
|
||
|
This page contains the information necessary for someone who has read the OMeta paper to be able to use OMeta/JS.
|
||
|
|
||
|
|
||
|
Pattern Syntax
|
||
|
--------------
|
||
|
|
||
|
+------------------------------------------------------------------------+
|
||
|
| "kind of thing" OMeta OMeta/JS |
|
||
|
+------------------------------------------------------------------------+
|
||
|
| boolean true true |
|
||
|
| number 123 123 |
|
||
|
| character 'x' 'x' | (see note #1)
|
||
|
| string "foo" 'foo' |
|
||
|
| `foo |
|
||
|
| #foo |
|
||
|
| atom foo N/A |
|
||
|
| rule application <expr> expr |
|
||
|
| <r x y> r(x, y) | (see note #3)
|
||
|
| <super stmt> ^stmt | (see note #4)
|
||
|
| list ("hello" 42 answer ()) ['hello' 42 `answer []] |
|
||
|
| negation ~'x' ~'x' |
|
||
|
| look-ahead ~~'x' ~~'x' |
|
||
|
| &'x' |
|
||
|
| semantic predicate ?(> x y) ?(x > y) | (see note #2)
|
||
|
| semantic action => (+ x y) -> (x + y) |
|
||
|
| !(+ x y) !(x + y) |
|
||
|
| binding <expr>:x expr:x | (in OMeta/JS, spaces are not allowed before the colon)
|
||
|
| :x | (this is shorthand for "anything:x")
|
||
|
+------------------------------------------------------------------------+
|
||
|
|
||
|
|
||
|
Note #1: There is no such thing as a character in JavaScript. Even though the language lets you access each "character" of a string via indexing, e.g, "foo"[0], the answer is not a character, but rather a string of length 1.
|
||
|
|
||
|
|
||
|
Note #2: In the version of OMeta described in the paper, semantic actions and predicates were written in COLA (kind of a mix between Scheme and Smalltalk). In OMeta/JS, they are written in JavaScript. More specifically, they are either primary expressions, e.g.,
|
||
|
|
||
|
123
|
||
|
x
|
||
|
foo.bar()
|
||
|
new Person()
|
||
|
(x + y) // note that you need parentheses around "x + y" in order to make it into a primary expression
|
||
|
|
||
|
or something I made up called "statement expressions", which have the form
|
||
|
|
||
|
"{" <statement>* <expr> "}"
|
||
|
|
||
|
For example,
|
||
|
|
||
|
{ x += 2; y = "foo"; f(x) }
|
||
|
|
||
|
The value of a statement expression is equal to that of its last expression.
|
||
|
|
||
|
|
||
|
Note #3: The arguments you pass to a rule don't have to be statement expressions - they can be any JavaScript expression.
|
||
|
|
||
|
Note #4: In OMeta/JS, "super" is just like any other rule (not a special form), so you have to quote the rule name that you pass in as an argument, e.g., both ^r(1, 2) and super("r", 1, 2) are valid super-sends.
|
||
|
|
||
|
|
||
|
A "Handy" New Shorthand
|
||
|
-----------------------
|
||
|
|
||
|
In OMeta/JS, the pattern
|
||
|
|
||
|
"foo"
|
||
|
|
||
|
does not match the string 'foo'; it is instead shorthand for
|
||
|
|
||
|
token('foo')
|
||
|
|
||
|
The Parser grammar provides a definition for token that skips any number of spaces, then tries to match the sequence of characters that was passed to it as an argument. I have used this in many of the example projects, and have found it to be very useful.
|
||
|
|
||
|
Still, there are times when this is not what you want. But that's not a problem, because you can define it to do whatever you want (see the JavaScript Compiler project for an example).
|
||
|
|
||
|
|
||
|
Rules
|
||
|
-----
|
||
|
|
||
|
Here is a parameterized rule taken from the paper, in the original OMeta syntax:
|
||
|
|
||
|
cRange x y ::= <char>:c ?(>= c x)
|
||
|
?(<= c y) => c;
|
||
|
|
||
|
And here is the same rule rule, in the new OMeta/JS syntax:
|
||
|
|
||
|
cRange :x :y = char:c ?(c >= x)
|
||
|
?(c <= y) -> c
|
||
|
|
||
|
|
||
|
A couple of (purely syntactic) differences:
|
||
|
|
||
|
(1) rule declarations now use "=" instead of "::=", and
|
||
|
(2) they are no longer terminated with a ";"
|
||
|
|
||
|
|
||
|
A more significant difference has to do with the rule's arguments; note that in the OMeta/JS version, they are preceded by a ':'. This is actually shorthand for
|
||
|
|
||
|
cRange anything:x anything:y = ...
|
||
|
|
||
|
This change has to do with an improvement in the parameter-passing mechanism, which now allows a rule's parameters to be pattern-matched against. (See the paper's "Future Work" section for more details.)
|
||
|
|
||
|
The "=" is actually optional in rule declarations... this, combined with some new syntax that allows a rule to have multiple definitions that are tried in lexicographic order, allows programmers to write rules that have an "ML flavor":
|
||
|
|
||
|
ometa M {
|
||
|
fact 0 -> 1,
|
||
|
fact :n ?(n > 0) fact(n - 1):m -> (n * m)
|
||
|
}
|
||
|
M.match(5, "fact")
|
||
|
|
||
|
|
||
|
Grammar Syntax
|
||
|
--------------
|
||
|
|
||
|
The only change here has to do with rule declarations, which now must be separated by commas:
|
||
|
|
||
|
ometa M {
|
||
|
x = y z,
|
||
|
y = "foo" "bar",
|
||
|
z = "baz"
|
||
|
}
|
||
|
|
||
|
|
||
|
Using Grammars "from the outside"
|
||
|
----------------------------------
|
||
|
|
||
|
The public interface provided by an OMeta/JS grammar object to the rest of the world consists of two methods:
|
||
|
|
||
|
match(object, ruleName)
|
||
|
|
||
|
and
|
||
|
|
||
|
matchAll(arrayOrStringObject, ruleName)
|
||
|
|
||
|
|
||
|
Here's an example that hopefully explains the difference between the two. The key to understanding it is that a string is just a list of characters.
|
||
|
|
||
|
ometa M <: Parser {
|
||
|
theCharacters = "the" "cat" "sat" "on" "the" "mat",
|
||
|
theWholeString = [theCharacters]
|
||
|
}
|
||
|
|
||
|
input = "the cat sat on the mat"
|
||
|
M.matchAll(input, "theCharacters")
|
||
|
M.match(input, "theWholeString")
|
||
|
|