|
@@ -3,18 +3,20 @@ |
|
|
import os.path |
|
|
import os.path |
|
|
import sys |
|
|
import sys |
|
|
|
|
|
|
|
|
from lark import Lark, InlineTransformer |
|
|
|
|
|
|
|
|
import js2py |
|
|
|
|
|
|
|
|
|
|
|
from lark import Lark, InlineTransformer, Transformer |
|
|
|
|
|
|
|
|
nearley_grammar = r""" |
|
|
nearley_grammar = r""" |
|
|
start: (ruledef|directive)+ |
|
|
start: (ruledef|directive)+ |
|
|
|
|
|
|
|
|
directive: "@" NAME (STRING|NAME) |
|
|
directive: "@" NAME (STRING|NAME) |
|
|
| "@" _JS -> js_code |
|
|
|
|
|
|
|
|
| "@" JS -> js_code |
|
|
ruledef: NAME "->" expansions |
|
|
ruledef: NAME "->" expansions |
|
|
| NAME REGEXP "->" expansions -> macro |
|
|
| NAME REGEXP "->" expansions -> macro |
|
|
expansions: expansion ("|" expansion)* |
|
|
expansions: expansion ("|" expansion)* |
|
|
|
|
|
|
|
|
expansion: expr+ _JS? |
|
|
|
|
|
|
|
|
expansion: expr+ js |
|
|
|
|
|
|
|
|
?expr: item [":" /[+*?]/] |
|
|
?expr: item [":" /[+*?]/] |
|
|
|
|
|
|
|
@@ -24,7 +26,8 @@ nearley_grammar = r""" |
|
|
rule: NAME |
|
|
rule: NAME |
|
|
string: STRING |
|
|
string: STRING |
|
|
regexp: REGEXP |
|
|
regexp: REGEXP |
|
|
_JS: /(?s){%.*?%}/ |
|
|
|
|
|
|
|
|
JS: /(?s){%.*?%}/ |
|
|
|
|
|
js: JS? |
|
|
|
|
|
|
|
|
NAME: /[a-zA-Z_$]\w*/ |
|
|
NAME: /[a-zA-Z_$]\w*/ |
|
|
COMMENT: /\#[^\n]*/ |
|
|
COMMENT: /\#[^\n]*/ |
|
@@ -37,60 +40,104 @@ nearley_grammar = r""" |
|
|
|
|
|
|
|
|
""" |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
nearley_grammar_parser = Lark(nearley_grammar, parser='earley', lexer='standard') |
|
|
|
|
|
|
|
|
|
|
|
def _get_rulename(name): |
|
|
|
|
|
name = {'_': '_ws_maybe', '__':'_ws'}.get(name, name) |
|
|
|
|
|
return 'n_' + name.replace('$', '__DOLLAR__') |
|
|
|
|
|
|
|
|
class NearleyToLark(InlineTransformer): |
|
|
class NearleyToLark(InlineTransformer): |
|
|
def __init__(self, builtin_path): |
|
|
|
|
|
self.builtin_path = builtin_path |
|
|
|
|
|
|
|
|
def __init__(self, context): |
|
|
|
|
|
self.context = context |
|
|
|
|
|
self.functions = {} |
|
|
|
|
|
self.extra_rules = {} |
|
|
|
|
|
|
|
|
|
|
|
def _new_function(self, code): |
|
|
|
|
|
n = len(self.functions) |
|
|
|
|
|
name = 'alias_%d' % n |
|
|
|
|
|
assert name not in self.functions |
|
|
|
|
|
code = "%s = (%s);" % (name, code) |
|
|
|
|
|
self.context.execute(code) |
|
|
|
|
|
f = getattr(self.context, name) |
|
|
|
|
|
self.functions[name] = f |
|
|
|
|
|
|
|
|
|
|
|
return name |
|
|
|
|
|
|
|
|
|
|
|
def _extra_rule(self, rule): |
|
|
|
|
|
name = 'xrule_%d' % len(self.extra_rules) |
|
|
|
|
|
assert name not in self.extra_rules |
|
|
|
|
|
self.extra_rules[name] = rule |
|
|
|
|
|
return name |
|
|
|
|
|
|
|
|
def rule(self, name): |
|
|
def rule(self, name): |
|
|
# return {'_': '_WS?', '__':'_WS'}.get(name, name) |
|
|
|
|
|
return {'_': '_ws_maybe', '__':'_ws'}.get(name, name) |
|
|
|
|
|
|
|
|
return _get_rulename(name) |
|
|
|
|
|
|
|
|
def ruledef(self, name, exps): |
|
|
def ruledef(self, name, exps): |
|
|
name = {'_': '_ws_maybe', '__':'_ws'}.get(name, name) |
|
|
|
|
|
return '%s: %s' % (name, exps) |
|
|
|
|
|
|
|
|
return '!%s: %s' % (_get_rulename(name), exps) |
|
|
|
|
|
|
|
|
def expr(self, item, op): |
|
|
def expr(self, item, op): |
|
|
return '(%s)%s' % (item, op) |
|
|
|
|
|
|
|
|
rule = '(%s)%s' % (item, op) |
|
|
|
|
|
return self._extra_rule(rule) |
|
|
|
|
|
|
|
|
def regexp(self, r): |
|
|
def regexp(self, r): |
|
|
return '/%s/' % r |
|
|
return '/%s/' % r |
|
|
|
|
|
|
|
|
def string(self, s): |
|
|
def string(self, s): |
|
|
# TODO allow regular strings, and split them in the parser frontend |
|
|
|
|
|
return ' '.join('"%s"'%ch for ch in s[1:-1]) |
|
|
|
|
|
|
|
|
return self._extra_rule(s) |
|
|
|
|
|
|
|
|
def expansion(self, *x): |
|
|
def expansion(self, *x): |
|
|
return ' '.join(x) |
|
|
|
|
|
|
|
|
x, js = x[:-1], x[-1] |
|
|
|
|
|
if js.children: |
|
|
|
|
|
js_code ,= js.children |
|
|
|
|
|
js_code = js_code[2:-2] |
|
|
|
|
|
alias = '-> ' + self._new_function(js_code) |
|
|
|
|
|
else: |
|
|
|
|
|
alias = '' |
|
|
|
|
|
return ' '.join(x) + alias |
|
|
|
|
|
|
|
|
def expansions(self, *x): |
|
|
def expansions(self, *x): |
|
|
return '(%s)' % ('\n |'.join(x)) |
|
|
|
|
|
|
|
|
|
|
|
def js_code(self): |
|
|
|
|
|
return '' |
|
|
|
|
|
|
|
|
|
|
|
def macro(self, *args): |
|
|
|
|
|
return '' # TODO support macros?! |
|
|
|
|
|
|
|
|
|
|
|
def directive(self, name, *args): |
|
|
|
|
|
if name == 'builtin': |
|
|
|
|
|
arg = args[0][1:-1] |
|
|
|
|
|
with open(os.path.join(self.builtin_path, arg)) as f: |
|
|
|
|
|
text = f.read() |
|
|
|
|
|
return nearley_to_lark(text, self.builtin_path) |
|
|
|
|
|
elif name == 'preprocessor': |
|
|
|
|
|
return '' |
|
|
|
|
|
|
|
|
|
|
|
raise Exception('Unknown directive: %s' % name) |
|
|
|
|
|
|
|
|
return '%s' % ('\n |'.join(x)) |
|
|
|
|
|
|
|
|
def start(self, *rules): |
|
|
def start(self, *rules): |
|
|
return '\n'.join(filter(None, rules)) |
|
|
return '\n'.join(filter(None, rules)) |
|
|
|
|
|
|
|
|
def nearley_to_lark(g, builtin_path): |
|
|
|
|
|
parser = Lark(nearley_grammar, parser='earley', lexer='standard') |
|
|
|
|
|
tree = parser.parse(g) |
|
|
|
|
|
return NearleyToLark(builtin_path).transform(tree) |
|
|
|
|
|
|
|
|
def _nearley_to_lark(g, builtin_path, n2l): |
|
|
|
|
|
rule_defs = [] |
|
|
|
|
|
|
|
|
|
|
|
tree = nearley_grammar_parser.parse(g) |
|
|
|
|
|
for statement in tree.children: |
|
|
|
|
|
if statement.data == 'directive': |
|
|
|
|
|
directive, arg = statement.children |
|
|
|
|
|
if directive == 'builtin': |
|
|
|
|
|
with open(os.path.join(builtin_path, arg[1:-1])) as f: |
|
|
|
|
|
text = f.read() |
|
|
|
|
|
rule_defs += _nearley_to_lark(text, builtin_path, n2l) |
|
|
|
|
|
else: |
|
|
|
|
|
assert False, directive |
|
|
|
|
|
elif statement.data == 'js_code': |
|
|
|
|
|
code ,= statement.children |
|
|
|
|
|
code = code[2:-2] |
|
|
|
|
|
n2l.context.execute(code) |
|
|
|
|
|
elif statement.data == 'macro': |
|
|
|
|
|
pass # TODO Add support for macros! |
|
|
|
|
|
elif statement.data == 'ruledef': |
|
|
|
|
|
rule_defs.append( n2l.transform(statement) ) |
|
|
|
|
|
else: |
|
|
|
|
|
raise Exception("Unknown statement: %s" % statement) |
|
|
|
|
|
|
|
|
|
|
|
return rule_defs |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def nearley_to_lark(g, builtin_path, context): |
|
|
|
|
|
n2l = NearleyToLark(context) |
|
|
|
|
|
lark_g = '\n'.join(_nearley_to_lark(g, builtin_path, n2l)) |
|
|
|
|
|
lark_g += '\n'+'\n'.join('!%s: %s' % item for item in n2l.extra_rules.items()) |
|
|
|
|
|
t = Transformer() |
|
|
|
|
|
for fname, fcode in n2l.functions.items(): |
|
|
|
|
|
setattr(t, fname, fcode) |
|
|
|
|
|
setattr(t, '__default__', lambda n, c: c if c else None) |
|
|
|
|
|
|
|
|
|
|
|
return lark_g, t |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test(): |
|
|
def test(): |
|
@@ -129,12 +176,17 @@ def test(): |
|
|
function(d) {return Math.floor(d[0]*255); } |
|
|
function(d) {return Math.floor(d[0]*255); } |
|
|
%} |
|
|
%} |
|
|
""" |
|
|
""" |
|
|
converted_grammar = nearley_to_lark(css_example_grammar, '/home/erez/nearley/builtin') |
|
|
|
|
|
print(converted_grammar) |
|
|
|
|
|
|
|
|
context = js2py.EvalJs() |
|
|
|
|
|
context.execute('function id(x) {return x[0]; }') |
|
|
|
|
|
|
|
|
|
|
|
converted_grammar, t = nearley_to_lark(css_example_grammar, '/home/erez/nearley/builtin', context) |
|
|
|
|
|
# print(converted_grammar) |
|
|
|
|
|
|
|
|
l = Lark(converted_grammar, start='csscolor') |
|
|
|
|
|
print(l.parse('#a199ff').pretty()) |
|
|
|
|
|
print(l.parse('rgb(255, 70%, 3)').pretty()) |
|
|
|
|
|
|
|
|
l = Lark(converted_grammar, start='n_csscolor') |
|
|
|
|
|
tree = l.parse('#a199ff') |
|
|
|
|
|
print(t.transform(tree)) |
|
|
|
|
|
tree = l.parse('rgb(255, 70%, 3)') |
|
|
|
|
|
print(t.transform(tree)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
def main(): |
|
|