From 6ec458bdb87c938ba27213a9e1fd169ff918fba4 Mon Sep 17 00:00:00 2001 From: Jonathan David Page Date: Mon, 25 Jun 2018 01:05:39 -0400 Subject: [PATCH 1/3] Start factoring out assembly-related stuff into a new module --- jeff65/brundle/__init__.py | 15 + jeff65/brundle/ast.py | 134 +++++ jeff65/brundle/mos6502/__init__.py | 15 + jeff65/brundle/sexp.py | 215 ++++++++ jeff65/gold/ast.py | 101 +--- jeff65/gold/grammar/Gold.g4 | 11 +- jeff65/gold/grammar/Gold.py | 819 +++++++++++++++------------- jeff65/gold/grammar/Gold.tokens | 73 +-- jeff65/gold/grammar/GoldListener.py | 9 + jeff65/gold/lexer.py | 1 + requirements.txt | 1 + tests/test_sexp.py | 44 ++ 12 files changed, 928 insertions(+), 510 deletions(-) create mode 100644 jeff65/brundle/__init__.py create mode 100644 jeff65/brundle/ast.py create mode 100644 jeff65/brundle/mos6502/__init__.py create mode 100644 jeff65/brundle/sexp.py create mode 100644 tests/test_sexp.py diff --git a/jeff65/brundle/__init__.py b/jeff65/brundle/__init__.py new file mode 100644 index 0000000..31d549f --- /dev/null +++ b/jeff65/brundle/__init__.py @@ -0,0 +1,15 @@ +# jeff65.brundle module root +# Copyright (C) 2018 jeff65 maintainers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/jeff65/brundle/ast.py b/jeff65/brundle/ast.py new file mode 100644 index 0000000..ac29fe3 --- /dev/null +++ b/jeff65/brundle/ast.py @@ -0,0 +1,134 @@ +# jeff65 AST structure manipulation +# Copyright (C) 2018 jeff65 maintainers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import attr +from . import sexp + + +@attr.s(slots=True, repr=False) +class AstNode: + t = attr.ib() + position = attr.ib(cmp=False, hash=False) + attrs = attr.ib(factory=dict) + children = attr.ib(factory=list) + + def clone(self, with_attrs=None, with_children=None): + node = AstNode(self.t, self.position, dict(self.attrs), + list(with_children or self.children)) + if with_attrs: + node.attrs.update(with_attrs) + return node + + def get_attr_default(self, attr, default_value): + if attr not in self.attrs: + self.attrs[attr] = default_value + return self.attrs[attr] + + def transform(self, transformer): + node = transformer.transform_enter(self.t, self) + + if transformer.transform_attrs and type(node) is AstNode: + attrs = {} + for n, v in node.attrs.items(): + if type(v) is AstNode: + tv = v.transform(transformer) + if tv: + assert len(tv) == 1 + attrs[n] = tv[0] + else: + attrs[n] = v + if attrs != node.attrs: + if node is self: + node = node.clone() + node.attrs = attrs + + if type(node) is AstNode: + children = [] + for child in node.children: + if type(child) is AstNode: + children.extend(child.transform(transformer)) + else: + children.append(child) + if children != node.children: + if node is self: + node = node.clone() + node.children = children + + nodes = transformer.transform_exit(self.t, node) + + if type(nodes) is None: + nodes = [] + elif type(nodes) is not list: + nodes = [nodes] + + if self.t == 'unit': + assert len(nodes) == 1 + return nodes[0] + return nodes + + def __repr__(self): + return "".format(self.t, *self.position) + + def pretty(self, indent=0, no_position=False): + return self._pretty(indent, no_position).strip() + + def _pretty(self, indent, no_position): + def i(n=0): + return " " * (indent + n) + + pp = [] + + if no_position: + pp.append("{}{}\n".format(i(), self.t)) + else: + pp.append("{}{:<{}} {}:{}\n".format(i(), self.t, 70 - indent, + *self.position)) + for a, value in self.attrs.items(): + if type(value) is AstNode: + pp.append("{}:{} ".format(i(2), a)) + pp.append(value._pretty(indent + 4 + len(a), + no_position).lstrip()) + else: + pp.append("{}:{} {}\n".format(i(2), a, repr(value))) + for child in self.children: + if type(child) is AstNode: + pp.append(child._pretty(indent + 2, no_position)) + else: + pp.append("{}{}\n".format(i(2), repr(child))) + return "".join(pp) + + def dump(self, f): + data = self.transform(AstSerializer()) + sexp.dump(f, data) + + def dumps(self): + data = self.transform(AstSerializer()) + return sexp.dumps(data) + + +class AstSerializer: + transform_attrs = True + + def transform_enter(self, t, node): + return node + + def transform_exit(self, t, node): + at = sexp.Atom(t) + attrs = [] + for k, v in node.attrs.items(): + attrs.append([sexp.Atom(':' + k), v]) + # this has to be a double-list to avoid it being exploded + return [[at, attrs, *node.children]] diff --git a/jeff65/brundle/mos6502/__init__.py b/jeff65/brundle/mos6502/__init__.py new file mode 100644 index 0000000..815aac6 --- /dev/null +++ b/jeff65/brundle/mos6502/__init__.py @@ -0,0 +1,15 @@ +# jeff65.brundle.mos6502 module root +# Copyright (C) 2018 jeff65 maintainers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/jeff65/brundle/sexp.py b/jeff65/brundle/sexp.py new file mode 100644 index 0000000..815674b --- /dev/null +++ b/jeff65/brundle/sexp.py @@ -0,0 +1,215 @@ +# jeff65 s-expression parser +# Copyright (C) 2018 jeff65 maintainers +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import io +import re +from enum import Enum, auto +import attr + + +class T(Enum): + EOF = auto() + MYSTERY = auto() + PAREN_OPEN = auto() + PAREN_CLOSE = auto() + ATOM = auto() + STRING = auto() + NUMERIC = auto() + + +@attr.s(frozen=True, slots=True) +class Token: + t = attr.ib() + position = attr.ib() + text = attr.ib() + + +@attr.s(frozen=True, slots=True) +class Atom: + text = attr.ib() + + +terminators = re.escape('()"') +str_delim = '"' +m_str_escape = re.compile(r'\\.', re.M) +m_str_delim = re.compile(re.escape(str_delim)) +m_str_control = re.compile(fr'{m_str_escape.pattern}|{m_str_delim.pattern}') +m_whitespace = re.compile(r'\s+', re.M) +m_numeric = re.compile(fr'[+-]?\d[^\s{terminators}]*') +m_atom = re.compile(fr'[\w:][^\s{terminators}]*') +singles = { + '(': T.PAREN_OPEN, + ')': T.PAREN_CLOSE, +} + + +def lexer(stream, line=0, column=0): + current = None + string_value = None + string_line = 0 + string_column = 0 + + def make_token(t, text): + return Token(t, (line, column), text) + + while True: + if current is None or column >= len(current): + try: + current = next(stream) + line += 1 + column = 0 + except StopIteration: + # If we're in string mode, we are NOT expecting this kind of + # behavior and will kick up a fuss. + # TODO: check this + break + + # String collects a string until it's done, then emits a single STRING + # token. + if string_value is not None: + m = m_str_control.search(current, column) + if not m: + # the rest of the line is a string + string_value.append(current[column:]) + column = len(current) + elif m.start() == column: + # the string control character is right here! + if m.group() in set(str_delim): + # the string has ended + yield Token(T.STRING, (string_line, string_column), + ''.join(string_value)) + string_value = None + else: + # it must be an escaped character + string_value.append(m.group()[1]) + column = m.end() + else: + # string the text before the control + string_value.append(current[column:m.start()]) + column = m.start() + continue + if current[column] == '"': + string_value = [] + string_line = line + string_column = column + column += 1 + continue + + # Whitespace is discarded + m = m_whitespace.match(current, column) + if m: + column = m.end() + continue + # This has to be run before the word match, since the word regex + # matches numbers as well. + m = m_numeric.match(current, column) + if m: + yield make_token(T.NUMERIC, m.group()) + column = m.end() + continue + m = m_atom.match(current, column) + if m: + yield make_token(T.ATOM, m.group()) + column = m.end() + continue + # match the special characters. If it isn't one of those, this is the + # last stop on the match train, so get rid of the character by emitting + # it as an OFFICIAL TOKEN OF MYSTERY for one lucky winner. Mystery + # tokens are a surefire way of putting the parser into FABULOUS PRIZES + # MODE. + yield make_token(singles.get(current[column], T.MYSTERY), + current[column]) + column += 1 + # (note that the prizes in question are parse errors. If you don't like + # parse errors, then you may not enjoy this mode very much.) + + # end while, in case you forgot + + +def parse(tokens): + data = [[]] + for tok in tokens: + if tok.t == T.PAREN_OPEN: + data.append([]) + elif tok.t == T.PAREN_CLOSE: + lst = data.pop() + data[-1].append(lst) + elif tok.t == T.NUMERIC: + data[-1].append(int(tok.text)) + elif tok.t == T.STRING: + data[-1].append(tok.text) + elif tok.t == T.ATOM: + if tok.text == 'nil': + data[-1].append(None) + else: + data[-1].append(Atom(tok.text)) + else: + raise Exception(f"Unexpected '{tok.text}' at {tok.position}") + assert len(data) == 1 + assert len(data[0]) == 1 + return data[0][0] + + +def load(stream): + return parse(lexer(stream)) + + +def loads(s): + with io.StringIO(s) as f: + return load(f) + + +def dump(f, data, indent=0): + it = ' '*indent + if data is None: + f.write('nil') + elif isinstance(data, int): + f.write('{}'.format(repr(data))) + elif isinstance(data, str): + f.write('"{}"'.format(data.replace('"', r'\"'))) + elif isinstance(data, Atom): + f.write('{}'.format(data.text)) + elif isinstance(data, list): + if len(data) == 0: + f.write('()') + elif len(data) == 1: + f.write('(') + dump(f, data[0], indent+1) + f.write(')') + elif not any(isinstance(e, list) for e in data): + f.write('('.format(it)) + for e in data[:-1]: + dump(f, e, indent) + f.write(' ') + dump(f, data[-1], indent) + f.write(')') + else: + f.write('('.format(it)) + dump(f, data[0], 0) + f.write('\n{} '.format(it)) + for e in data[1:-1]: + dump(f, e, indent+1) + f.write('\n{} '.format(it)) + dump(f, data[-1], indent+1) + f.write(')'.format(it)) + else: + raise Exception(f"don't know what to do with '{data}'") + + +def dumps(data): + with io.StringIO() as f: + dump(f, data) + return f.getvalue() diff --git a/jeff65/gold/ast.py b/jeff65/gold/ast.py index ac5b9f7..cc47cf5 100644 --- a/jeff65/gold/ast.py +++ b/jeff65/gold/ast.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from ..brundle.ast import AstNode from .grammar import ParseListener @@ -22,106 +23,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -class AstNode: - def __init__(self, t, position, attrs=None, children=None): - self.t = t - self.position = position - self.attrs = attrs or {} - self.children = children or [] - - def clone(self, with_attrs=None, with_children=None): - node = AstNode(self.t, self.position, dict(self.attrs), - list(with_children or self.children)) - if with_attrs: - node.attrs.update(with_attrs) - return node - - def get_attr_default(self, attr, default_value): - if attr not in self.attrs: - self.attrs[attr] = default_value - return self.attrs[attr] - - def __eq__(self, other): - return ( - type(other) is AstNode - and self.t == other.t - and self.attrs == other.attrs - and self.children == other.children) - - def transform(self, transformer): - node = transformer.transform_enter(self.t, self) - - if transformer.transform_attrs and type(node) is AstNode: - attrs = {} - for n, v in node.attrs.items(): - if type(v) is AstNode: - tv = v.transform(transformer) - if tv: - assert len(tv) == 1 - attrs[n] = tv[0] - else: - attrs[n] = v - if attrs != node.attrs: - if node is self: - node = node.clone() - node.attrs = attrs - - if type(node) is AstNode: - children = [] - for child in node.children: - if type(child) is AstNode: - children.extend(child.transform(transformer)) - else: - children.append(child) - if children != node.children: - if node is self: - node = node.clone() - node.children = children - - nodes = transformer.transform_exit(self.t, node) - - if type(nodes) is None: - nodes = [] - elif type(nodes) is not list: - nodes = [nodes] - - if self.t == 'unit': - assert len(nodes) == 1 - return nodes[0] - return nodes - - def __repr__(self): - return "".format(self.t, *self.position) - - def pretty(self, indent=0, no_position=False): - return self._pretty(indent, no_position).strip() - - def _pretty(self, indent, no_position): - def i(n=0): - return " " * (indent + n) - - pp = [] - - if no_position: - pp.append("{}{}\n".format(i(), self.t)) - else: - pp.append("{}{:<{}} {}:{}\n".format(i(), self.t, 70 - indent, - *self.position)) - for attr, value in self.attrs.items(): - if type(value) is AstNode: - pp.append("{}:{} ".format(i(2), attr)) - pp.append(value._pretty(indent + 4 + len(attr), - no_position).lstrip()) - else: - pp.append("{}:{} {}\n".format(i(2), attr, repr(value))) - for child in self.children: - if type(child) is AstNode: - pp.append(child._pretty(indent + 2, no_position)) - else: - pp.append("{}{}\n".format(i(2), repr(child))) - return "".join(pp) - - class TranslationPass: """Base class for translation passes.""" diff --git a/jeff65/gold/grammar/Gold.g4 b/jeff65/gold/grammar/Gold.g4 index bcc4f96..35a920c 100644 --- a/jeff65/gold/grammar/Gold.g4 +++ b/jeff65/gold/grammar/Gold.g4 @@ -28,8 +28,8 @@ tokens { OPERATOR_DEREF, OPERATOR_REF, // statement keywords - STMT_CONSTANT, STMT_FOR, STMT_FUN, STMT_IF, STMT_ISR, STMT_LET, STMT_RETURN, - STMT_USE, STMT_WHILE, + STMT_ASM, STMT_CONSTANT, STMT_FOR, STMT_FUN, STMT_IF, STMT_ISR, STMT_LET, + STMT_RETURN, STMT_USE, STMT_WHILE, // storage classes STORAGE_MUT, STORAGE_STASH, @@ -62,7 +62,7 @@ expr : PAREN_OPEN expr PAREN_CLOSE # ExprParen | expr op=( OPERATOR_PLUS | OPERATOR_MINUS ) expr # ExprSum | expr op=( OPERATOR_EQ | OPERATOR_NE | OPERATOR_LE | OPERATOR_GE - | OPERATOR_LT | OPERATOR_GT) expr # ExprCompare + | OPERATOR_LT | OPERATOR_GT) expr # ExprCompare | value=NUMERIC # ExprNumber | name=IDENTIFIER # ExprId ; @@ -102,6 +102,8 @@ stmtIf : STMT_IF expr PUNCT_THEN block ( PUNCT_ELSE block )? PUNCT_END ; +stmtAsm : STMT_ASM string PUNCT_DO string PUNCT_END ; + stmtIsr : STMT_ISR IDENTIFIER block PUNCT_ENDISR ; stmtFun : STMT_FUN name=IDENTIFIER @@ -117,7 +119,8 @@ stmtAssign : expr OPERATOR_ASSIGN expr # stmtAssignVal | expr OPERATOR_ASSIGN_DEC expr # stmtAssignDec ; -block : ( stmtConstant +block : ( stmtAsm + | stmtConstant | stmtFor | stmtIf | stmtLet diff --git a/jeff65/gold/grammar/Gold.py b/jeff65/gold/grammar/Gold.py index 73dced1..66da6ad 100644 --- a/jeff65/gold/grammar/Gold.py +++ b/jeff65/gold/grammar/Gold.py @@ -5,133 +5,138 @@ def serializedATN(): with StringIO() as buf: - buf.write("\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3B") - buf.write("\u011c\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") + buf.write("\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3C") + buf.write("\u0125\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7") buf.write("\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16") buf.write("\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22\4\23\t\23") - buf.write("\4\24\t\24\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2") - buf.write("\5\2\64\n\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3") + buf.write("\4\24\t\24\4\25\t\25\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3") + buf.write("\2\3\2\3\2\5\2\66\n\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2") + buf.write("\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3") buf.write("\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2") - buf.write("\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\3\2\7") - buf.write("\2[\n\2\f\2\16\2^\13\2\5\2`\n\2\3\2\7\2c\n\2\f\2\16\2") - buf.write("f\13\2\3\3\3\3\3\3\3\3\7\3l\n\3\f\3\16\3o\13\3\5\3q\n") - buf.write("\3\3\3\3\3\3\4\6\4v\n\4\r\4\16\4w\3\5\3\5\3\6\3\6\3\6") - buf.write("\3\6\5\6\u0080\n\6\3\6\3\6\3\6\3\6\3\6\5\6\u0087\n\6\3") - buf.write("\6\3\6\3\6\5\6\u008c\n\6\3\6\3\6\3\6\3\6\5\6\u0092\n\6") - buf.write("\3\6\3\6\5\6\u0096\n\6\3\7\3\7\3\7\3\7\3\b\3\b\3\b\3\b") - buf.write("\3\t\3\t\3\t\3\t\3\t\5\t\u00a5\n\t\3\n\3\n\3\n\3\13\3") - buf.write("\13\5\13\u00ac\n\13\3\13\3\13\3\13\3\13\3\13\5\13\u00b3") + buf.write("\3\2\3\2\7\2]\n\2\f\2\16\2`\13\2\5\2b\n\2\3\2\7\2e\n\2") + buf.write("\f\2\16\2h\13\2\3\3\3\3\3\3\3\3\7\3n\n\3\f\3\16\3q\13") + buf.write("\3\5\3s\n\3\3\3\3\3\3\4\6\4x\n\4\r\4\16\4y\3\5\3\5\3\6") + buf.write("\3\6\3\6\3\6\5\6\u0082\n\6\3\6\3\6\3\6\3\6\3\6\5\6\u0089") + buf.write("\n\6\3\6\3\6\3\6\5\6\u008e\n\6\3\6\3\6\3\6\3\6\5\6\u0094") + buf.write("\n\6\3\6\3\6\5\6\u0098\n\6\3\7\3\7\3\7\3\7\3\b\3\b\3\b") + buf.write("\3\b\3\t\3\t\3\t\3\t\3\t\5\t\u00a7\n\t\3\n\3\n\3\n\3\13") + buf.write("\3\13\5\13\u00ae\n\13\3\13\3\13\3\13\3\13\3\13\5\13\u00b5") buf.write("\n\13\3\f\3\f\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\5\r") - buf.write("\u00c0\n\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3") - buf.write("\16\7\16\u00cc\n\16\f\16\16\16\u00cf\13\16\3\16\3\16\5") - buf.write("\16\u00d3\n\16\3\16\3\16\3\17\3\17\3\17\3\17\3\17\3\20") - buf.write("\3\20\3\20\3\20\3\20\3\20\7\20\u00e2\n\20\f\20\16\20\u00e5") - buf.write("\13\20\5\20\u00e7\n\20\3\20\3\20\3\20\5\20\u00ec\n\20") - buf.write("\3\20\3\20\3\20\3\21\3\21\5\21\u00f3\n\21\3\22\3\22\3") - buf.write("\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\5\22") - buf.write("\u0101\n\22\3\23\3\23\3\23\3\23\3\23\3\23\3\23\3\23\7") - buf.write("\23\u010b\n\23\f\23\16\23\u010e\13\23\3\24\3\24\3\24\3") - buf.write("\24\3\24\7\24\u0115\n\24\f\24\16\24\u0118\13\24\3\24\3") - buf.write("\24\3\24\2\3\2\25\2\4\6\b\n\f\16\20\22\24\26\30\32\34") - buf.write("\36 \"$&\2\7\3\2\r\16\3\2\b\t\3\2\6\7\3\2\23\30\3\2()") - buf.write("\u013d\2\63\3\2\2\2\4g\3\2\2\2\6u\3\2\2\2\by\3\2\2\2\n") - buf.write("\u0095\3\2\2\2\f\u0097\3\2\2\2\16\u009b\3\2\2\2\20\u009f") - buf.write("\3\2\2\2\22\u00a6\3\2\2\2\24\u00a9\3\2\2\2\26\u00b4\3") - buf.write("\2\2\2\30\u00ba\3\2\2\2\32\u00c5\3\2\2\2\34\u00d6\3\2") - buf.write("\2\2\36\u00db\3\2\2\2 \u00f0\3\2\2\2\"\u0100\3\2\2\2$") - buf.write("\u010c\3\2\2\2&\u0116\3\2\2\2()\b\2\1\2)*\7\35\2\2*\64") - buf.write("\5\2\2\16+,\7\7\2\2,\64\5\2\2\r-.\7\67\2\2./\5\2\2\2/") - buf.write("\60\78\2\2\60\64\3\2\2\2\61\64\7\4\2\2\62\64\7\3\2\2\63") - buf.write("(\3\2\2\2\63+\3\2\2\2\63-\3\2\2\2\63\61\3\2\2\2\63\62") - buf.write("\3\2\2\2\64d\3\2\2\2\65\66\f\f\2\2\66\67\7\17\2\2\67c") - buf.write("\5\2\2\r89\f\13\2\29:\7\20\2\2:c\5\2\2\f;<\f\n\2\2<=\7") - buf.write("\21\2\2=c\5\2\2\13>?\f\t\2\2?@\7\22\2\2@c\5\2\2\nAB\f") - buf.write("\b\2\2BC\t\2\2\2Cc\5\2\2\tDE\f\7\2\2EF\t\3\2\2Fc\5\2\2") - buf.write("\bGH\f\6\2\2HI\t\4\2\2Ic\5\2\2\7JK\f\5\2\2KL\t\5\2\2L") - buf.write("c\5\2\2\6MN\f\21\2\2NO\7\34\2\2Oc\7\3\2\2PQ\f\20\2\2Q") - buf.write("R\79\2\2RS\5\2\2\2ST\7:\2\2Tc\3\2\2\2UV\f\17\2\2V_\7\67") - buf.write("\2\2W\\\5\2\2\2XY\7\65\2\2Y[\5\2\2\2ZX\3\2\2\2[^\3\2\2") - buf.write("\2\\Z\3\2\2\2\\]\3\2\2\2]`\3\2\2\2^\\\3\2\2\2_W\3\2\2") - buf.write("\2_`\3\2\2\2`a\3\2\2\2ac\78\2\2b\65\3\2\2\2b8\3\2\2\2") - buf.write("b;\3\2\2\2b>\3\2\2\2bA\3\2\2\2bD\3\2\2\2bG\3\2\2\2bJ\3") - buf.write("\2\2\2bM\3\2\2\2bP\3\2\2\2bU\3\2\2\2cf\3\2\2\2db\3\2\2") - buf.write("\2de\3\2\2\2e\3\3\2\2\2fd\3\2\2\2gp\79\2\2hm\5\2\2\2i") - buf.write("j\7\65\2\2jl\5\2\2\2ki\3\2\2\2lo\3\2\2\2mk\3\2\2\2mn\3") - buf.write("\2\2\2nq\3\2\2\2om\3\2\2\2ph\3\2\2\2pq\3\2\2\2qr\3\2\2") - buf.write("\2rs\7:\2\2s\5\3\2\2\2tv\7\5\2\2ut\3\2\2\2vw\3\2\2\2w") - buf.write("u\3\2\2\2wx\3\2\2\2x\7\3\2\2\2yz\t\6\2\2z\t\3\2\2\2{\u0096") - buf.write("\7\3\2\2|}\7\36\2\2}\177\79\2\2~\u0080\5\b\5\2\177~\3") - buf.write("\2\2\2\177\u0080\3\2\2\2\u0080\u0081\3\2\2\2\u0081\u0082") - buf.write("\5\n\6\2\u0082\u0083\7:\2\2\u0083\u0096\3\2\2\2\u0084") - buf.write("\u0086\7\36\2\2\u0085\u0087\5\b\5\2\u0086\u0085\3\2\2") - buf.write("\2\u0086\u0087\3\2\2\2\u0087\u0088\3\2\2\2\u0088\u0096") - buf.write("\5\n\6\2\u0089\u008b\79\2\2\u008a\u008c\5\b\5\2\u008b") - buf.write("\u008a\3\2\2\2\u008b\u008c\3\2\2\2\u008c\u008d\3\2\2\2") - buf.write("\u008d\u008e\5\n\6\2\u008e\u0091\7\64\2\2\u008f\u0092") - buf.write("\5\2\2\2\u0090\u0092\5\f\7\2\u0091\u008f\3\2\2\2\u0091") - buf.write("\u0090\3\2\2\2\u0092\u0093\3\2\2\2\u0093\u0094\7:\2\2") - buf.write("\u0094\u0096\3\2\2\2\u0095{\3\2\2\2\u0095|\3\2\2\2\u0095") - buf.write("\u0084\3\2\2\2\u0095\u0089\3\2\2\2\u0096\13\3\2\2\2\u0097") - buf.write("\u0098\5\2\2\2\u0098\u0099\7\62\2\2\u0099\u009a\5\2\2") - buf.write("\2\u009a\r\3\2\2\2\u009b\u009c\7\3\2\2\u009c\u009d\7\63") - buf.write("\2\2\u009d\u009e\5\n\6\2\u009e\17\3\2\2\2\u009f\u00a0") - buf.write("\7\37\2\2\u00a0\u00a1\5\16\b\2\u00a1\u00a4\7\31\2\2\u00a2") - buf.write("\u00a5\5\2\2\2\u00a3\u00a5\5\4\3\2\u00a4\u00a2\3\2\2\2") - buf.write("\u00a4\u00a3\3\2\2\2\u00a5\21\3\2\2\2\u00a6\u00a7\7&\2") - buf.write("\2\u00a7\u00a8\7\3\2\2\u00a8\23\3\2\2\2\u00a9\u00ab\7") - buf.write("$\2\2\u00aa\u00ac\5\b\5\2\u00ab\u00aa\3\2\2\2\u00ab\u00ac") - buf.write("\3\2\2\2\u00ac\u00ad\3\2\2\2\u00ad\u00ae\5\16\b\2\u00ae") - buf.write("\u00b2\7\31\2\2\u00af\u00b3\5\2\2\2\u00b0\u00b3\5\4\3") - buf.write("\2\u00b1\u00b3\5\6\4\2\u00b2\u00af\3\2\2\2\u00b2\u00b0") - buf.write("\3\2\2\2\u00b2\u00b1\3\2\2\2\u00b3\25\3\2\2\2\u00b4\u00b5") - buf.write("\7\'\2\2\u00b5\u00b6\5\2\2\2\u00b6\u00b7\7*\2\2\u00b7") - buf.write("\u00b8\5$\23\2\u00b8\u00b9\7-\2\2\u00b9\27\3\2\2\2\u00ba") - buf.write("\u00bb\7 \2\2\u00bb\u00bc\5\16\b\2\u00bc\u00bf\7\60\2") - buf.write("\2\u00bd\u00c0\5\f\7\2\u00be\u00c0\5\2\2\2\u00bf\u00bd") - buf.write("\3\2\2\2\u00bf\u00be\3\2\2\2\u00c0\u00c1\3\2\2\2\u00c1") - buf.write("\u00c2\7*\2\2\u00c2\u00c3\5$\23\2\u00c3\u00c4\7-\2\2\u00c4") - buf.write("\31\3\2\2\2\u00c5\u00c6\7\"\2\2\u00c6\u00c7\5\2\2\2\u00c7") - buf.write("\u00c8\7\61\2\2\u00c8\u00cd\5$\23\2\u00c9\u00ca\7,\2\2") - buf.write("\u00ca\u00cc\5$\23\2\u00cb\u00c9\3\2\2\2\u00cc\u00cf\3") - buf.write("\2\2\2\u00cd\u00cb\3\2\2\2\u00cd\u00ce\3\2\2\2\u00ce\u00d2") - buf.write("\3\2\2\2\u00cf\u00cd\3\2\2\2\u00d0\u00d1\7+\2\2\u00d1") - buf.write("\u00d3\5$\23\2\u00d2\u00d0\3\2\2\2\u00d2\u00d3\3\2\2\2") - buf.write("\u00d3\u00d4\3\2\2\2\u00d4\u00d5\7-\2\2\u00d5\33\3\2\2") - buf.write("\2\u00d6\u00d7\7#\2\2\u00d7\u00d8\7\3\2\2\u00d8\u00d9") - buf.write("\5$\23\2\u00d9\u00da\7/\2\2\u00da\35\3\2\2\2\u00db\u00dc") - buf.write("\7!\2\2\u00dc\u00dd\7\3\2\2\u00dd\u00e6\7\67\2\2\u00de") - buf.write("\u00e3\5\16\b\2\u00df\u00e0\7\65\2\2\u00e0\u00e2\5\16") - buf.write("\b\2\u00e1\u00df\3\2\2\2\u00e2\u00e5\3\2\2\2\u00e3\u00e1") - buf.write("\3\2\2\2\u00e3\u00e4\3\2\2\2\u00e4\u00e7\3\2\2\2\u00e5") - buf.write("\u00e3\3\2\2\2\u00e6\u00de\3\2\2\2\u00e6\u00e7\3\2\2\2") - buf.write("\u00e7\u00e8\3\2\2\2\u00e8\u00eb\78\2\2\u00e9\u00ea\7") - buf.write("\66\2\2\u00ea\u00ec\5\n\6\2\u00eb\u00e9\3\2\2\2\u00eb") - buf.write("\u00ec\3\2\2\2\u00ec\u00ed\3\2\2\2\u00ed\u00ee\5$\23\2") - buf.write("\u00ee\u00ef\7.\2\2\u00ef\37\3\2\2\2\u00f0\u00f2\7%\2") - buf.write("\2\u00f1\u00f3\5\2\2\2\u00f2\u00f1\3\2\2\2\u00f2\u00f3") - buf.write("\3\2\2\2\u00f3!\3\2\2\2\u00f4\u00f5\5\2\2\2\u00f5\u00f6") - buf.write("\7\31\2\2\u00f6\u00f7\5\2\2\2\u00f7\u0101\3\2\2\2\u00f8") - buf.write("\u00f9\5\2\2\2\u00f9\u00fa\7\32\2\2\u00fa\u00fb\5\2\2") - buf.write("\2\u00fb\u0101\3\2\2\2\u00fc\u00fd\5\2\2\2\u00fd\u00fe") - buf.write("\7\33\2\2\u00fe\u00ff\5\2\2\2\u00ff\u0101\3\2\2\2\u0100") - buf.write("\u00f4\3\2\2\2\u0100\u00f8\3\2\2\2\u0100\u00fc\3\2\2\2") - buf.write("\u0101#\3\2\2\2\u0102\u010b\5\20\t\2\u0103\u010b\5\30") - buf.write("\r\2\u0104\u010b\5\32\16\2\u0105\u010b\5\24\13\2\u0106") - buf.write("\u010b\5 \21\2\u0107\u010b\5\26\f\2\u0108\u010b\5\"\22") - buf.write("\2\u0109\u010b\5\2\2\2\u010a\u0102\3\2\2\2\u010a\u0103") - buf.write("\3\2\2\2\u010a\u0104\3\2\2\2\u010a\u0105\3\2\2\2\u010a") - buf.write("\u0106\3\2\2\2\u010a\u0107\3\2\2\2\u010a\u0108\3\2\2\2") - buf.write("\u010a\u0109\3\2\2\2\u010b\u010e\3\2\2\2\u010c\u010a\3") - buf.write("\2\2\2\u010c\u010d\3\2\2\2\u010d%\3\2\2\2\u010e\u010c") - buf.write("\3\2\2\2\u010f\u0115\5\20\t\2\u0110\u0115\5\34\17\2\u0111") - buf.write("\u0115\5\24\13\2\u0112\u0115\5\22\n\2\u0113\u0115\5\36") - buf.write("\20\2\u0114\u010f\3\2\2\2\u0114\u0110\3\2\2\2\u0114\u0111") - buf.write("\3\2\2\2\u0114\u0112\3\2\2\2\u0114\u0113\3\2\2\2\u0115") - buf.write("\u0118\3\2\2\2\u0116\u0114\3\2\2\2\u0116\u0117\3\2\2\2") - buf.write("\u0117\u0119\3\2\2\2\u0118\u0116\3\2\2\2\u0119\u011a\7") - buf.write("\2\2\3\u011a\'\3\2\2\2\36\63\\_bdmpw\177\u0086\u008b\u0091") - buf.write("\u0095\u00a4\u00ab\u00b2\u00bf\u00cd\u00d2\u00e3\u00e6") - buf.write("\u00eb\u00f2\u0100\u010a\u010c\u0114\u0116") + buf.write("\u00c2\n\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3") + buf.write("\16\7\16\u00ce\n\16\f\16\16\16\u00d1\13\16\3\16\3\16\5") + buf.write("\16\u00d5\n\16\3\16\3\16\3\17\3\17\3\17\3\17\3\17\3\17") + buf.write("\3\20\3\20\3\20\3\20\3\20\3\21\3\21\3\21\3\21\3\21\3\21") + buf.write("\7\21\u00ea\n\21\f\21\16\21\u00ed\13\21\5\21\u00ef\n\21") + buf.write("\3\21\3\21\3\21\5\21\u00f4\n\21\3\21\3\21\3\21\3\22\3") + buf.write("\22\5\22\u00fb\n\22\3\23\3\23\3\23\3\23\3\23\3\23\3\23") + buf.write("\3\23\3\23\3\23\3\23\3\23\5\23\u0109\n\23\3\24\3\24\3") + buf.write("\24\3\24\3\24\3\24\3\24\3\24\3\24\7\24\u0114\n\24\f\24") + buf.write("\16\24\u0117\13\24\3\25\3\25\3\25\3\25\3\25\7\25\u011e") + buf.write("\n\25\f\25\16\25\u0121\13\25\3\25\3\25\3\25\2\3\2\26\2") + buf.write("\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(\2\7\3\2\r") + buf.write("\16\3\2\b\t\3\2\6\7\3\2\23\30\3\2)*\u0146\2\65\3\2\2\2") + buf.write("\4i\3\2\2\2\6w\3\2\2\2\b{\3\2\2\2\n\u0097\3\2\2\2\f\u0099") + buf.write("\3\2\2\2\16\u009d\3\2\2\2\20\u00a1\3\2\2\2\22\u00a8\3") + buf.write("\2\2\2\24\u00ab\3\2\2\2\26\u00b6\3\2\2\2\30\u00bc\3\2") + buf.write("\2\2\32\u00c7\3\2\2\2\34\u00d8\3\2\2\2\36\u00de\3\2\2") + buf.write("\2 \u00e3\3\2\2\2\"\u00f8\3\2\2\2$\u0108\3\2\2\2&\u0115") + buf.write("\3\2\2\2(\u011f\3\2\2\2*+\b\2\1\2+,\7\35\2\2,\66\5\2\2") + buf.write("\16-.\7\7\2\2.\66\5\2\2\r/\60\78\2\2\60\61\5\2\2\2\61") + buf.write("\62\79\2\2\62\66\3\2\2\2\63\66\7\4\2\2\64\66\7\3\2\2\65") + buf.write("*\3\2\2\2\65-\3\2\2\2\65/\3\2\2\2\65\63\3\2\2\2\65\64") + buf.write("\3\2\2\2\66f\3\2\2\2\678\f\f\2\289\7\17\2\29e\5\2\2\r") + buf.write(":;\f\13\2\2;<\7\20\2\2\f\n\2\2>?\7\21\2\2") + buf.write("?e\5\2\2\13@A\f\t\2\2AB\7\22\2\2Be\5\2\2\nCD\f\b\2\2D") + buf.write("E\t\2\2\2Ee\5\2\2\tFG\f\7\2\2GH\t\3\2\2He\5\2\2\bIJ\f") + buf.write("\6\2\2JK\t\4\2\2Ke\5\2\2\7LM\f\5\2\2MN\t\5\2\2Ne\5\2\2") + buf.write("\6OP\f\21\2\2PQ\7\34\2\2Qe\7\3\2\2RS\f\20\2\2ST\7:\2\2") + buf.write("TU\5\2\2\2UV\7;\2\2Ve\3\2\2\2WX\f\17\2\2Xa\78\2\2Y^\5") + buf.write("\2\2\2Z[\7\66\2\2[]\5\2\2\2\\Z\3\2\2\2]`\3\2\2\2^\\\3") + buf.write("\2\2\2^_\3\2\2\2_b\3\2\2\2`^\3\2\2\2aY\3\2\2\2ab\3\2\2") + buf.write("\2bc\3\2\2\2ce\79\2\2d\67\3\2\2\2d:\3\2\2\2d=\3\2\2\2") + buf.write("d@\3\2\2\2dC\3\2\2\2dF\3\2\2\2dI\3\2\2\2dL\3\2\2\2dO\3") + buf.write("\2\2\2dR\3\2\2\2dW\3\2\2\2eh\3\2\2\2fd\3\2\2\2fg\3\2\2") + buf.write("\2g\3\3\2\2\2hf\3\2\2\2ir\7:\2\2jo\5\2\2\2kl\7\66\2\2") + buf.write("ln\5\2\2\2mk\3\2\2\2nq\3\2\2\2om\3\2\2\2op\3\2\2\2ps\3") + buf.write("\2\2\2qo\3\2\2\2rj\3\2\2\2rs\3\2\2\2st\3\2\2\2tu\7;\2") + buf.write("\2u\5\3\2\2\2vx\7\5\2\2wv\3\2\2\2xy\3\2\2\2yw\3\2\2\2") + buf.write("yz\3\2\2\2z\7\3\2\2\2{|\t\6\2\2|\t\3\2\2\2}\u0098\7\3") + buf.write("\2\2~\177\7\36\2\2\177\u0081\7:\2\2\u0080\u0082\5\b\5") + buf.write("\2\u0081\u0080\3\2\2\2\u0081\u0082\3\2\2\2\u0082\u0083") + buf.write("\3\2\2\2\u0083\u0084\5\n\6\2\u0084\u0085\7;\2\2\u0085") + buf.write("\u0098\3\2\2\2\u0086\u0088\7\36\2\2\u0087\u0089\5\b\5") + buf.write("\2\u0088\u0087\3\2\2\2\u0088\u0089\3\2\2\2\u0089\u008a") + buf.write("\3\2\2\2\u008a\u0098\5\n\6\2\u008b\u008d\7:\2\2\u008c") + buf.write("\u008e\5\b\5\2\u008d\u008c\3\2\2\2\u008d\u008e\3\2\2\2") + buf.write("\u008e\u008f\3\2\2\2\u008f\u0090\5\n\6\2\u0090\u0093\7") + buf.write("\65\2\2\u0091\u0094\5\2\2\2\u0092\u0094\5\f\7\2\u0093") + buf.write("\u0091\3\2\2\2\u0093\u0092\3\2\2\2\u0094\u0095\3\2\2\2") + buf.write("\u0095\u0096\7;\2\2\u0096\u0098\3\2\2\2\u0097}\3\2\2\2") + buf.write("\u0097~\3\2\2\2\u0097\u0086\3\2\2\2\u0097\u008b\3\2\2") + buf.write("\2\u0098\13\3\2\2\2\u0099\u009a\5\2\2\2\u009a\u009b\7") + buf.write("\63\2\2\u009b\u009c\5\2\2\2\u009c\r\3\2\2\2\u009d\u009e") + buf.write("\7\3\2\2\u009e\u009f\7\64\2\2\u009f\u00a0\5\n\6\2\u00a0") + buf.write("\17\3\2\2\2\u00a1\u00a2\7 \2\2\u00a2\u00a3\5\16\b\2\u00a3") + buf.write("\u00a6\7\31\2\2\u00a4\u00a7\5\2\2\2\u00a5\u00a7\5\4\3") + buf.write("\2\u00a6\u00a4\3\2\2\2\u00a6\u00a5\3\2\2\2\u00a7\21\3") + buf.write("\2\2\2\u00a8\u00a9\7\'\2\2\u00a9\u00aa\7\3\2\2\u00aa\23") + buf.write("\3\2\2\2\u00ab\u00ad\7%\2\2\u00ac\u00ae\5\b\5\2\u00ad") + buf.write("\u00ac\3\2\2\2\u00ad\u00ae\3\2\2\2\u00ae\u00af\3\2\2\2") + buf.write("\u00af\u00b0\5\16\b\2\u00b0\u00b4\7\31\2\2\u00b1\u00b5") + buf.write("\5\2\2\2\u00b2\u00b5\5\4\3\2\u00b3\u00b5\5\6\4\2\u00b4") + buf.write("\u00b1\3\2\2\2\u00b4\u00b2\3\2\2\2\u00b4\u00b3\3\2\2\2") + buf.write("\u00b5\25\3\2\2\2\u00b6\u00b7\7(\2\2\u00b7\u00b8\5\2\2") + buf.write("\2\u00b8\u00b9\7+\2\2\u00b9\u00ba\5&\24\2\u00ba\u00bb") + buf.write("\7.\2\2\u00bb\27\3\2\2\2\u00bc\u00bd\7!\2\2\u00bd\u00be") + buf.write("\5\16\b\2\u00be\u00c1\7\61\2\2\u00bf\u00c2\5\f\7\2\u00c0") + buf.write("\u00c2\5\2\2\2\u00c1\u00bf\3\2\2\2\u00c1\u00c0\3\2\2\2") + buf.write("\u00c2\u00c3\3\2\2\2\u00c3\u00c4\7+\2\2\u00c4\u00c5\5") + buf.write("&\24\2\u00c5\u00c6\7.\2\2\u00c6\31\3\2\2\2\u00c7\u00c8") + buf.write("\7#\2\2\u00c8\u00c9\5\2\2\2\u00c9\u00ca\7\62\2\2\u00ca") + buf.write("\u00cf\5&\24\2\u00cb\u00cc\7-\2\2\u00cc\u00ce\5&\24\2") + buf.write("\u00cd\u00cb\3\2\2\2\u00ce\u00d1\3\2\2\2\u00cf\u00cd\3") + buf.write("\2\2\2\u00cf\u00d0\3\2\2\2\u00d0\u00d4\3\2\2\2\u00d1\u00cf") + buf.write("\3\2\2\2\u00d2\u00d3\7,\2\2\u00d3\u00d5\5&\24\2\u00d4") + buf.write("\u00d2\3\2\2\2\u00d4\u00d5\3\2\2\2\u00d5\u00d6\3\2\2\2") + buf.write("\u00d6\u00d7\7.\2\2\u00d7\33\3\2\2\2\u00d8\u00d9\7\37") + buf.write("\2\2\u00d9\u00da\5\6\4\2\u00da\u00db\7+\2\2\u00db\u00dc") + buf.write("\5\6\4\2\u00dc\u00dd\7.\2\2\u00dd\35\3\2\2\2\u00de\u00df") + buf.write("\7$\2\2\u00df\u00e0\7\3\2\2\u00e0\u00e1\5&\24\2\u00e1") + buf.write("\u00e2\7\60\2\2\u00e2\37\3\2\2\2\u00e3\u00e4\7\"\2\2\u00e4") + buf.write("\u00e5\7\3\2\2\u00e5\u00ee\78\2\2\u00e6\u00eb\5\16\b\2") + buf.write("\u00e7\u00e8\7\66\2\2\u00e8\u00ea\5\16\b\2\u00e9\u00e7") + buf.write("\3\2\2\2\u00ea\u00ed\3\2\2\2\u00eb\u00e9\3\2\2\2\u00eb") + buf.write("\u00ec\3\2\2\2\u00ec\u00ef\3\2\2\2\u00ed\u00eb\3\2\2\2") + buf.write("\u00ee\u00e6\3\2\2\2\u00ee\u00ef\3\2\2\2\u00ef\u00f0\3") + buf.write("\2\2\2\u00f0\u00f3\79\2\2\u00f1\u00f2\7\67\2\2\u00f2\u00f4") + buf.write("\5\n\6\2\u00f3\u00f1\3\2\2\2\u00f3\u00f4\3\2\2\2\u00f4") + buf.write("\u00f5\3\2\2\2\u00f5\u00f6\5&\24\2\u00f6\u00f7\7/\2\2") + buf.write("\u00f7!\3\2\2\2\u00f8\u00fa\7&\2\2\u00f9\u00fb\5\2\2\2") + buf.write("\u00fa\u00f9\3\2\2\2\u00fa\u00fb\3\2\2\2\u00fb#\3\2\2") + buf.write("\2\u00fc\u00fd\5\2\2\2\u00fd\u00fe\7\31\2\2\u00fe\u00ff") + buf.write("\5\2\2\2\u00ff\u0109\3\2\2\2\u0100\u0101\5\2\2\2\u0101") + buf.write("\u0102\7\32\2\2\u0102\u0103\5\2\2\2\u0103\u0109\3\2\2") + buf.write("\2\u0104\u0105\5\2\2\2\u0105\u0106\7\33\2\2\u0106\u0107") + buf.write("\5\2\2\2\u0107\u0109\3\2\2\2\u0108\u00fc\3\2\2\2\u0108") + buf.write("\u0100\3\2\2\2\u0108\u0104\3\2\2\2\u0109%\3\2\2\2\u010a") + buf.write("\u0114\5\34\17\2\u010b\u0114\5\20\t\2\u010c\u0114\5\30") + buf.write("\r\2\u010d\u0114\5\32\16\2\u010e\u0114\5\24\13\2\u010f") + buf.write("\u0114\5\"\22\2\u0110\u0114\5\26\f\2\u0111\u0114\5$\23") + buf.write("\2\u0112\u0114\5\2\2\2\u0113\u010a\3\2\2\2\u0113\u010b") + buf.write("\3\2\2\2\u0113\u010c\3\2\2\2\u0113\u010d\3\2\2\2\u0113") + buf.write("\u010e\3\2\2\2\u0113\u010f\3\2\2\2\u0113\u0110\3\2\2\2") + buf.write("\u0113\u0111\3\2\2\2\u0113\u0112\3\2\2\2\u0114\u0117\3") + buf.write("\2\2\2\u0115\u0113\3\2\2\2\u0115\u0116\3\2\2\2\u0116\'") + buf.write("\3\2\2\2\u0117\u0115\3\2\2\2\u0118\u011e\5\20\t\2\u0119") + buf.write("\u011e\5\36\20\2\u011a\u011e\5\24\13\2\u011b\u011e\5\22") + buf.write("\n\2\u011c\u011e\5 \21\2\u011d\u0118\3\2\2\2\u011d\u0119") + buf.write("\3\2\2\2\u011d\u011a\3\2\2\2\u011d\u011b\3\2\2\2\u011d") + buf.write("\u011c\3\2\2\2\u011e\u0121\3\2\2\2\u011f\u011d\3\2\2\2") + buf.write("\u011f\u0120\3\2\2\2\u0120\u0122\3\2\2\2\u0121\u011f\3") + buf.write("\2\2\2\u0122\u0123\7\2\2\3\u0123)\3\2\2\2\36\65^adfor") + buf.write("y\u0081\u0088\u008d\u0093\u0097\u00a6\u00ad\u00b4\u00c1") + buf.write("\u00cf\u00d4\u00eb\u00ee\u00f3\u00fa\u0108\u0113\u0115") + buf.write("\u011d\u011f") return buf.getvalue() @@ -155,8 +160,8 @@ class Gold ( Parser ): "OPERATOR_EQ", "OPERATOR_LE", "OPERATOR_GE", "OPERATOR_LT", "OPERATOR_GT", "OPERATOR_ASSIGN", "OPERATOR_ASSIGN_INC", "OPERATOR_ASSIGN_DEC", "OPERATOR_DOT", "OPERATOR_DEREF", - "OPERATOR_REF", "STMT_CONSTANT", "STMT_FOR", "STMT_FUN", - "STMT_IF", "STMT_ISR", "STMT_LET", "STMT_RETURN", + "OPERATOR_REF", "STMT_ASM", "STMT_CONSTANT", "STMT_FOR", + "STMT_FUN", "STMT_IF", "STMT_ISR", "STMT_LET", "STMT_RETURN", "STMT_USE", "STMT_WHILE", "STORAGE_MUT", "STORAGE_STASH", "PUNCT_DO", "PUNCT_ELSE", "PUNCT_ELSEIF", "PUNCT_END", "PUNCT_ENDFUN", "PUNCT_ENDISR", "PUNCT_IN", "PUNCT_THEN", @@ -179,17 +184,18 @@ class Gold ( Parser ): RULE_stmtWhile = 10 RULE_stmtFor = 11 RULE_stmtIf = 12 - RULE_stmtIsr = 13 - RULE_stmtFun = 14 - RULE_stmtReturn = 15 - RULE_stmtAssign = 16 - RULE_block = 17 - RULE_unit = 18 + RULE_stmtAsm = 13 + RULE_stmtIsr = 14 + RULE_stmtFun = 15 + RULE_stmtReturn = 16 + RULE_stmtAssign = 17 + RULE_block = 18 + RULE_unit = 19 ruleNames = [ "expr", "array", "string", "storage", "typeId", "rangeTo", "declaration", "stmtConstant", "stmtUse", "stmtLet", - "stmtWhile", "stmtFor", "stmtIf", "stmtIsr", "stmtFun", - "stmtReturn", "stmtAssign", "block", "unit" ] + "stmtWhile", "stmtFor", "stmtIf", "stmtAsm", "stmtIsr", + "stmtFun", "stmtReturn", "stmtAssign", "block", "unit" ] EOF = Token.EOF IDENTIFIER=1 @@ -220,42 +226,43 @@ class Gold ( Parser ): OPERATOR_DOT=26 OPERATOR_DEREF=27 OPERATOR_REF=28 - STMT_CONSTANT=29 - STMT_FOR=30 - STMT_FUN=31 - STMT_IF=32 - STMT_ISR=33 - STMT_LET=34 - STMT_RETURN=35 - STMT_USE=36 - STMT_WHILE=37 - STORAGE_MUT=38 - STORAGE_STASH=39 - PUNCT_DO=40 - PUNCT_ELSE=41 - PUNCT_ELSEIF=42 - PUNCT_END=43 - PUNCT_ENDFUN=44 - PUNCT_ENDISR=45 - PUNCT_IN=46 - PUNCT_THEN=47 - PUNCT_TO=48 - PUNCT_COLON=49 - PUNCT_SEMICOLON=50 - PUNCT_COMMA=51 - PUNCT_ARROWR=52 - PAREN_OPEN=53 - PAREN_CLOSE=54 - BRACKET_OPEN=55 - BRACKET_CLOSE=56 - BRACE_OPEN=57 - BRACE_CLOSE=58 - STRING_DELIM=59 - COMMENT_OPEN=60 - COMMENT_CLOSE=61 - COMMENT_TEXT=62 - WHITESPACE=63 - MYSTERY=64 + STMT_ASM=29 + STMT_CONSTANT=30 + STMT_FOR=31 + STMT_FUN=32 + STMT_IF=33 + STMT_ISR=34 + STMT_LET=35 + STMT_RETURN=36 + STMT_USE=37 + STMT_WHILE=38 + STORAGE_MUT=39 + STORAGE_STASH=40 + PUNCT_DO=41 + PUNCT_ELSE=42 + PUNCT_ELSEIF=43 + PUNCT_END=44 + PUNCT_ENDFUN=45 + PUNCT_ENDISR=46 + PUNCT_IN=47 + PUNCT_THEN=48 + PUNCT_TO=49 + PUNCT_COLON=50 + PUNCT_SEMICOLON=51 + PUNCT_COMMA=52 + PUNCT_ARROWR=53 + PAREN_OPEN=54 + PAREN_CLOSE=55 + BRACKET_OPEN=56 + BRACKET_CLOSE=57 + BRACE_OPEN=58 + BRACE_CLOSE=59 + STRING_DELIM=60 + COMMENT_OPEN=61 + COMMENT_CLOSE=62 + COMMENT_TEXT=63 + WHITESPACE=64 + MYSTERY=65 def __init__(self, input:TokenStream): super().__init__(input) @@ -690,57 +697,57 @@ def expr(self, _p:int=0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 49 + self.state = 51 token = self._input.LA(1) if token in [Gold.OPERATOR_DEREF]: localctx = Gold.ExprDerefContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 39 + self.state = 41 self.match(Gold.OPERATOR_DEREF) - self.state = 40 + self.state = 42 self.expr(12) elif token in [Gold.OPERATOR_MINUS]: localctx = Gold.ExprNegationContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 41 + self.state = 43 self.match(Gold.OPERATOR_MINUS) - self.state = 42 + self.state = 44 self.expr(11) elif token in [Gold.PAREN_OPEN]: localctx = Gold.ExprParenContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 43 + self.state = 45 self.match(Gold.PAREN_OPEN) - self.state = 44 + self.state = 46 self.expr(0) - self.state = 45 + self.state = 47 self.match(Gold.PAREN_CLOSE) elif token in [Gold.NUMERIC]: localctx = Gold.ExprNumberContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 47 + self.state = 49 localctx.value = self.match(Gold.NUMERIC) elif token in [Gold.IDENTIFIER]: localctx = Gold.ExprIdContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 48 + self.state = 50 localctx.name = self.match(Gold.IDENTIFIER) else: raise NoViableAltException(self) self._ctx.stop = self._input.LT(-1) - self.state = 98 + self.state = 100 self._errHandler.sync(self) _alt = self._interp.adaptivePredict(self._input,4,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: @@ -748,158 +755,158 @@ def expr(self, _p:int=0): if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx - self.state = 96 + self.state = 98 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,3,self._ctx) if la_ == 1: localctx = Gold.ExprBitNotContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 51 + self.state = 53 if not self.precpred(self._ctx, 10): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 10)") - self.state = 52 + self.state = 54 self.match(Gold.OPERATOR_BITNOT) - self.state = 53 + self.state = 55 self.expr(11) pass elif la_ == 2: localctx = Gold.ExprBitAndContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 54 + self.state = 56 if not self.precpred(self._ctx, 9): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 9)") - self.state = 55 + self.state = 57 self.match(Gold.OPERATOR_BITAND) - self.state = 56 + self.state = 58 self.expr(10) pass elif la_ == 3: localctx = Gold.ExprBitOrContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 57 + self.state = 59 if not self.precpred(self._ctx, 8): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 8)") - self.state = 58 + self.state = 60 self.match(Gold.OPERATOR_BITOR) - self.state = 59 + self.state = 61 self.expr(9) pass elif la_ == 4: localctx = Gold.ExprBitXorContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 60 + self.state = 62 if not self.precpred(self._ctx, 7): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 7)") - self.state = 61 + self.state = 63 self.match(Gold.OPERATOR_BITXOR) - self.state = 62 + self.state = 64 self.expr(8) pass elif la_ == 5: localctx = Gold.ExprBitShiftContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 63 + self.state = 65 if not self.precpred(self._ctx, 6): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 6)") - self.state = 64 + self.state = 66 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not(_la==Gold.OPERATOR_SHR or _la==Gold.OPERATOR_SHL): localctx.op = self._errHandler.recoverInline(self) else: self.consume() - self.state = 65 + self.state = 67 self.expr(7) pass elif la_ == 6: localctx = Gold.ExprProductContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 66 + self.state = 68 if not self.precpred(self._ctx, 5): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 5)") - self.state = 67 + self.state = 69 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not(_la==Gold.OPERATOR_TIMES or _la==Gold.OPERATOR_DIVIDE): localctx.op = self._errHandler.recoverInline(self) else: self.consume() - self.state = 68 + self.state = 70 self.expr(6) pass elif la_ == 7: localctx = Gold.ExprSumContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 69 + self.state = 71 if not self.precpred(self._ctx, 4): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 4)") - self.state = 70 + self.state = 72 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not(_la==Gold.OPERATOR_PLUS or _la==Gold.OPERATOR_MINUS): localctx.op = self._errHandler.recoverInline(self) else: self.consume() - self.state = 71 + self.state = 73 self.expr(5) pass elif la_ == 8: localctx = Gold.ExprCompareContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 72 + self.state = 74 if not self.precpred(self._ctx, 3): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") - self.state = 73 + self.state = 75 localctx.op = self._input.LT(1) _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.OPERATOR_NE) | (1 << Gold.OPERATOR_EQ) | (1 << Gold.OPERATOR_LE) | (1 << Gold.OPERATOR_GE) | (1 << Gold.OPERATOR_LT) | (1 << Gold.OPERATOR_GT))) != 0)): localctx.op = self._errHandler.recoverInline(self) else: self.consume() - self.state = 74 + self.state = 76 self.expr(4) pass elif la_ == 9: localctx = Gold.ExprMemberContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 75 + self.state = 77 if not self.precpred(self._ctx, 15): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 15)") - self.state = 76 + self.state = 78 self.match(Gold.OPERATOR_DOT) - self.state = 77 + self.state = 79 localctx.member = self.match(Gold.IDENTIFIER) pass elif la_ == 10: localctx = Gold.ExprIndexContext(self, Gold.ExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 78 + self.state = 80 if not self.precpred(self._ctx, 14): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 14)") - self.state = 79 + self.state = 81 self.match(Gold.BRACKET_OPEN) - self.state = 80 + self.state = 82 self.expr(0) - self.state = 81 + self.state = 83 self.match(Gold.BRACKET_CLOSE) pass @@ -907,39 +914,39 @@ def expr(self, _p:int=0): localctx = Gold.ExprFunCallContext(self, Gold.ExprContext(self, _parentctx, _parentState)) localctx.fun = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_expr) - self.state = 83 + self.state = 85 if not self.precpred(self._ctx, 13): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 13)") - self.state = 84 + self.state = 86 self.match(Gold.PAREN_OPEN) - self.state = 93 + self.state = 95 _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.IDENTIFIER) | (1 << Gold.NUMERIC) | (1 << Gold.OPERATOR_MINUS) | (1 << Gold.OPERATOR_DEREF) | (1 << Gold.PAREN_OPEN))) != 0): - self.state = 85 + self.state = 87 localctx._expr = self.expr(0) localctx.args.append(localctx._expr) - self.state = 90 + self.state = 92 self._errHandler.sync(self) _la = self._input.LA(1) while _la==Gold.PUNCT_COMMA: - self.state = 86 + self.state = 88 self.match(Gold.PUNCT_COMMA) - self.state = 87 + self.state = 89 localctx._expr = self.expr(0) localctx.args.append(localctx._expr) - self.state = 92 + self.state = 94 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 95 + self.state = 97 self.match(Gold.PAREN_CLOSE) pass - self.state = 100 + self.state = 102 self._errHandler.sync(self) _alt = self._interp.adaptivePredict(self._input,4,self._ctx) @@ -997,28 +1004,28 @@ def array(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 101 + self.state = 103 self.match(Gold.BRACKET_OPEN) - self.state = 110 + self.state = 112 _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.IDENTIFIER) | (1 << Gold.NUMERIC) | (1 << Gold.OPERATOR_MINUS) | (1 << Gold.OPERATOR_DEREF) | (1 << Gold.PAREN_OPEN))) != 0): - self.state = 102 + self.state = 104 self.expr(0) - self.state = 107 + self.state = 109 self._errHandler.sync(self) _la = self._input.LA(1) while _la==Gold.PUNCT_COMMA: - self.state = 103 + self.state = 105 self.match(Gold.PUNCT_COMMA) - self.state = 104 + self.state = 106 self.expr(0) - self.state = 109 + self.state = 111 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 112 + self.state = 114 self.match(Gold.BRACKET_CLOSE) except RecognitionException as re: localctx.exception = re @@ -1063,14 +1070,14 @@ def string(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 115 + self.state = 117 self._errHandler.sync(self) _la = self._input.LA(1) while True: - self.state = 114 + self.state = 116 localctx._STRING = self.match(Gold.STRING) localctx.s.append(localctx._STRING) - self.state = 117 + self.state = 119 self._errHandler.sync(self) _la = self._input.LA(1) if not (_la==Gold.STRING): @@ -1118,7 +1125,7 @@ def storage(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 119 + self.state = 121 localctx.storage_class = self._input.LT(1) _la = self._input.LA(1) if not(_la==Gold.STORAGE_MUT or _la==Gold.STORAGE_STASH): @@ -1261,83 +1268,83 @@ def typeId(self): self.enterRule(localctx, 8, self.RULE_typeId) self._la = 0 # Token type try: - self.state = 147 + self.state = 149 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,12,self._ctx) if la_ == 1: localctx = Gold.TypePrimitiveContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 121 + self.state = 123 localctx.name = self.match(Gold.IDENTIFIER) pass elif la_ == 2: localctx = Gold.TypeSliceContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 122 + self.state = 124 self.match(Gold.OPERATOR_REF) - self.state = 123 - self.match(Gold.BRACKET_OPEN) self.state = 125 + self.match(Gold.BRACKET_OPEN) + self.state = 127 _la = self._input.LA(1) if _la==Gold.STORAGE_MUT or _la==Gold.STORAGE_STASH: - self.state = 124 + self.state = 126 self.storage() - self.state = 127 + self.state = 129 self.typeId() - self.state = 128 + self.state = 130 self.match(Gold.BRACKET_CLOSE) pass elif la_ == 3: localctx = Gold.TypePointerContext(self, localctx) self.enterOuterAlt(localctx, 3) - self.state = 130 - self.match(Gold.OPERATOR_REF) self.state = 132 + self.match(Gold.OPERATOR_REF) + self.state = 134 _la = self._input.LA(1) if _la==Gold.STORAGE_MUT or _la==Gold.STORAGE_STASH: - self.state = 131 + self.state = 133 self.storage() - self.state = 134 + self.state = 136 self.typeId() pass elif la_ == 4: localctx = Gold.TypeArrayContext(self, localctx) self.enterOuterAlt(localctx, 4) - self.state = 135 - self.match(Gold.BRACKET_OPEN) self.state = 137 + self.match(Gold.BRACKET_OPEN) + self.state = 139 _la = self._input.LA(1) if _la==Gold.STORAGE_MUT or _la==Gold.STORAGE_STASH: - self.state = 136 + self.state = 138 self.storage() - self.state = 139 + self.state = 141 self.typeId() - self.state = 140 + self.state = 142 self.match(Gold.PUNCT_SEMICOLON) - self.state = 143 + self.state = 145 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,11,self._ctx) if la_ == 1: - self.state = 141 + self.state = 143 self.expr(0) pass elif la_ == 2: - self.state = 142 + self.state = 144 self.rangeTo() pass - self.state = 145 + self.state = 147 self.match(Gold.BRACKET_CLOSE) pass @@ -1386,11 +1393,11 @@ def rangeTo(self): self.enterRule(localctx, 10, self.RULE_rangeTo) try: self.enterOuterAlt(localctx, 1) - self.state = 149 + self.state = 151 self.expr(0) - self.state = 150 + self.state = 152 self.match(Gold.PUNCT_TO) - self.state = 151 + self.state = 153 self.expr(0) except RecognitionException as re: localctx.exception = re @@ -1437,11 +1444,11 @@ def declaration(self): self.enterRule(localctx, 12, self.RULE_declaration) try: self.enterOuterAlt(localctx, 1) - self.state = 153 + self.state = 155 localctx.name = self.match(Gold.IDENTIFIER) - self.state = 154 + self.state = 156 self.match(Gold.PUNCT_COLON) - self.state = 155 + self.state = 157 self.typeId() except RecognitionException as re: localctx.exception = re @@ -1495,20 +1502,20 @@ def stmtConstant(self): self.enterRule(localctx, 14, self.RULE_stmtConstant) try: self.enterOuterAlt(localctx, 1) - self.state = 157 + self.state = 159 self.match(Gold.STMT_CONSTANT) - self.state = 158 + self.state = 160 self.declaration() - self.state = 159 + self.state = 161 self.match(Gold.OPERATOR_ASSIGN) - self.state = 162 + self.state = 164 token = self._input.LA(1) if token in [Gold.IDENTIFIER, Gold.NUMERIC, Gold.OPERATOR_MINUS, Gold.OPERATOR_DEREF, Gold.PAREN_OPEN]: - self.state = 160 + self.state = 162 self.expr(0) elif token in [Gold.BRACKET_OPEN]: - self.state = 161 + self.state = 163 self.array() else: @@ -1555,9 +1562,9 @@ def stmtUse(self): self.enterRule(localctx, 16, self.RULE_stmtUse) try: self.enterOuterAlt(localctx, 1) - self.state = 164 + self.state = 166 self.match(Gold.STMT_USE) - self.state = 165 + self.state = 167 localctx.unitId = self.match(Gold.IDENTIFIER) except RecognitionException as re: localctx.exception = re @@ -1620,31 +1627,31 @@ def stmtLet(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 167 - self.match(Gold.STMT_LET) self.state = 169 + self.match(Gold.STMT_LET) + self.state = 171 _la = self._input.LA(1) if _la==Gold.STORAGE_MUT or _la==Gold.STORAGE_STASH: - self.state = 168 + self.state = 170 self.storage() - self.state = 171 + self.state = 173 self.declaration() - self.state = 172 + self.state = 174 self.match(Gold.OPERATOR_ASSIGN) - self.state = 176 + self.state = 178 token = self._input.LA(1) if token in [Gold.IDENTIFIER, Gold.NUMERIC, Gold.OPERATOR_MINUS, Gold.OPERATOR_DEREF, Gold.PAREN_OPEN]: - self.state = 173 + self.state = 175 self.expr(0) elif token in [Gold.BRACKET_OPEN]: - self.state = 174 + self.state = 176 self.array() elif token in [Gold.STRING]: - self.state = 175 + self.state = 177 self.string() else: @@ -1701,15 +1708,15 @@ def stmtWhile(self): self.enterRule(localctx, 20, self.RULE_stmtWhile) try: self.enterOuterAlt(localctx, 1) - self.state = 178 + self.state = 180 self.match(Gold.STMT_WHILE) - self.state = 179 + self.state = 181 self.expr(0) - self.state = 180 + self.state = 182 self.match(Gold.PUNCT_DO) - self.state = 181 + self.state = 183 self.block() - self.state = 182 + self.state = 184 self.match(Gold.PUNCT_END) except RecognitionException as re: localctx.exception = re @@ -1773,31 +1780,31 @@ def stmtFor(self): self.enterRule(localctx, 22, self.RULE_stmtFor) try: self.enterOuterAlt(localctx, 1) - self.state = 184 + self.state = 186 self.match(Gold.STMT_FOR) - self.state = 185 + self.state = 187 self.declaration() - self.state = 186 + self.state = 188 self.match(Gold.PUNCT_IN) - self.state = 189 + self.state = 191 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,16,self._ctx) if la_ == 1: - self.state = 187 + self.state = 189 self.rangeTo() pass elif la_ == 2: - self.state = 188 + self.state = 190 self.expr(0) pass - self.state = 191 + self.state = 193 self.match(Gold.PUNCT_DO) - self.state = 192 + self.state = 194 self.block() - self.state = 193 + self.state = 195 self.match(Gold.PUNCT_END) except RecognitionException as re: localctx.exception = re @@ -1863,36 +1870,96 @@ def stmtIf(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 195 + self.state = 197 self.match(Gold.STMT_IF) - self.state = 196 + self.state = 198 self.expr(0) - self.state = 197 + self.state = 199 self.match(Gold.PUNCT_THEN) - self.state = 198 + self.state = 200 self.block() - self.state = 203 + self.state = 205 self._errHandler.sync(self) _la = self._input.LA(1) while _la==Gold.PUNCT_ELSEIF: - self.state = 199 + self.state = 201 self.match(Gold.PUNCT_ELSEIF) - self.state = 200 + self.state = 202 self.block() - self.state = 205 + self.state = 207 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 208 + self.state = 210 _la = self._input.LA(1) if _la==Gold.PUNCT_ELSE: - self.state = 206 + self.state = 208 self.match(Gold.PUNCT_ELSE) - self.state = 207 + self.state = 209 self.block() - self.state = 210 + self.state = 212 + self.match(Gold.PUNCT_END) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class StmtAsmContext(ParserRuleContext): + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STMT_ASM(self): + return self.getToken(Gold.STMT_ASM, 0) + + def string(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(Gold.StringContext) + else: + return self.getTypedRuleContext(Gold.StringContext,i) + + + def PUNCT_DO(self): + return self.getToken(Gold.PUNCT_DO, 0) + + def PUNCT_END(self): + return self.getToken(Gold.PUNCT_END, 0) + + def getRuleIndex(self): + return Gold.RULE_stmtAsm + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStmtAsm" ): + listener.enterStmtAsm(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStmtAsm" ): + listener.exitStmtAsm(self) + + + + + def stmtAsm(self): + + localctx = Gold.StmtAsmContext(self, self._ctx, self.state) + self.enterRule(localctx, 26, self.RULE_stmtAsm) + try: + self.enterOuterAlt(localctx, 1) + self.state = 214 + self.match(Gold.STMT_ASM) + self.state = 215 + self.string() + self.state = 216 + self.match(Gold.PUNCT_DO) + self.state = 217 + self.string() + self.state = 218 self.match(Gold.PUNCT_END) except RecognitionException as re: localctx.exception = re @@ -1938,16 +2005,16 @@ def exitRule(self, listener:ParseTreeListener): def stmtIsr(self): localctx = Gold.StmtIsrContext(self, self._ctx, self.state) - self.enterRule(localctx, 26, self.RULE_stmtIsr) + self.enterRule(localctx, 28, self.RULE_stmtIsr) try: self.enterOuterAlt(localctx, 1) - self.state = 212 + self.state = 220 self.match(Gold.STMT_ISR) - self.state = 213 + self.state = 221 self.match(Gold.IDENTIFIER) - self.state = 214 + self.state = 222 self.block() - self.state = 215 + self.state = 223 self.match(Gold.PUNCT_ENDISR) except RecognitionException as re: localctx.exception = re @@ -2023,51 +2090,51 @@ def exitRule(self, listener:ParseTreeListener): def stmtFun(self): localctx = Gold.StmtFunContext(self, self._ctx, self.state) - self.enterRule(localctx, 28, self.RULE_stmtFun) + self.enterRule(localctx, 30, self.RULE_stmtFun) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 217 + self.state = 225 self.match(Gold.STMT_FUN) - self.state = 218 + self.state = 226 localctx.name = self.match(Gold.IDENTIFIER) - self.state = 219 + self.state = 227 self.match(Gold.PAREN_OPEN) - self.state = 228 + self.state = 236 _la = self._input.LA(1) if _la==Gold.IDENTIFIER: - self.state = 220 + self.state = 228 localctx._declaration = self.declaration() localctx.args.append(localctx._declaration) - self.state = 225 + self.state = 233 self._errHandler.sync(self) _la = self._input.LA(1) while _la==Gold.PUNCT_COMMA: - self.state = 221 + self.state = 229 self.match(Gold.PUNCT_COMMA) - self.state = 222 + self.state = 230 localctx._declaration = self.declaration() localctx.args.append(localctx._declaration) - self.state = 227 + self.state = 235 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 230 + self.state = 238 self.match(Gold.PAREN_CLOSE) - self.state = 233 + self.state = 241 _la = self._input.LA(1) if _la==Gold.PUNCT_ARROWR: - self.state = 231 + self.state = 239 self.match(Gold.PUNCT_ARROWR) - self.state = 232 + self.state = 240 localctx.ret = self.typeId() - self.state = 235 + self.state = 243 self.block() - self.state = 236 + self.state = 244 self.match(Gold.PUNCT_ENDFUN) except RecognitionException as re: localctx.exception = re @@ -2107,16 +2174,16 @@ def exitRule(self, listener:ParseTreeListener): def stmtReturn(self): localctx = Gold.StmtReturnContext(self, self._ctx, self.state) - self.enterRule(localctx, 30, self.RULE_stmtReturn) + self.enterRule(localctx, 32, self.RULE_stmtReturn) try: self.enterOuterAlt(localctx, 1) - self.state = 238 + self.state = 246 self.match(Gold.STMT_RETURN) - self.state = 240 + self.state = 248 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,22,self._ctx) if la_ == 1: - self.state = 239 + self.state = 247 self.expr(0) @@ -2220,41 +2287,41 @@ def exitRule(self, listener:ParseTreeListener): def stmtAssign(self): localctx = Gold.StmtAssignContext(self, self._ctx, self.state) - self.enterRule(localctx, 32, self.RULE_stmtAssign) + self.enterRule(localctx, 34, self.RULE_stmtAssign) try: - self.state = 254 + self.state = 262 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,23,self._ctx) if la_ == 1: localctx = Gold.StmtAssignValContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 242 + self.state = 250 self.expr(0) - self.state = 243 + self.state = 251 self.match(Gold.OPERATOR_ASSIGN) - self.state = 244 + self.state = 252 self.expr(0) pass elif la_ == 2: localctx = Gold.StmtAssignIncContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 246 + self.state = 254 self.expr(0) - self.state = 247 + self.state = 255 self.match(Gold.OPERATOR_ASSIGN_INC) - self.state = 248 + self.state = 256 self.expr(0) pass elif la_ == 3: localctx = Gold.StmtAssignDecContext(self, localctx) self.enterOuterAlt(localctx, 3) - self.state = 250 + self.state = 258 self.expr(0) - self.state = 251 + self.state = 259 self.match(Gold.OPERATOR_ASSIGN_DEC) - self.state = 252 + self.state = 260 self.expr(0) pass @@ -2273,6 +2340,13 @@ def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): super().__init__(parent, invokingState) self.parser = parser + def stmtAsm(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(Gold.StmtAsmContext) + else: + return self.getTypedRuleContext(Gold.StmtAsmContext,i) + + def stmtConstant(self, i:int=None): if i is None: return self.getTypedRuleContexts(Gold.StmtConstantContext) @@ -2346,59 +2420,64 @@ def exitRule(self, listener:ParseTreeListener): def block(self): localctx = Gold.BlockContext(self, self._ctx, self.state) - self.enterRule(localctx, 34, self.RULE_block) + self.enterRule(localctx, 36, self.RULE_block) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 266 + self.state = 275 self._errHandler.sync(self) _la = self._input.LA(1) - while (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.IDENTIFIER) | (1 << Gold.NUMERIC) | (1 << Gold.OPERATOR_MINUS) | (1 << Gold.OPERATOR_DEREF) | (1 << Gold.STMT_CONSTANT) | (1 << Gold.STMT_FOR) | (1 << Gold.STMT_IF) | (1 << Gold.STMT_LET) | (1 << Gold.STMT_RETURN) | (1 << Gold.STMT_WHILE) | (1 << Gold.PAREN_OPEN))) != 0): - self.state = 264 + while (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.IDENTIFIER) | (1 << Gold.NUMERIC) | (1 << Gold.OPERATOR_MINUS) | (1 << Gold.OPERATOR_DEREF) | (1 << Gold.STMT_ASM) | (1 << Gold.STMT_CONSTANT) | (1 << Gold.STMT_FOR) | (1 << Gold.STMT_IF) | (1 << Gold.STMT_LET) | (1 << Gold.STMT_RETURN) | (1 << Gold.STMT_WHILE) | (1 << Gold.PAREN_OPEN))) != 0): + self.state = 273 self._errHandler.sync(self); la_ = self._interp.adaptivePredict(self._input,24,self._ctx) if la_ == 1: - self.state = 256 - self.stmtConstant() + self.state = 264 + self.stmtAsm() pass elif la_ == 2: - self.state = 257 - self.stmtFor() + self.state = 265 + self.stmtConstant() pass elif la_ == 3: - self.state = 258 - self.stmtIf() + self.state = 266 + self.stmtFor() pass elif la_ == 4: - self.state = 259 - self.stmtLet() + self.state = 267 + self.stmtIf() pass elif la_ == 5: - self.state = 260 - self.stmtReturn() + self.state = 268 + self.stmtLet() pass elif la_ == 6: - self.state = 261 - self.stmtWhile() + self.state = 269 + self.stmtReturn() pass elif la_ == 7: - self.state = 262 - self.stmtAssign() + self.state = 270 + self.stmtWhile() pass elif la_ == 8: - self.state = 263 + self.state = 271 + self.stmtAssign() + pass + + elif la_ == 9: + self.state = 272 self.expr(0) pass - self.state = 268 + self.state = 277 self._errHandler.sync(self) _la = self._input.LA(1) @@ -2471,44 +2550,44 @@ def exitRule(self, listener:ParseTreeListener): def unit(self): localctx = Gold.UnitContext(self, self._ctx, self.state) - self.enterRule(localctx, 36, self.RULE_unit) + self.enterRule(localctx, 38, self.RULE_unit) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 276 + self.state = 285 self._errHandler.sync(self) _la = self._input.LA(1) while (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << Gold.STMT_CONSTANT) | (1 << Gold.STMT_FUN) | (1 << Gold.STMT_ISR) | (1 << Gold.STMT_LET) | (1 << Gold.STMT_USE))) != 0): - self.state = 274 + self.state = 283 token = self._input.LA(1) if token in [Gold.STMT_CONSTANT]: - self.state = 269 + self.state = 278 self.stmtConstant() elif token in [Gold.STMT_ISR]: - self.state = 270 + self.state = 279 self.stmtIsr() elif token in [Gold.STMT_LET]: - self.state = 271 + self.state = 280 self.stmtLet() elif token in [Gold.STMT_USE]: - self.state = 272 + self.state = 281 self.stmtUse() elif token in [Gold.STMT_FUN]: - self.state = 273 + self.state = 282 self.stmtFun() else: raise NoViableAltException(self) - self.state = 278 + self.state = 287 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 279 + self.state = 288 self.match(Gold.EOF) except RecognitionException as re: localctx.exception = re diff --git a/jeff65/gold/grammar/Gold.tokens b/jeff65/gold/grammar/Gold.tokens index e32d277..e7a39f5 100644 --- a/jeff65/gold/grammar/Gold.tokens +++ b/jeff65/gold/grammar/Gold.tokens @@ -26,39 +26,40 @@ OPERATOR_ASSIGN_DEC=25 OPERATOR_DOT=26 OPERATOR_DEREF=27 OPERATOR_REF=28 -STMT_CONSTANT=29 -STMT_FOR=30 -STMT_FUN=31 -STMT_IF=32 -STMT_ISR=33 -STMT_LET=34 -STMT_RETURN=35 -STMT_USE=36 -STMT_WHILE=37 -STORAGE_MUT=38 -STORAGE_STASH=39 -PUNCT_DO=40 -PUNCT_ELSE=41 -PUNCT_ELSEIF=42 -PUNCT_END=43 -PUNCT_ENDFUN=44 -PUNCT_ENDISR=45 -PUNCT_IN=46 -PUNCT_THEN=47 -PUNCT_TO=48 -PUNCT_COLON=49 -PUNCT_SEMICOLON=50 -PUNCT_COMMA=51 -PUNCT_ARROWR=52 -PAREN_OPEN=53 -PAREN_CLOSE=54 -BRACKET_OPEN=55 -BRACKET_CLOSE=56 -BRACE_OPEN=57 -BRACE_CLOSE=58 -STRING_DELIM=59 -COMMENT_OPEN=60 -COMMENT_CLOSE=61 -COMMENT_TEXT=62 -WHITESPACE=63 -MYSTERY=64 +STMT_ASM=29 +STMT_CONSTANT=30 +STMT_FOR=31 +STMT_FUN=32 +STMT_IF=33 +STMT_ISR=34 +STMT_LET=35 +STMT_RETURN=36 +STMT_USE=37 +STMT_WHILE=38 +STORAGE_MUT=39 +STORAGE_STASH=40 +PUNCT_DO=41 +PUNCT_ELSE=42 +PUNCT_ELSEIF=43 +PUNCT_END=44 +PUNCT_ENDFUN=45 +PUNCT_ENDISR=46 +PUNCT_IN=47 +PUNCT_THEN=48 +PUNCT_TO=49 +PUNCT_COLON=50 +PUNCT_SEMICOLON=51 +PUNCT_COMMA=52 +PUNCT_ARROWR=53 +PAREN_OPEN=54 +PAREN_CLOSE=55 +BRACKET_OPEN=56 +BRACKET_CLOSE=57 +BRACE_OPEN=58 +BRACE_CLOSE=59 +STRING_DELIM=60 +COMMENT_OPEN=61 +COMMENT_CLOSE=62 +COMMENT_TEXT=63 +WHITESPACE=64 +MYSTERY=65 diff --git a/jeff65/gold/grammar/GoldListener.py b/jeff65/gold/grammar/GoldListener.py index a02e0d7..cff49aa 100644 --- a/jeff65/gold/grammar/GoldListener.py +++ b/jeff65/gold/grammar/GoldListener.py @@ -287,6 +287,15 @@ def exitStmtIf(self, ctx:Gold.StmtIfContext): pass + # Enter a parse tree produced by Gold#stmtAsm. + def enterStmtAsm(self, ctx:Gold.StmtAsmContext): + pass + + # Exit a parse tree produced by Gold#stmtAsm. + def exitStmtAsm(self, ctx:Gold.StmtAsmContext): + pass + + # Enter a parse tree produced by Gold#stmtIsr. def enterStmtIsr(self, ctx:Gold.StmtIsrContext): pass diff --git a/jeff65/gold/lexer.py b/jeff65/gold/lexer.py index 80631b5..0dd5fbd 100644 --- a/jeff65/gold/lexer.py +++ b/jeff65/gold/lexer.py @@ -61,6 +61,7 @@ '&': Parser.OPERATOR_REF, # statement leaders + 'asm': Parser.STMT_ASM, 'constant': Parser.STMT_CONSTANT, 'for': Parser.STMT_FOR, 'fun': Parser.STMT_FUN, diff --git a/requirements.txt b/requirements.txt index 66ccf9b..791e958 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ coverage>=4.5.1 antlr4-python3-runtime==4.5.2 coveralls>=1.3.0 hypothesis>=3.57.0 +attrs>=18.1.0 diff --git a/tests/test_sexp.py b/tests/test_sexp.py new file mode 100644 index 0000000..3be5dc4 --- /dev/null +++ b/tests/test_sexp.py @@ -0,0 +1,44 @@ +import string +from hypothesis import given +import hypothesis.strategies as st +from nose.tools import ( + assert_equal) +from jeff65.brundle.sexp import dumps, loads, Atom + + +def test_parse_empty(): + assert_equal([], loads('()')) + + +def test_parse_atom(): + assert_equal(Atom('spam'), loads('spam')) + + +def test_parse_string(): + assert_equal("spam", loads('"spam"')) + + +def test_parse_numeric(): + assert_equal(42, loads('42')) + + +def test_parse_nested(): + assert_equal([ + Atom('let'), [ + [Atom('spam'), 42], + [Atom('eggs'), "beans"], + ], + [Atom('foo'), Atom('bar')], + ], loads(''' + (let ((spam 42) + (eggs "beans")) + (foo bar)) + ''')) + + +@given(d=st.recursive( + st.integers() | st.text() | st.builds(Atom, st.text( + min_size=1, alphabet=string.ascii_letters)), + lambda children: st.lists(children))) +def test_roundtrip(d): + assert_equal(d, loads(dumps(d))) From dbd66af6ff174e404107df339ac10c19a5209e75 Mon Sep 17 00:00:00 2001 From: Jonathan David Page Date: Tue, 26 Jun 2018 23:01:56 -0400 Subject: [PATCH 2/3] Make it possible to serialize IL ASTs As part of this, some nodes that were previously custom classes became AstNodes. In particular, the entire gold.storage module is gone, along with gold.passes.asm.AsmRun. As an additional advantage, some compiler passes which were previously imperative are now pattern-based. --- jeff65/blum/types.py | 18 ++++++ jeff65/brundle/ast.py | 29 +++++++--- jeff65/brundle/sexp.py | 13 ++++- jeff65/gold/compiler.py | 2 +- jeff65/gold/mem.py | 4 ++ jeff65/gold/passes/asm.py | 96 +++++++++++++++----------------- jeff65/gold/passes/lower.py | 4 +- jeff65/gold/passes/resolve.py | 15 +++-- jeff65/gold/pattern.py | 2 +- jeff65/gold/storage.py | 51 ----------------- jeff65/gold/units.py | 5 ++ tests/test_pass_assemble.py | 101 +++++++++++++--------------------- tests/test_sexp.py | 12 +++- 13 files changed, 169 insertions(+), 183 deletions(-) delete mode 100644 jeff65/gold/storage.py diff --git a/jeff65/blum/types.py b/jeff65/blum/types.py index 31ecdf0..f741888 100644 --- a/jeff65/blum/types.py +++ b/jeff65/blum/types.py @@ -16,6 +16,7 @@ import struct from .fmt import Fmt +from ..brundle.sexp import Atom # We'll make this empty for now, and use it in class definitions, then mutate @@ -44,6 +45,9 @@ def validate(self): def _empty(cls): return cls() + def _ast_serialize(self): + return [Atom('phantom-type*')] + class VoidType: """A type with no values.""" @@ -64,6 +68,9 @@ def validate(self): def _empty(cls): return cls() + def _ast_serialize(self): + return [Atom('void-type*')] + class IntType: """An integral type.""" @@ -128,6 +135,9 @@ def validate(self): def _empty(cls): return cls(None, None) + def _ast_serialize(self): + return [Atom('int-type*'), self.width, self.signed] + class RefType: """A reference type.""" @@ -170,6 +180,9 @@ def validate(self): def _empty(cls): return cls(None) + def _ast_serialize(self): + return [Atom('ref-type*'), self.target._ast_serialize()] + class FunctionType: """A function type.""" @@ -212,6 +225,11 @@ def _empty(cls): obj.args = None return obj + def _ast_serialize(self): + return [Atom('fun-type*'), + self.ret._ast_serialize(), + [Atom('list*'), *(a._ast_serialize() for a in self.args)]] + u8 = IntType(1, signed=False) u16 = IntType(2, signed=False) diff --git a/jeff65/brundle/ast.py b/jeff65/brundle/ast.py index ac29fe3..6538bff 100644 --- a/jeff65/brundle/ast.py +++ b/jeff65/brundle/ast.py @@ -37,7 +37,7 @@ def get_attr_default(self, attr, default_value): self.attrs[attr] = default_value return self.attrs[attr] - def transform(self, transformer): + def transform(self, transformer, always_list=False): node = transformer.transform_enter(self.t, self) if transformer.transform_attrs and type(node) is AstNode: @@ -74,7 +74,7 @@ def transform(self, transformer): elif type(nodes) is not list: nodes = [nodes] - if self.t == 'unit': + if not always_list and self.t == 'unit': assert len(nodes) == 1 return nodes[0] return nodes @@ -111,12 +111,13 @@ def i(n=0): return "".join(pp) def dump(self, f): - data = self.transform(AstSerializer()) - sexp.dump(f, data) + sexp.dump(f, self._ast_serialize()) def dumps(self): - data = self.transform(AstSerializer()) - return sexp.dumps(data) + return sexp.dumps(self._ast_serialize()) + + def _ast_serialize(self): + return self.transform(AstSerializer()) class AstSerializer: @@ -129,6 +130,20 @@ def transform_exit(self, t, node): at = sexp.Atom(t) attrs = [] for k, v in node.attrs.items(): - attrs.append([sexp.Atom(':' + k), v]) + attrs.append([sexp.Atom(k), self._convert(v)]) # this has to be a double-list to avoid it being exploded return [[at, attrs, *node.children]] + + def _convert(self, value): + try: + return value._ast_serialize() + except AttributeError: + pass + + if isinstance(value, dict): + return [sexp.Atom('dict*'), + *([sexp.Atom('list*'), k, self._convert(v)] + for k, v in value.items())] + elif isinstance(value, bytes): + return [sexp.Atom('bytes*'), *value] + return value diff --git a/jeff65/brundle/sexp.py b/jeff65/brundle/sexp.py index 815674b..4ea4ff6 100644 --- a/jeff65/brundle/sexp.py +++ b/jeff65/brundle/sexp.py @@ -28,6 +28,7 @@ class T(Enum): ATOM = auto() STRING = auto() NUMERIC = auto() + BOOLEAN = auto() @attr.s(frozen=True, slots=True) @@ -50,6 +51,7 @@ class Atom: m_whitespace = re.compile(r'\s+', re.M) m_numeric = re.compile(fr'[+-]?\d[^\s{terminators}]*') m_atom = re.compile(fr'[\w:][^\s{terminators}]*') +m_bool = re.compile(r'#[tf]') singles = { '(': T.PAREN_OPEN, ')': T.PAREN_CLOSE, @@ -113,6 +115,11 @@ def make_token(t, text): if m: column = m.end() continue + m = m_bool.match(current, column) + if m: + yield make_token(T.BOOLEAN, m.group()) + column = m.end() + continue # This has to be run before the word match, since the word regex # matches numbers as well. m = m_numeric.match(current, column) @@ -151,6 +158,8 @@ def parse(tokens): data[-1].append(int(tok.text)) elif tok.t == T.STRING: data[-1].append(tok.text) + elif tok.t == T.BOOLEAN: + data[-1].append(tok.text == '#t') elif tok.t == T.ATOM: if tok.text == 'nil': data[-1].append(None) @@ -176,6 +185,8 @@ def dump(f, data, indent=0): it = ' '*indent if data is None: f.write('nil') + elif isinstance(data, bool): + f.write('#t' if data else '#f') elif isinstance(data, int): f.write('{}'.format(repr(data))) elif isinstance(data, str): @@ -189,7 +200,7 @@ def dump(f, data, indent=0): f.write('(') dump(f, data[0], indent+1) f.write(')') - elif not any(isinstance(e, list) for e in data): + elif not any(isinstance(e, list) and len(e) > 0 for e in data): f.write('('.format(it)) for e in data[:-1]: dump(f, e, indent) diff --git a/jeff65/gold/compiler.py b/jeff65/gold/compiler.py index 57cc8a1..095fd26 100644 --- a/jeff65/gold/compiler.py +++ b/jeff65/gold/compiler.py @@ -66,7 +66,7 @@ def translate(unit, verbose=False): obj = obj.transform(p()) if (verbose): print(p.__name__) - print(obj.pretty()) + print(obj.dumps()) archive = blum.Archive() for node in obj.children: diff --git a/jeff65/gold/mem.py b/jeff65/gold/mem.py index 37d3982..ee3b099 100644 --- a/jeff65/gold/mem.py +++ b/jeff65/gold/mem.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from ..brundle.sexp import Atom from ..blum import types from . import units @@ -52,3 +53,6 @@ def _as_address(self, arg): def __repr__(self): return "MemUnit()" + + def _ast_serialize(self): + return [Atom('unit*'), 'mem'] diff --git a/jeff65/gold/passes/asm.py b/jeff65/gold/passes/asm.py index 87c703c..e995c08 100644 --- a/jeff65/gold/passes/asm.py +++ b/jeff65/gold/passes/asm.py @@ -15,7 +15,7 @@ # along with this program. If not, see . import struct -from .. import ast, pattern, storage +from .. import ast, pattern class AssemblyError(Exception): @@ -23,88 +23,80 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -class AsmRun: - def __init__(self, data): - self.data = data - - def __repr__(self): - return "".format(self.data) +def asmrun(pos, fmt, *args): + return ast.AstNode('asmrun', pos, attrs={ + 'bin': struct.pack(fmt, *args) + }) @pattern.transform(pattern.Order.Any) def AssembleWithRelocations(p): yield ( - ast.AstNode('lda', p.any(), attrs={ - 'storage': storage.ImmediateStorage(p.any('value'), - p.require(1, AssemblyError)), - }), - lambda m: AsmRun(struct.pack('. - - -class AbsoluteStorage: - def __init__(self, address, width): - self.address = address - self.width = width - - def __repr__(self): - return "<{} bytes at ${:x}>".format(self.width, self.address) - - def _to_predicate(self, a, pf): - pa = a.make_predicate(self.address) - pw = a.make_predicate(self.width) - - def _storage_predicate(s): - return (pa._match(s.address) - and pw._match(s.width)) - return pf.predicate(_storage_predicate) - - -class ImmediateStorage: - def __init__(self, value, width): - self.value = value - self.width = width - - def __repr__(self): - return "".format(self.width, self.value) - - def _to_predicate(self, a, pf): - pv = a.make_predicate(self.value) - pw = a.make_predicate(self.width) - - def _storage_predicate(s): - return (pv._match(s.value) - and pw._match(s.width)) - return pf.predicate(_storage_predicate) diff --git a/jeff65/gold/units.py b/jeff65/gold/units.py index b099ae3..18a7598 100644 --- a/jeff65/gold/units.py +++ b/jeff65/gold/units.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from ..brundle.sexp import Atom + class ExternalUnit: """Represents an external unit.""" @@ -42,6 +44,9 @@ def __repr__(self): return "<{} {}.{}: {}>".format( type(self).__name__, self.unit, self.name, self.type) + def _ast_serialize(self): + return [Atom('get-member*'), self.unit._ast_serialize()] + class IntrinsicSymbol(UnitSymbol): """Represents a compiler intrinsic symbol. diff --git a/tests/test_pass_assemble.py b/tests/test_pass_assemble.py index 5518228..8bba0d1 100644 --- a/tests/test_pass_assemble.py +++ b/tests/test_pass_assemble.py @@ -1,10 +1,9 @@ import sys from nose.tools import ( assert_equal, - assert_false, assert_raises) from jeff65.blum import types -from jeff65.gold import ast, storage +from jeff65.gold import ast from jeff65.gold.passes import asm sys.stderr = sys.stdout @@ -15,7 +14,7 @@ def assemble(node): result = node.transform(asm.AssembleWithRelocations()) assert_equal(1, len(result)) assert_equal(backup, node) # check that the previous AST wasn't mutated - return result[0] + return result[0].attrs['bin'] def flatten(unit): @@ -26,87 +25,65 @@ def flatten(unit): def test_assemble_rts(): - a = asm.rts(None) - assert_equal('rts', a.t) - assert_equal(1, a.attrs['size']) - b = assemble(a) - assert_equal(bytes([0x60]), b.data) + assert_equal(b'\x60', assemble(asm.rts(None))) def test_assemble_jmp_abs(): - a = asm.jmp(None, storage.AbsoluteStorage(0xbeef, 0)) - assert_equal('jmp', a.t) - assert_equal(3, a.attrs['size']) - b = assemble(a) - assert_equal(bytes([0x4c, 0xef, 0xbe]), b.data) + assert_equal(b'\x4c\xef\xbe', assemble( + asm.jmp(None, ast.AstNode('absolute_storage', None, attrs={ + 'address': 0xbeef, + 'width': 0, + })))) def test_assemble_lda_imm(): - a = asm.lda(None, storage.ImmediateStorage(0x42, 1)) - assert_equal('lda', a.t) - assert_equal(2, a.attrs['size']) - b = assemble(a) - assert_equal(bytes([0xa9, 0x42]), b.data) + assert_equal(b'\xa9\x42', assemble( + asm.lda(None, ast.AstNode('immediate_storage', None, attrs={ + 'value': 0x42, + 'width': 1, + })) + )) def test_assemble_lda_imm_too_wide(): - a = asm.lda(None, storage.ImmediateStorage(0xcafe, 2)) - assert_raises(asm.AssemblyError, assemble, a) + assert_raises( + asm.AssemblyError, assemble, + asm.lda(None, ast.AstNode('immediate_storage', None, attrs={ + 'value': 0xcafe, + 'width': 2, + }))) def test_assemble_sta_abs(): - a = asm.sta(None, storage.AbsoluteStorage(0xbeef, 1)) - assert_equal('sta', a.t) - assert_equal(3, a.attrs['size']) - b = assemble(a) - assert_equal(bytes([0x8d, 0xef, 0xbe]), b.data) + assert_equal(b'\x8d\xef\xbe', assemble( + asm.sta(None, ast.AstNode('absolute_storage', None, attrs={ + 'address': 0xbeef, + 'width': 1, + })))) def test_assemble_sta_abs_too_wide(): - a = asm.sta(None, storage.AbsoluteStorage(0xbeef, 2)) - assert_raises(asm.AssemblyError, assemble, a) + assert_raises( + asm.AssemblyError, assemble, + asm.sta(None, ast.AstNode('absolute_storage', None, attrs={ + 'address': 0xbeef, + 'width': 2, + }))) def test_flatten_symbol(): - a = ast.AstNode('unit', None, attrs={ - 'known_names': {}, - }, children=[ - ast.AstNode('fun', None, attrs={ + assert_equal(ast.AstNode('unit', None, children=[ + ast.AstNode('fun_symbol', None, attrs={ 'name': 'meaning-of-life', 'type': types.FunctionType(types.u8), - }, children=[ - asm.AsmRun(bytes([0xa9, 0x42])), - asm.AsmRun(bytes([0x60])), - ]) - ]) - b = flatten(a) - assert_equal(1, len(b.children)) - sym = b.children[0] - assert_equal(0, len(sym.children)) - assert_equal('meaning-of-life', sym.attrs['name']) - assert_equal(types.FunctionType(types.u8), sym.attrs['type']) - assert_equal(bytes([0xa9, 0x42, 0x60]), sym.attrs['text']) - assert_false('return_addr' in sym.attrs) - - -def test_flatten_symbol_with_return_addr(): - a = ast.AstNode('unit', None, attrs={ - 'known_names': {}, - }, children=[ + 'text': b'\xa9\x42\x60', + }) + ]), flatten(ast.AstNode('unit', None, children=[ ast.AstNode('fun', None, attrs={ 'name': 'meaning-of-life', 'type': types.FunctionType(types.u8), - 'return_addr': '.+3', }, children=[ - asm.AsmRun(bytes([0xa9, 0x42])), - asm.AsmRun(bytes([0x4c, 0xff, 0xff])), + ast.AstNode('asmrun', None, attrs={'bin': b'\xa9\x42'}), + ast.AstNode('asmrun', None, attrs={'bin': b'\x60'}), ]) - ]) - b = flatten(a) - assert_equal(1, len(b.children)) - sym = b.children[0] - assert_equal(0, len(sym.children)) - assert_equal('meaning-of-life', sym.attrs['name']) - assert_equal(types.FunctionType(types.u8), sym.attrs['type']) - assert_equal(bytes([0xa9, 0x42, 0x4c, 0xff, 0xff]), sym.attrs['text']) - assert_equal('.+3', sym.attrs['return_addr']) + ]))) diff --git a/tests/test_sexp.py b/tests/test_sexp.py index 3be5dc4..b69df21 100644 --- a/tests/test_sexp.py +++ b/tests/test_sexp.py @@ -10,6 +10,15 @@ def test_parse_empty(): assert_equal([], loads('()')) +def test_parse_nil(): + assert_equal(None, loads('nil')) + + +def test_parse_bool(): + assert_equal(True, loads('#t')) + assert_equal(False, loads('#f')) + + def test_parse_atom(): assert_equal(Atom('spam'), loads('spam')) @@ -37,7 +46,8 @@ def test_parse_nested(): @given(d=st.recursive( - st.integers() | st.text() | st.builds(Atom, st.text( + st.none() | st.booleans() | st.integers() | st.text() | + st.builds(Atom, st.text( min_size=1, alphabet=string.ascii_letters)), lambda children: st.lists(children))) def test_roundtrip(d): From ca145f783b4ede57958f001177cf2d7f70034d20 Mon Sep 17 00:00:00 2001 From: Jonathan David Page Date: Wed, 27 Jun 2018 01:54:33 -0400 Subject: [PATCH 3/3] S-expression system now works with actual AstNodes instead of lists The point of this is to be able to include position information when parsing s-expressions. Additionally, this should make the IL deserializer that much simpler. --- jeff65/blum/types.py | 34 +++++--- jeff65/brundle/ast.py | 77 ++++++++++++----- jeff65/brundle/sexp.py | 184 +++++++++++++++++++++++++++++----------- jeff65/gold/compiler.py | 2 + jeff65/gold/mem.py | 6 +- jeff65/gold/units.py | 9 +- tests/test_sexp.py | 137 ++++++++++++++++++++++++------ 7 files changed, 334 insertions(+), 115 deletions(-) diff --git a/jeff65/blum/types.py b/jeff65/blum/types.py index f741888..71437e0 100644 --- a/jeff65/blum/types.py +++ b/jeff65/blum/types.py @@ -16,7 +16,7 @@ import struct from .fmt import Fmt -from ..brundle.sexp import Atom +from ..brundle import sexp # We'll make this empty for now, and use it in class definitions, then mutate @@ -45,8 +45,8 @@ def validate(self): def _empty(cls): return cls() - def _ast_serialize(self): - return [Atom('phantom-type*')] + def _il_serialize(self): + return sexp.slist(children=[sexp.satom('phantom-type')]) class VoidType: @@ -68,8 +68,8 @@ def validate(self): def _empty(cls): return cls() - def _ast_serialize(self): - return [Atom('void-type*')] + def _il_serialize(self): + return sexp.slist(children=[sexp.satom('void-type')]) class IntType: @@ -135,8 +135,11 @@ def validate(self): def _empty(cls): return cls(None, None) - def _ast_serialize(self): - return [Atom('int-type*'), self.width, self.signed] + def _il_serialize(self): + return sexp.slist(children=[ + sexp.satom('int-type'), + sexp.snumeric(self.width), + sexp.sboolean(self.signed)]) class RefType: @@ -180,8 +183,10 @@ def validate(self): def _empty(cls): return cls(None) - def _ast_serialize(self): - return [Atom('ref-type*'), self.target._ast_serialize()] + def _il_serialize(self): + return sexp.slist(children=[ + sexp.satom('ref-type'), + self.target._il_serialize()]) class FunctionType: @@ -225,10 +230,13 @@ def _empty(cls): obj.args = None return obj - def _ast_serialize(self): - return [Atom('fun-type*'), - self.ret._ast_serialize(), - [Atom('list*'), *(a._ast_serialize() for a in self.args)]] + def _il_serialize(self): + return sexp.slist(children=[ + sexp.satom('fun-type'), + self.ret._il_serialize(), + sexp.slist(children=[ + sexp.satom('list'), + *(a._il_serialize() for a in self.args)])]) u8 = IntType(1, signed=False) diff --git a/jeff65/brundle/ast.py b/jeff65/brundle/ast.py index 6538bff..b465699 100644 --- a/jeff65/brundle/ast.py +++ b/jeff65/brundle/ast.py @@ -15,13 +15,12 @@ # along with this program. If not, see . import attr -from . import sexp @attr.s(slots=True, repr=False) class AstNode: t = attr.ib() - position = attr.ib(cmp=False, hash=False) + position = attr.ib(default=None, cmp=False, hash=False) attrs = attr.ib(factory=dict) children = attr.ib(factory=list) @@ -80,6 +79,8 @@ def transform(self, transformer, always_list=False): return nodes def __repr__(self): + if self.position is None: + return "".format(self.t) return "".format(self.t, *self.position) def pretty(self, indent=0, no_position=False): @@ -111,39 +112,77 @@ def i(n=0): return "".join(pp) def dump(self, f): - sexp.dump(f, self._ast_serialize()) + from . import sexp + sexp.dump(f, self._il_serialize()) def dumps(self): - return sexp.dumps(self._ast_serialize()) + from . import sexp + return sexp.dumps(self._il_serialize()) - def _ast_serialize(self): - return self.transform(AstSerializer()) + def _il_serialize(self): + nodes = self.transform(IlSerializer(), always_list=True) + assert len(nodes) == 1 + return nodes[0] -class AstSerializer: +class IlSerializer: transform_attrs = True def transform_enter(self, t, node): return node def transform_exit(self, t, node): - at = sexp.Atom(t) + from . import sexp + at = sexp.satom('@' + t) + if node.position is None: + pos = sexp.snil() + else: + pos = sexp.slist( + children=[sexp.snumeric(v) for v in node.position]) + attrs = [] for k, v in node.attrs.items(): - attrs.append([sexp.Atom(k), self._convert(v)]) - # this has to be a double-list to avoid it being exploded - return [[at, attrs, *node.children]] - - def _convert(self, value): + attrs.append(sexp.slist(children=[ + sexp.satom(k), + self._convert(v)])) + return sexp.slist(children=[ + at, + pos, + sexp.slist(children=attrs), + *node.children, + ]) + + @classmethod + def _convert(cls, value): + from . import sexp try: - return value._ast_serialize() + return value._il_serialize() except AttributeError: pass if isinstance(value, dict): - return [sexp.Atom('dict*'), - *([sexp.Atom('list*'), k, self._convert(v)] - for k, v in value.items())] + return sexp.slist(children=[ + sexp.satom('dict'), + *(sexp.slist(children=[ + sexp.satom('list'), + sexp.sstring(k), + cls._convert(v), + ]) for k, v in value.items()) + ]) elif isinstance(value, bytes): - return [sexp.Atom('bytes*'), *value] - return value + return sexp.slist(children=[ + sexp.satom('bytes'), + *(sexp.snumeric(v) for v in value) + ]) + elif value is None: + return sexp.snil() + elif isinstance(value, int): + return sexp.snumeric(value) + elif isinstance(value, bool): + return sexp.sboolean(value) + elif isinstance(value, str): + return sexp.sstring(value) + elif isinstance(value, list): + return sexp.slist(children=value) + else: + raise Exception(f"Don't know how to cope with {value}") diff --git a/jeff65/brundle/sexp.py b/jeff65/brundle/sexp.py index 4ea4ff6..ec2491a 100644 --- a/jeff65/brundle/sexp.py +++ b/jeff65/brundle/sexp.py @@ -18,10 +18,12 @@ import re from enum import Enum, auto import attr +from . import ast class T(Enum): EOF = auto() + SOF = auto() MYSTERY = auto() PAREN_OPEN = auto() PAREN_CLOSE = auto() @@ -34,27 +36,28 @@ class T(Enum): @attr.s(frozen=True, slots=True) class Token: t = attr.ib() - position = attr.ib() + position = attr.ib(cmp=False) text = attr.ib() + @classmethod + def is_t(cls, v, *ts): + return isinstance(v, cls) and v.t in ts -@attr.s(frozen=True, slots=True) -class Atom: - text = attr.ib() - -terminators = re.escape('()"') +terminators = re.escape('()[]"') str_delim = '"' m_str_escape = re.compile(r'\\.', re.M) m_str_delim = re.compile(re.escape(str_delim)) m_str_control = re.compile(fr'{m_str_escape.pattern}|{m_str_delim.pattern}') m_whitespace = re.compile(r'\s+', re.M) m_numeric = re.compile(fr'[+-]?\d[^\s{terminators}]*') -m_atom = re.compile(fr'[\w:][^\s{terminators}]*') +m_atom = re.compile(fr'[^\s{terminators}]+') m_bool = re.compile(r'#[tf]') singles = { '(': T.PAREN_OPEN, ')': T.PAREN_CLOSE, + '[': T.PAREN_OPEN, + ']': T.PAREN_CLOSE, } @@ -67,6 +70,8 @@ def lexer(stream, line=0, column=0): def make_token(t, text): return Token(t, (line, column), text) + # our parser needs a start-of-file token for anchoring. + yield make_token(T.SOF, '') while True: if current is None or column >= len(current): try: @@ -77,6 +82,7 @@ def make_token(t, text): # If we're in string mode, we are NOT expecting this kind of # behavior and will kick up a fuss. # TODO: check this + yield make_token(T.EOF, '') break # String collects a string until it's done, then emits a single STRING @@ -146,30 +152,103 @@ def make_token(t, text): # end while, in case you forgot +# character-to-predicate mappings to support matches local function of the +# parse function +_parser_predicates = { + '(': lambda v: v == Token(T.PAREN_OPEN, None, '('), + ')': lambda v: v == Token(T.PAREN_CLOSE, None, ')'), + '[': lambda v: v == Token(T.PAREN_OPEN, None, '['), + ']': lambda v: v == Token(T.PAREN_CLOSE, None, ']'), + 'a': lambda v: Token.is_t(v, T.ATOM), + 'n': lambda v: Token.is_t(v, T.NUMERIC), + '?': lambda v: Token.is_t(v, T.BOOLEAN), + 's': lambda v: Token.is_t(v, T.STRING), + 'u': lambda v: isinstance(v, ast.AstNode) and v.t == 'unit', + 'e': lambda v: isinstance(v, ast.AstNode) and v.t == 'list', + '$': lambda v: Token.is_t(v, T.EOF), + '^': lambda v: Token.is_t(v, T.SOF), + '<': lambda v: Token.is_t(v, T.PAREN_OPEN), +} + + +def sunit(position=None, children=None): + return ast.AstNode('unit', position, children=children or []) + + +def slist(position=None, children=None): + return ast.AstNode('list', position, children=children or []) + + +def snil(position=None): + return ast.AstNode('nil', position) + + +def satom(name, position=None): + return ast.AstNode('atom', position, attrs={'name': name}) + + +def snumeric(value, position=None): + return ast.AstNode('numeric', position, attrs={'value': value}) + + +def sboolean(value, position=None): + return ast.AstNode('boolean', position, attrs={'value': value}) + + +def sstring(value, position=None): + return ast.AstNode('string', position, attrs={'value': value}) + + def parse(tokens): - data = [[]] - for tok in tokens: - if tok.t == T.PAREN_OPEN: - data.append([]) - elif tok.t == T.PAREN_CLOSE: - lst = data.pop() - data[-1].append(lst) - elif tok.t == T.NUMERIC: - data[-1].append(int(tok.text)) - elif tok.t == T.STRING: - data[-1].append(tok.text) - elif tok.t == T.BOOLEAN: - data[-1].append(tok.text == '#t') - elif tok.t == T.ATOM: - if tok.text == 'nil': - data[-1].append(None) + """Parses a stream of tokens """ + stack = [] + + def matches(pattern): + if len(stack) < len(pattern): + return False + ps = (_parser_predicates[c] for c in reversed(pattern)) + return all(p(v) for p, v in zip(ps, reversed(stack))) + + def unshift(count): + vals = stack[-count:] + del stack[-count:] + return vals + + def lift(v): + stack[-1].children.append(v) + + while True: + stack.append(next(tokens)) + if matches('^'): + stack.append(sunit(stack[-1].position)) + elif matches('<'): + stack.append(slist(stack[-1].position)) + elif matches('a'): + a, = unshift(1) + if a.text == 'nil': + lift(snil(a.position)) else: - data[-1].append(Atom(tok.text)) + lift(satom(a.text, a.position)) + elif matches('n'): + n, = unshift(1) + lift(snumeric(int(n.text), n.position)) + elif matches('?'): + b, = unshift(1) + lift(sboolean(b.text == '#t', b.position)) + elif matches('s'): + s, = unshift(1) + lift(sstring(s.text, s.position)) + elif matches('(e)') or matches('[e]'): + _, e, _ = unshift(3) + lift(e) + elif matches('^u$'): + break else: + tok = stack[-1] raise Exception(f"Unexpected '{tok.text}' at {tok.position}") - assert len(data) == 1 - assert len(data[0]) == 1 - return data[0][0] + + assert len(stack) == 3 + return stack[1] def load(stream): @@ -181,43 +260,48 @@ def loads(s): return load(f) -def dump(f, data, indent=0): +def dump(f, node, indent=0): it = ' '*indent - if data is None: + if node.t == 'nil': f.write('nil') - elif isinstance(data, bool): - f.write('#t' if data else '#f') - elif isinstance(data, int): - f.write('{}'.format(repr(data))) - elif isinstance(data, str): - f.write('"{}"'.format(data.replace('"', r'\"'))) - elif isinstance(data, Atom): - f.write('{}'.format(data.text)) - elif isinstance(data, list): - if len(data) == 0: + elif node.t == 'boolean': + f.write('#t' if node.attrs['value'] else '#f') + elif node.t == 'numeric': + f.write(repr(node.attrs['value'])) + elif node.t == 'string': + f.write('"{}"'.format(node.attrs['value'].replace('"', r'\"'))) + elif node.t == 'atom': + f.write(node.attrs['name']) + elif node.t == 'list': + if len(node.children) == 0: f.write('()') - elif len(data) == 1: + elif len(node.children) == 1: f.write('(') - dump(f, data[0], indent+1) + dump(f, node.children[0], indent+1) f.write(')') - elif not any(isinstance(e, list) and len(e) > 0 for e in data): + elif not any(n.t == 'list' and len(n.children) > 0 + for n in node.children): f.write('('.format(it)) - for e in data[:-1]: - dump(f, e, indent) + for n in node.children[:-1]: + dump(f, n, indent) f.write(' ') - dump(f, data[-1], indent) + dump(f, node.children[-1], indent) f.write(')') else: f.write('('.format(it)) - dump(f, data[0], 0) + dump(f, node.children[0], 0) f.write('\n{} '.format(it)) - for e in data[1:-1]: - dump(f, e, indent+1) + for n in node.children[1:-1]: + dump(f, n, indent+1) f.write('\n{} '.format(it)) - dump(f, data[-1], indent+1) + dump(f, node.children[-1], indent+1) f.write(')'.format(it)) + elif node.t == 'unit': + for n in node.children: + dump(f, n, indent) + f.write('\n{}'.format(it)) else: - raise Exception(f"don't know what to do with '{data}'") + raise Exception(f"don't know what to do with '{node}'") def dumps(data): diff --git a/jeff65/gold/compiler.py b/jeff65/gold/compiler.py index 095fd26..9cc80fa 100644 --- a/jeff65/gold/compiler.py +++ b/jeff65/gold/compiler.py @@ -66,6 +66,8 @@ def translate(unit, verbose=False): obj = obj.transform(p()) if (verbose): print(p.__name__) + # print(obj.pretty()) + # print(obj._il_serialize().pretty(no_position=True)) print(obj.dumps()) archive = blum.Archive() diff --git a/jeff65/gold/mem.py b/jeff65/gold/mem.py index ee3b099..23862ad 100644 --- a/jeff65/gold/mem.py +++ b/jeff65/gold/mem.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ..brundle.sexp import Atom +from ..brundle import sexp from ..blum import types from . import units @@ -54,5 +54,5 @@ def _as_address(self, arg): def __repr__(self): return "MemUnit()" - def _ast_serialize(self): - return [Atom('unit*'), 'mem'] + def _il_serialize(self): + return sexp.slist(children=[sexp.satom('unit'), sexp.sstring('mem')]) diff --git a/jeff65/gold/units.py b/jeff65/gold/units.py index 18a7598..bf17c3a 100644 --- a/jeff65/gold/units.py +++ b/jeff65/gold/units.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ..brundle.sexp import Atom +from ..brundle import sexp class ExternalUnit: @@ -44,8 +44,11 @@ def __repr__(self): return "<{} {}.{}: {}>".format( type(self).__name__, self.unit, self.name, self.type) - def _ast_serialize(self): - return [Atom('get-member*'), self.unit._ast_serialize()] + def _il_serialize(self): + return sexp.slist(children=[ + sexp.satom('get-member'), + self.unit._il_serialize(), + sexp.sstring(self.name)]) class IntrinsicSymbol(UnitSymbol): diff --git a/tests/test_sexp.py b/tests/test_sexp.py index b69df21..d02ec89 100644 --- a/tests/test_sexp.py +++ b/tests/test_sexp.py @@ -3,52 +3,135 @@ import hypothesis.strategies as st from nose.tools import ( assert_equal) -from jeff65.brundle.sexp import dumps, loads, Atom +from jeff65.brundle.ast import AstNode +from jeff65.brundle.sexp import dumps, loads def test_parse_empty(): - assert_equal([], loads('()')) + assert_equal(AstNode('unit'), loads('')) def test_parse_nil(): - assert_equal(None, loads('nil')) + assert_equal( + AstNode('unit', children=[ + AstNode('nil') + ]), + loads('nil')) def test_parse_bool(): - assert_equal(True, loads('#t')) - assert_equal(False, loads('#f')) + assert_equal( + AstNode('unit', children=[ + AstNode('boolean', attrs={'value': True}) + ]), + loads('#t')) + assert_equal( + AstNode('unit', children=[ + AstNode('boolean', attrs={'value': False}) + ]), + loads('#f')) def test_parse_atom(): - assert_equal(Atom('spam'), loads('spam')) + assert_equal( + AstNode('unit', children=[ + AstNode('atom', attrs={'name': 'spam'}) + ]), + loads('spam')) def test_parse_string(): - assert_equal("spam", loads('"spam"')) + assert_equal( + AstNode('unit', children=[ + AstNode('string', attrs={'value': 'spam'}) + ]), + loads('"spam"')) def test_parse_numeric(): - assert_equal(42, loads('42')) + assert_equal( + AstNode('unit', children=[ + AstNode('numeric', attrs={'value': 42}) + ]), + loads('42')) def test_parse_nested(): - assert_equal([ - Atom('let'), [ - [Atom('spam'), 42], - [Atom('eggs'), "beans"], - ], - [Atom('foo'), Atom('bar')], - ], loads(''' - (let ((spam 42) - (eggs "beans")) - (foo bar)) - ''')) - - -@given(d=st.recursive( - st.none() | st.booleans() | st.integers() | st.text() | - st.builds(Atom, st.text( - min_size=1, alphabet=string.ascii_letters)), - lambda children: st.lists(children))) + assert_equal( + AstNode('unit', children=[ + AstNode('list', children=[ + AstNode('atom', attrs={'name': 'let'}), + AstNode('list', children=[ + AstNode('list', children=[ + AstNode('atom', attrs={'name': 'spam'}), + AstNode('numeric', attrs={'value': 42}), + ]), + AstNode('list', children=[ + AstNode('atom', attrs={'name': 'eggs'}), + AstNode('string', attrs={'value': "beans"}), + ]) + ]), + AstNode('list', children=[ + AstNode('atom', attrs={'name': 'foo'}), + AstNode('atom', attrs={'name': 'bar'}), + ]) + ]) + ]), + loads(''' + (let [(spam 42) + (eggs "beans")] + (foo bar)) + ''')) + + +@st.composite +def sbooleans(draw): + return AstNode('boolean', attrs={'value': draw(st.booleans())}) + + +@st.composite +def snumerics(draw): + return AstNode('numeric', attrs={'value': draw(st.integers())}) + + +@st.composite +def sstrings(draw): + return AstNode('string', attrs={'value': draw(st.text())}) + + +@st.composite +def satoms(draw): + return AstNode('atom', attrs={'name': draw(st.text( + min_size=1, alphabet=string.ascii_letters))}) + + +@st.composite +def slists(draw): + return draw(st.recursive( + st.just(AstNode('nil')) + | sbooleans() + | snumerics() + | sstrings() + | satoms(), + lambda children: st.builds( + AstNode, st.just('list'), children=st.lists(children)))) + + +@st.composite +def sunits(draw): + return AstNode('unit', children=draw( + st.lists( + st.just(AstNode('nil')) + | sbooleans() + | snumerics() + | sstrings() + | satoms() + | slists()))) + + +@given(d=sunits()) def test_roundtrip(d): - assert_equal(d, loads(dumps(d))) + print(d.pretty(no_position=True)) + s = dumps(d) + print(s) + assert_equal(d, loads(s))