diff --git a/comb_parse.py b/comb_parse.py index ce0d7b4..b5b1923 100644 --- a/comb_parse.py +++ b/comb_parse.py @@ -6,7 +6,6 @@ from functools import reduce from re import compile, Pattern from lex import Lexeme, tokenize -from parse import Action from typing import Any, Callable, Collection, Mapping, Sequence, Tuple, TypeAlias diff --git a/genir.py b/genir.py new file mode 100644 index 0000000..9cc1a8f --- /dev/null +++ b/genir.py @@ -0,0 +1,24 @@ +from emis_funky_funktions import * +from typing import * + +from silly_thing import * +from pattern import lex_and_parse_pattern +from ir import Function, Application, Int, Variable + +import json + +JsonType: TypeAlias = 'Mapping[str, JsonType] | Sequence[JsonType] | int | str' + +def json_to_ir(j: JsonType) -> Expression: + if isinstance(j, Mapping): + return Function(tuple( + #TODO handle parse errors + (unwrap_r(lex_and_parse_pattern(k)), json_to_ir(v)) + for (k, v) in j.items() + )) + elif isinstance(j, str): + return Variable(j) + elif isinstance(j, Sequence): + return Application([json_to_ir(e) for e in j]) + else: + return Int(j) \ No newline at end of file diff --git a/ir.py b/ir.py new file mode 100644 index 0000000..c7f8d42 --- /dev/null +++ b/ir.py @@ -0,0 +1,281 @@ +from emis_funky_funktions import * + +from typing import Mapping, Sequence, Tuple, TypeAlias + + +Expression: TypeAlias = 'Function | Application | Int | Variable | Builtin' +Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern | IgnorePattern' + +@dataclass(frozen=True) +class NamePattern: + """ + A pattern which always succeeds to match, and binds a whole expression to a name + """ + name: str + + def binds(self, var: str) -> bool: + """ + Test to see if this pattern binds a given variable + """ + return var == self.name + + def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: + """ + Match an expression against this pattern + + >>> NamePattern('my_var').match(Int(1)) + Some((('my_var', 1),)) + """ + return Some(((self.name, e),)) + + def __repr__(self) -> str: + return self.name + +@dataclass(frozen=True) +class IgnorePattern: + """ + A pattern which always succeeds to match, but binds nothing + """ + + def binds(self, var: str) -> bool: + """ + Test to see if this pattern binds a given variable + + For an `IgnorePattern` this is always false + """ + return False + + def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: + """ + Match an expression against this pattern + + >>> IgnorePattern().match(Int(1)) + Some(()) + """ + return Some(tuple()) + + def __repr__(self) -> str: + return '_' + +@dataclass(frozen=True) +class IntPattern: + value: int + + def binds(self, var: str) -> bool: + """ + Test to see if this pattern binds a given variable + + For an `IntPattern` this is always false + """ + return False + + def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: + """ + Match an expression against this pattern + + >>> IntPattern(2).match(Int(1)) is None + True + + >>> IntPattern(1).match(Int(1)) + Some(()) + """ + match e: + case Int(v) if v == self.value: + return Some(tuple()) + return None + + def __repr__(self) -> str: + return repr(self.value) + +@dataclass(frozen=True) +class SPattern: + pred: Pattern + + def binds(self, var: str) -> bool: + """ + Test to see if this pattern binds a given variable + """ + return self.pred.binds(var) + + def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: + """ + Match an expression against this pattern + + >>> SPattern(NamePattern('n')).match(Int(1)) + Some((('n', 0),)) + + >>> SPattern(NamePattern('n')).match(Int(0)) is None + True + + >>> SPattern(SPattern(NamePattern('n'))).match(Int(4)) + Some((('n', 2),)) + """ + match e: + case Int(v) if v > 0: + return self.pred.match(Int(v - 1)) + return None + + def __repr__(self) -> str: + return 'S ' + repr(self.pred) + +@dataclass(frozen=True) +class Builtin: + name: str + f: Callable[[Expression], Option[Expression]] + + def subst(self, expression: Expression, variable: str) -> Expression: + return self + + def is_value(self) -> bool: + return True + + def step(self) -> Option[Expression]: + return None + + def try_apply(self, v: Expression) -> Option[Expression]: + return self.f(v) + + def __repr__(self) -> str: + return "'" + repr(self.name)[1:-1] + "'" + + @cur2 + @staticmethod + def _PLUS_CONST(i: int, e: Expression) -> Option[Expression]: + match e: + case Int(v): + return Some(Int(i + v)) + return None + + @staticmethod + def _PLUS(e: Expression) -> Option[Expression]: + match e: + case Int(v): + return Some(Builtin(f'+{v}', Builtin._PLUS_CONST(v))) + return None + + @staticmethod + def PLUS() -> 'Builtin': + return Builtin('+', Builtin._PLUS) + + @staticmethod + def S() -> 'Builtin': + return Builtin('S', Builtin._PLUS_CONST(1)) + +@dataclass(frozen=True) +class Function: + forms: Sequence[Tuple[Pattern, Expression]] + + def subst(self, expression: Expression, variable: str) -> Expression: + return Function([ + (p, e if p.binds(variable) else e.subst(expression, variable)) + for (p, e) in self.forms + ]) + + def is_value(self) -> bool: + return True + + def step(self) -> Option[Expression]: + return None + + def try_apply(self, v: Expression) -> Option[Expression]: + match tuple((bindings.val, body) for (pattern, body) in self.forms for bindings in (pattern.match(v),) if bindings is not None): + case []: + return None + case [(bindings, body), *rest]: + return Some(subst_all(bindings, body.subst(self, 'recur'))) + raise Exception('Unreachable') + + def __repr__(self) -> str: + return '{ ' + ', '.join('"' + repr(repr(p))[1:-1] + '" : ' + repr(e) for (p, e) in self.forms) + ' }' + +@dataclass +class Application: + expressions: Sequence[Expression] + + def subst(self, expression: Expression, variable: str) -> Expression: + return Application([ + e.subst(expression, variable) + for e in self.expressions + ]) + + def is_value(self) -> bool: + return not len(self.expressions) + + def step(self) -> Option[Expression]: + match self.expressions: + case []: + return None + case [e]: + return Some(e) + case [f, a, *rest]: + if f.is_value(): + if a.is_value(): + if isinstance(f, Function) or isinstance(f, Builtin): + return map_opt( + lambda maybe_f_sub: Application([maybe_f_sub, *rest]), + f.try_apply(a) + ) + else: + return None + else: + return map_opt( + lambda next_a: Application([f, next_a, *rest]), + a.step() + ) + else: + return map_opt( + lambda next_f: Application([next_f, a, *rest]), + f.step() + ) + raise Exception('Unreachable') + + def __repr__(self) -> str: + return '[ ' + ', '.join(repr(e) for e in self.expressions) + ' ]' + +@dataclass +class Int: + value: int + + def subst(self, expression: Expression, variable: str) -> Expression: + return self + + def is_value(self) -> bool: + return True + + def step(self) -> Option[Expression]: + return None + + def __repr__(self) -> str: + return str(self.value) + +@dataclass +class Variable: + name: str + + def subst(self, expression: Expression, variable: str) -> Expression: + if variable == self.name: + return expression + else: + return self + + def is_value(self) -> bool: + return False + + def step(self) -> Option[Expression]: + match self.name: + case '+': + return Some(Builtin.PLUS()) + case 'S': + return Some(Builtin.S()) + return None + + def __repr__(self) -> str: + return '"' + repr(self.name)[1:-1] + '"' + +def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression: + match bindings: + case []: + return body + case [(var, replacement), *rest]: + return subst_all(rest, body.subst(replacement, var)) + raise Exception('Unreachable') \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..29c011f --- /dev/null +++ b/main.py @@ -0,0 +1,8 @@ +from emis_funky_funktions import * + +from genir import json_to_ir +from silly_thing import evaluate + +import json, sys + +print(evaluate(json_to_ir(json.loads(open(sys.argv[1]).read())))) \ No newline at end of file diff --git a/pattern.py b/pattern.py new file mode 100644 index 0000000..54f6b9b --- /dev/null +++ b/pattern.py @@ -0,0 +1,56 @@ +from emis_funky_funktions import * +from typing import Collection, Mapping, Sequence, Tuple, TypeAlias + +from comb_parse import Parser +from ir import Pattern, NamePattern, IgnorePattern, IntPattern, SPattern +from lex import Lexeme, tokenize + +from enum import auto, IntEnum +import re + +class PatTok(IntEnum): + """ + All possible tokens used in the grammar + """ + Whitespace = auto() + Number = auto() + Succ = auto() + Underscore = auto() + Name = auto() + Eof = auto() + + def __repr__(self): + return self._name_ + +PATTERN_LEX_TABLE: Collection[Tuple[re.Pattern[str], PatTok]] = [ + (re.compile(r"\s+"), PatTok.Whitespace), + (re.compile(r"\d+"), PatTok.Number), + (re.compile(r"S"), PatTok.Succ), + (re.compile(r"_"), PatTok.Underscore), + (re.compile(r"\w+"), PatTok.Name), +] + +# P := int +# P := name +# P := underscore +# P := S <P> + +parse_int: Parser[Pattern, PatTok] = Parser.token(PatTok.Number).map(Lexeme.get_match).map(int).map(IntPattern) +parse_name: Parser[Pattern, PatTok] = Parser.token(PatTok.Name).map(Lexeme.get_match).map(p(NamePattern)) +parse_ignore: Parser[Pattern, PatTok] = Parser.token(PatTok.Underscore).map(lambda _: IgnorePattern()) +parse_succ: Parser[Pattern, PatTok] = Parser.token(PatTok.Succ).map(k(SPattern)).fapply(Parser.lazy(lambda: parse_P)) #type: ignore +parse_P: Parser[Pattern, PatTok] = parse_int.or_(parse_name, parse_ignore, parse_succ) + +parse_pattern = parse_P.seq_ignore_tok(PatTok.Eof) + +def lex_and_parse_pattern(input: str) -> Result[Pattern, str | Mapping[Lexeme[PatTok], Collection[PatTok]]]: + match tokenize(PATTERN_LEX_TABLE, [PatTok.Whitespace], PatTok.Eof, input): + case Ok(lexemes): + match parse_pattern.parse_(lexemes): + case Ok(pattern): # Imagine having a good type system + return Ok(pattern) + case Err(e): + return Err(e) + case Err(remainder): + return Err(remainder) + raise Exception('Unreachable') diff --git a/silly_thing.py b/silly_thing.py index 4d85007..fa104aa 100644 --- a/silly_thing.py +++ b/silly_thing.py @@ -1,290 +1,11 @@ from emis_funky_funktions import * from typing import Collection, Sequence, TypeAlias +from ir import Expression + from dataclasses import dataclass from operator import add -Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern' -Expression: TypeAlias = 'Function | Application | Int | Variable | Builtin' - -@dataclass(frozen=True) -class NamePattern: - """ - A pattern which always succeeds to match, and binds a whole expression to a name - """ - name: str - - def binds(self, var: str) -> bool: - """ - Test to see if this pattern binds a given variable - """ - return var == self.name - - def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: - """ - Match an expression against this pattern - - >>> NamePattern('my_var').match(Int(1)) - Some((('my_var', 1),)) - """ - return Some(((self.name, e),)) - - def __repr__(self) -> str: - return self.name - -@dataclass(frozen=True) -class IgnorePattern: - """ - A pattern which always succeeds to match, but binds nothing - """ - - def binds(self, var: str) -> bool: - """ - Test to see if this pattern binds a given variable - - For an `IgnorePattern` this is always false - """ - return False - - def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: - """ - Match an expression against this pattern - - >>> IgnorePattern().match(Int(1)) - Some(()) - """ - return Some(tuple()) - - def __repr__(self) -> str: - return '_' - -@dataclass(frozen=True) -class IntPattern: - value: int - - def binds(self, var: str) -> bool: - """ - Test to see if this pattern binds a given variable - - For an `IntPattern` this is always false - """ - return False - - def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: - """ - Match an expression against this pattern - - >>> IntPattern(2).match(Int(1)) is None - True - - >>> IntPattern(1).match(Int(1)) - Some(()) - """ - match e: - case Int(v) if v == self.value: - return Some(tuple()) - return None - - def __repr__(self) -> str: - return repr(self.value) - -@dataclass(frozen=True) -class SPattern: - pred: Pattern - - def binds(self, var: str) -> bool: - """ - Test to see if this pattern binds a given variable - """ - return self.pred.binds(var) - - def match(self, e: Expression) -> Option[Sequence[Tuple[str, Expression]]]: - """ - Match an expression against this pattern - - >>> SPattern(NamePattern('n')).match(Int(1)) - Some((('n', 0),)) - - >>> SPattern(NamePattern('n')).match(Int(0)) is None - True - - >>> SPattern(SPattern(NamePattern('n'))).match(Int(4)) - Some((('n', 2),)) - """ - match e: - case Int(v) if v > 0: - return self.pred.match(Int(v - 1)) - return None - - def __repr__(self) -> str: - return 'S ' + repr(self.pred) - -@dataclass(frozen=True) -class Builtin: - name: str - f: Callable[[Expression], Option[Expression]] - - def subst(self, expression: Expression, variable: str) -> Expression: - return self - - def is_value(self) -> bool: - return True - - def step(self) -> Option[Expression]: - return None - - def try_apply(self, v: Expression) -> Option[Expression]: - return self.f(v) - - def __repr__(self) -> str: - return '"' + repr(self.name)[1:-1] + '"' - - @cur2 - @staticmethod - def _PLUS_CONST(i: int, e: Expression) -> Option[Expression]: - match e: - case Int(v): - return Some(Int(i + v)) - return None - - @staticmethod - def _PLUS(e: Expression) -> Option[Expression]: - match e: - case Int(v): - return Some(Builtin(f'+{v}', Builtin._PLUS_CONST(v))) - return None - - @staticmethod - def PLUS() -> 'Builtin': - return Builtin('+', Builtin._PLUS) - - @staticmethod - def S() -> 'Builtin': - return Builtin('S', Builtin._PLUS_CONST(1)) - -@dataclass(frozen=True) -class Function: - forms: Sequence[Tuple[Pattern, Expression]] - - def subst(self, expression: Expression, variable: str) -> Expression: - return Function([ - (p, e if p.binds(variable) else e.subst(expression, variable)) - for (p, e) in self.forms - ]) - - def is_value(self) -> bool: - return True - - def step(self) -> Option[Expression]: - return None - - def try_apply(self, v: Expression) -> Option[Expression]: - match self.forms: - case []: - return None - case [(pattern, body), *rest]: - match pattern.match(v): - case Some(bindings): - return Some(subst_all(bindings, body.subst(self, 'recur'))) - case None: - return Function(rest).try_apply(v) - raise Exception('Unreachable') - - def __repr__(self) -> str: - return '{ ' + ', '.join('"' + repr(repr(p))[1:-1] + '" : ' + repr(e) for (p, e) in self.forms) + ' }' - -@dataclass -class Application: - expressions: Sequence[Expression] - - def subst(self, expression: Expression, variable: str) -> Expression: - return Application([ - e.subst(expression, variable) - for e in self.expressions - ]) - - def is_value(self) -> bool: - return not len(self.expressions) - - def step(self) -> Option[Expression]: - match self.expressions: - case []: - return None - case [e]: - return Some(e) - case [f, a, *rest]: - if f.is_value(): - if a.is_value(): - if isinstance(f, Function) or isinstance(f, Builtin): - return map_opt( - lambda maybe_f_sub: Application([maybe_f_sub, *rest]), - f.try_apply(a) - ) - else: - return None - else: - return map_opt( - lambda next_a: Application([f, next_a, *rest]), - a.step() - ) - else: - return map_opt( - lambda next_f: Application([next_f, a, *rest]), - f.step() - ) - raise Exception('Unreachable') - - def __repr__(self) -> str: - return '[ ' + ', '.join(repr(e) for e in self.expressions) + ' ]' - -@dataclass -class Int: - value: int - - def subst(self, expression: Expression, variable: str) -> Expression: - return self - - def is_value(self) -> bool: - return True - - def step(self) -> Option[Expression]: - return None - - def __repr__(self) -> str: - return str(self.value) - -@dataclass -class Variable: - name: str - - def subst(self, expression: Expression, variable: str) -> Expression: - if variable == self.name: - return expression - else: - return self - - def is_value(self) -> bool: - return False - - def step(self) -> Option[Expression]: - match self.name: - case '+': - return Some(Builtin.PLUS()) - case 'S': - return Some(Builtin.S()) - return None - - def __repr__(self) -> str: - return '"' + repr(self.name)[1:-1] + '"' - -def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression: - match bindings: - case []: - return body - case [(var, replacement), *rest]: - return subst_all(rest, body.subst(replacement, var)) - raise Exception('Unreachable') - def evaluate(expr: Expression) -> Expression: """ >>> funktion = Function((