# 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)