diff --git a/example/schcls/schcls.py b/example/schcls/schcls.py new file mode 100644 index 0000000..b763057 --- /dev/null +++ b/example/schcls/schcls.py @@ -0,0 +1,179 @@ +# schcls.py +# +# Proof of concept--not complete + +from sly.docparse import DocParseMeta +from sly import Lexer, Parser + +class SchLexer(Lexer): + tokens = { NUMBER, NAME, DEFINE, SET } + ignore = ' \t' + literals = ['=','+','-','*','/','(',')','.'] + + NAME = '[a-zA-Z_!][a-zA-Z0-9_!]*' + NAME['define'] = DEFINE + NAME['set!'] = SET + + @_(r'\d+') + def NUMBER(self, t): + t.value = int(t.value) + return t + + @_(r'\n+') + def newline(self, t): + self.lineno = t.lineno + t.value.count('\n') + + def error(self, t): + print(f"{self.cls_module}.{self.cls_name}:{self.lineno}: * Illegal character", repr(self.text[self.index])) + self.index += 1 + +class SchParser(Parser): + tokens = SchLexer.tokens + precedence = ( + ('left', '+','-'), + ('left', '*','/') + ) + def __init__(self): + self.env = { } + + @_('declarations', + '') + def program(self, p): + return self.env + + @_('declarations declaration') + def declarations(self, p): + pass + + @_('declaration') + def declarations(self, p): + pass + + @_("'(' DEFINE NAME expression ')'") + def declaration(self, p): + self.env[p.NAME] = p.expression + + @_("'(' DEFINE '(' NAME arglist ')' exprlist ')'") + def declaration(self, p): + args = ','.join(p.arglist) + self.env[p.NAME] = eval(f"lambda {args}: ({','.join(p.exprlist)},)[-1]") + + @_("'(' SET NAME '.' NAME expression ')'") + def expression(self, p): + return f'setattr({p.NAME0}, {p.NAME1!r}, {p.expression})' + + @_("") + def arglist(self, p): + return [] + + @_("arglist_nonempty") + def arglist(self, p): + return p.arglist_nonempty + + @_("arglist_nonempty NAME") + def arglist_nonempty(self, p): + p.arglist_nonempty.append(p.NAME) + return p.arglist_nonempty + + @_("NAME") + def arglist_nonempty(self, p): + return [ p.NAME ] + + @_("NUMBER") + def expression(self, p): + return str(p.NUMBER) + + @_("name") + def expression(self, p): + return p.name + + @_("'(' operator exprlist ')'") + def expression(self, p): + return '(' + p.operator.join(p.exprlist) + ')' + + @_("'+'", "'-'", "'*'", "'/'") + def operator(self, p): + return p[0] + + @_("'(' name exprlist ')'") + def expression(self, p): + return p.name + '(' + ','.join(p.exprlist) + ')' + + @_("'(' name ')'") + def expression(self, p): + return p.name + '()' + + @_('exprlist expression') + def exprlist(self, p): + p.exprlist.append(p.expression) + return p.exprlist + + @_('expression') + def exprlist(self, p): + return [ p.expression ] + + @_("NAME '.' NAME") + def name(self, p): + return f'{p.NAME0}.{p.NAME1}' + + @_("NAME") + def name(self, p): + return p.NAME + + def error(self, p): + print(f'{self.cls_module}.{self.cls_name}:{getattr(p,"lineno","")}: ' + f'Syntax error at {getattr(p,"value","EOC")}') + +class SchMeta(DocParseMeta): + lexer = SchLexer + parser = SchParser + +class Sch(metaclass=SchMeta): + pass + +class Rat(Sch): + ''' + (define (__init__ self numer denom) + (set! self.numer numer) + (set! self.denom denom) + ) + (define (__add__ self other) + (Rat (+ (* self.numer other.denom) + (* self.denom other.numer)) + (* self.denom other.denom) + ) + ) + (define (__sub__ self other) + (Rat (- (* self.numer other.denom) + (* self.denom other.numer)) + (* self.denom other.denom) + ) + ) + (define (__mul__ self other) + (Rat (* self.numer other.numer) + (* self.denom other.denom) + ) + ) + (define (__truediv__ self other) + (Rat (* self.numer other.denom) + (* self.denom other.numer) + ) + ) + ''' + def __repr__(self): + return f'Rat({self.numer}, {self.denom})' + +if __name__ == '__main__': + a = Rat(2, 3) + b = Rat(1, 4) + print(a + b) + print(a - b) + print(a * b) + print(a / b) + + + + + + + diff --git a/sly/ast.py b/sly/ast.py new file mode 100644 index 0000000..7b79ac5 --- /dev/null +++ b/sly/ast.py @@ -0,0 +1,25 @@ +# sly/ast.py +import sys + +class AST(object): + + @classmethod + def __init_subclass__(cls, **kwargs): + mod = sys.modules[cls.__module__] + if not hasattr(cls, '__annotations__'): + return + + hints = list(cls.__annotations__.items()) + + def __init__(self, *args, **kwargs): + if len(hints) != len(args): + raise TypeError(f'Expected {len(hints)} arguments') + for arg, (name, val) in zip(args, hints): + if isinstance(val, str): + val = getattr(mod, val) + if not isinstance(arg, val): + raise TypeError(f'{name} argument must be {val}') + setattr(self, name, arg) + + cls.__init__ = __init__ + diff --git a/sly/docparse.py b/sly/docparse.py new file mode 100644 index 0000000..d5a83ce --- /dev/null +++ b/sly/docparse.py @@ -0,0 +1,60 @@ +# docparse.py +# +# Support doc-string parsing classes + +__all__ = [ 'DocParseMeta' ] + +class DocParseMeta(type): + ''' + Metaclass that processes the class docstring through a parser and + incorporates the result into the resulting class definition. This + allows Python classes to be defined with alternative syntax. + To use this class, you first need to define a lexer and parser: + + from sly import Lexer, Parser + class MyLexer(Lexer): + ... + + class MyParser(Parser): + ... + + You then need to define a metaclass that inherits from DocParseMeta. + This class must specify the associated lexer and parser classes. + For example: + + class MyDocParseMeta(DocParseMeta): + lexer = MyLexer + parser = MyParser + + This metaclass is then used as a base for processing user-defined + classes: + + class Base(metaclass=MyDocParseMeta): + pass + + class Spam(Base): + """ + doc string is parsed + ... + """ + + It is expected that the MyParser() class would return a dictionary. + This dictionary is used to create the final class Spam in this example. + ''' + + @staticmethod + def __new__(meta, clsname, bases, clsdict): + if '__doc__' in clsdict: + lexer = meta.lexer() + parser = meta.parser() + lexer.cls_name = parser.cls_name = clsname + lexer.cls_qualname = parser.cls_qualname = clsdict['__qualname__'] + lexer.cls_module = parser.cls_module = clsdict['__module__'] + parsedict = parser.parse(lexer.tokenize(clsdict['__doc__'])) + assert isinstance(parsedict, dict), 'Parser must return a dictionary' + clsdict.update(parsedict) + return super().__new__(meta, clsname, bases, clsdict) + + @classmethod + def __init_subclass__(cls): + assert hasattr(cls, 'parser') and hasattr(cls, 'lexer')