Add in IR generation

This commit is contained in:
Emi Simpson 2023-03-08 08:35:21 -05:00
parent 92d458de85
commit c27852daba
Signed by: Emi
GPG Key ID: A12F2C2FFDC3D847
6 changed files with 371 additions and 282 deletions

View File

@ -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

24
genir.py Normal file
View File

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

281
ir.py Normal file
View File

@ -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')

8
main.py Normal file
View File

@ -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()))))

56
pattern.py Normal file
View File

@ -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')

View File

@ -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((