Added docparse
This commit is contained in:
parent
6a27431f81
commit
5fdc971f36
179
example/schcls/schcls.py
Normal file
179
example/schcls/schcls.py
Normal file
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
25
sly/ast.py
Normal file
25
sly/ast.py
Normal file
@ -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__
|
||||||
|
|
60
sly/docparse.py
Normal file
60
sly/docparse.py
Normal file
@ -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')
|
Loading…
Reference in New Issue
Block a user