246 lines
5.7 KiB
Python
246 lines
5.7 KiB
Python
# -----------------------------------------------------------------------------
|
|
# expr.py
|
|
#
|
|
# Proof-of-concept encoding of functions/expressions into Wasm.
|
|
#
|
|
# This file implements a mini-language for writing Wasm functions as expressions.
|
|
# It only supports integers.
|
|
#
|
|
# Here's a few examples:
|
|
#
|
|
# # Some basic function definitions
|
|
# add(x, y) = x + y;
|
|
# mul(x, y) = x * y;
|
|
# dsquare(x, y) = mul(x, x) + mul(y, y);
|
|
#
|
|
# # A recursive function
|
|
# fact(n) = if n < 1 then 1 else n*fact(n-1);
|
|
#
|
|
# The full grammar:
|
|
#
|
|
# functions : functions function
|
|
# | function
|
|
#
|
|
# function : NAME ( parms ) = expr ;
|
|
#
|
|
# expr : expr + expr
|
|
# | expr - expr
|
|
# | expr * expr
|
|
# | expr / expr
|
|
# | expr < expr
|
|
# | expr <= expr
|
|
# | expr > expr
|
|
# | expr >= expr
|
|
# | expr == expr
|
|
# | expr != expr
|
|
# | ( expr )
|
|
# | NAME (exprs)
|
|
# | if expr then expr else expr
|
|
# | NUMBER
|
|
#
|
|
# Note: This is implemented as one-pass compiler with no intermediate AST.
|
|
# Some of the grammar rules have to be written in a funny way to make this
|
|
# work. If doing this for real, I'd probably build an AST and construct
|
|
# Wasm code through AST walking.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
import sys
|
|
sys.path.append('../..')
|
|
|
|
from sly import Lexer, Parser
|
|
import wasm
|
|
|
|
class ExprLexer(Lexer):
|
|
tokens = { NAME, NUMBER, PLUS, TIMES, MINUS, DIVIDE, LPAREN, RPAREN, COMMA,
|
|
LT, LE, GT, GE, EQ, NE, IF, THEN, ELSE, ASSIGN, SEMI }
|
|
ignore = ' \t'
|
|
|
|
# Tokens
|
|
NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
|
|
NAME['if'] = IF
|
|
NAME['then'] = THEN
|
|
NAME['else'] = ELSE
|
|
|
|
NUMBER = r'\d+'
|
|
|
|
# Special symbols
|
|
PLUS = r'\+'
|
|
MINUS = r'-'
|
|
TIMES = r'\*'
|
|
DIVIDE = r'/'
|
|
LPAREN = r'\('
|
|
RPAREN = r'\)'
|
|
COMMA = r','
|
|
LE = r'<='
|
|
LT = r'<'
|
|
GE = r'>='
|
|
GT = r'>'
|
|
EQ = r'=='
|
|
NE = r'!='
|
|
ASSIGN = r'='
|
|
SEMI = ';'
|
|
|
|
# Ignored pattern
|
|
ignore_newline = r'\n+'
|
|
ignore_comment = r'#.*\n'
|
|
|
|
# Extra action for newlines
|
|
def ignore_newline(self, t):
|
|
self.lineno += t.value.count('\n')
|
|
|
|
def error(self, t):
|
|
print("Illegal character '%s'" % t.value[0])
|
|
self.index += 1
|
|
|
|
class ExprParser(Parser):
|
|
tokens = ExprLexer.tokens
|
|
|
|
precedence = (
|
|
('left', IF, ELSE),
|
|
('left', EQ, NE, LT, LE, GT, GE),
|
|
('left', PLUS, MINUS),
|
|
('left', TIMES, DIVIDE),
|
|
('right', UMINUS)
|
|
)
|
|
|
|
def __init__(self):
|
|
self.functions = { }
|
|
self.module = wasm.Module()
|
|
|
|
@_('functions function')
|
|
def functions(self, p):
|
|
pass
|
|
|
|
@_('function')
|
|
def functions(self, p):
|
|
pass
|
|
|
|
@_('function_decl ASSIGN expr SEMI')
|
|
def function(self, p):
|
|
self.function.block_end()
|
|
self.function = None
|
|
|
|
@_('NAME LPAREN parms RPAREN')
|
|
def function_decl(self, p):
|
|
self.locals = { name:n for n, name in enumerate(p.parms) }
|
|
self.function = self.module.add_function(p.NAME, [wasm.i32]*len(p.parms), [wasm.i32])
|
|
self.functions[p.NAME] = self.function
|
|
|
|
@_('NAME LPAREN RPAREN')
|
|
def function_decl(self, p):
|
|
self.locals = { }
|
|
self.function = self.module.add_function(p.NAME, [], [wasm.i32])
|
|
self.functions[p.NAME] = self.function
|
|
|
|
@_('parms COMMA parm')
|
|
def parms(self, p):
|
|
return p.parms + [p.parm]
|
|
|
|
@_('parm')
|
|
def parms(self, p):
|
|
return [ p.parm ]
|
|
|
|
@_('NAME')
|
|
def parm(self, p):
|
|
return p.NAME
|
|
|
|
@_('expr PLUS expr')
|
|
def expr(self, p):
|
|
self.function.i32.add()
|
|
|
|
@_('expr MINUS expr')
|
|
def expr(self, p):
|
|
self.function.i32.sub()
|
|
|
|
@_('expr TIMES expr')
|
|
def expr(self, p):
|
|
self.function.i32.mul()
|
|
|
|
@_('expr DIVIDE expr')
|
|
def expr(self, p):
|
|
self.function.i32.div_s()
|
|
|
|
@_('expr LT expr')
|
|
def expr(self, p):
|
|
self.function.i32.lt_s()
|
|
|
|
@_('expr LE expr')
|
|
def expr(self, p):
|
|
self.function.i32.le_s()
|
|
|
|
@_('expr GT expr')
|
|
def expr(self, p):
|
|
self.function.i32.gt_s()
|
|
|
|
@_('expr GE expr')
|
|
def expr(self, p):
|
|
self.function.i32.ge_s()
|
|
|
|
@_('expr EQ expr')
|
|
def expr(self, p):
|
|
self.function.i32.eq()
|
|
|
|
@_('expr NE expr')
|
|
def expr(self, p):
|
|
self.function.i32.ne()
|
|
|
|
@_('MINUS expr %prec UMINUS')
|
|
def expr(self, p):
|
|
pass
|
|
|
|
@_('LPAREN expr RPAREN')
|
|
def expr(self, p):
|
|
pass
|
|
|
|
@_('NUMBER')
|
|
def expr(self, p):
|
|
self.function.i32.const(int(p.NUMBER))
|
|
|
|
@_('NAME')
|
|
def expr(self, p):
|
|
self.function.local.get(self.locals[p.NAME])
|
|
|
|
@_('NAME LPAREN exprlist RPAREN')
|
|
def expr(self, p):
|
|
self.function.call(self.functions[p.NAME])
|
|
|
|
@_('NAME LPAREN RPAREN')
|
|
def expr(self, p):
|
|
self.function.call(self.functions[p.NAME])
|
|
|
|
@_('IF expr thenexpr ELSE expr')
|
|
def expr(self, p):
|
|
self.function.block_end()
|
|
|
|
@_('exprlist COMMA expr')
|
|
def exprlist(self, p):
|
|
pass
|
|
|
|
@_('expr')
|
|
def exprlist(self, p):
|
|
pass
|
|
|
|
@_('startthen expr')
|
|
def thenexpr(self, p):
|
|
self.function.else_start()
|
|
|
|
@_('THEN')
|
|
def startthen(self, p):
|
|
self.function.if_start(wasm.i32)
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
if len(sys.argv) != 2:
|
|
raise SystemExit(f'Usage: {sys.argv[0]} module')
|
|
|
|
lexer = ExprLexer()
|
|
parser = ExprParser()
|
|
parser.parse(lexer.tokenize(open(sys.argv[1]).read()))
|
|
|
|
name = sys.argv[1].split('.')[0]
|
|
parser.module.write_wasm(name)
|
|
parser.module.write_html(name)
|
|
print(f'Wrote: {name}.wasm')
|
|
print(f'Wrote: {name}.html')
|
|
print('Use python3 -m http.server to test')
|