Compare commits
No commits in common. "2238d363e51b3814e6595ea1d9743de94dabb9b5" and "88399fff69ff340de0429dd94023543b3a78a989" have entirely different histories.
2238d363e5
...
88399fff69
|
@ -23,13 +23,6 @@ OR
|
||||||
python3 main.py example.json # Evaluate some code and either print the results or open a REPL
|
python3 main.py example.json # Evaluate some code and either print the results or open a REPL
|
||||||
```
|
```
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 compile.py fibb.json # Compile some code to JavaScript
|
|
||||||
python3 compile.py fibb.json | npx uglify-js -c evaluate -m # Compile some code to JavaScript and tidy it up a bit
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Syntax Rundown
|
## Syntax Rundown
|
||||||
|
|
||||||
|
|
|
@ -829,7 +829,7 @@ def sequence(s: Sequence[Result[A, B]]) -> Result[Sequence[A], B]:
|
||||||
Err('Oops!')
|
Err('Oops!')
|
||||||
"""
|
"""
|
||||||
if all(s):
|
if all(s):
|
||||||
return Ok(tuple(map(unwrap_r, s)))
|
return Ok(list(map(unwrap_r, s)))
|
||||||
else:
|
else:
|
||||||
o = next(filter(not_, s))
|
o = next(filter(not_, s))
|
||||||
assert isinstance(o, Err)
|
assert isinstance(o, Err)
|
||||||
|
|
23
fibb.json
23
fibb.json
|
@ -1,14 +1,13 @@
|
||||||
[
|
[
|
||||||
"fibb",
|
"fibb_helper",
|
||||||
[
|
{
|
||||||
"fibb_helper",
|
"a": {"b": {
|
||||||
{
|
"0": "a",
|
||||||
"a": {"b": {
|
"1": "b",
|
||||||
"0": "a",
|
"S S n": ["fibb_helper", ["+", "a", "b"], ["+", ["+", "a", "b"], "b"], "n"]
|
||||||
"1": "b",
|
}}
|
||||||
"S S n": ["fibb_helper", ["+", "a", "b"], ["+", ["+", "a", "b"], "b"], "n"]
|
},
|
||||||
}}
|
"fibb_helper",
|
||||||
},
|
0,
|
||||||
["fibb_helper", 0, 1]
|
1
|
||||||
]
|
|
||||||
]
|
]
|
45
ir.py
45
ir.py
|
@ -2,8 +2,7 @@ from emis_funky_funktions import *
|
||||||
|
|
||||||
from typing import Collection, Mapping, Sequence, Tuple, TypeAlias
|
from typing import Collection, Mapping, Sequence, Tuple, TypeAlias
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from match_tree import MatchTree, MatchException, StructurePath, LeafNode, merge_all_trees, IntNode, EMPTY_STRUCT_PATH, FAIL_NODE
|
from match_tree import MatchException, StructurePath, LeafNode, merge_all_trees, IntNode
|
||||||
from patterns import Pattern
|
|
||||||
|
|
||||||
import types_
|
import types_
|
||||||
|
|
||||||
|
@ -33,9 +32,8 @@ class ReplHole:
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
return '\n'.join(
|
return '\n'.join(
|
||||||
f'const {var_name} = ({var_expr.codegen()});'
|
f'{var_name} = ({var_expr.codegen()});'
|
||||||
for (var_name, var_expr) in self.val_bindings
|
for (var_name, var_expr) in self.val_bindings
|
||||||
if var_name not in types_.BUILTINS_CONTEXT
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
@ -92,7 +90,7 @@ BUILTIN_SUBSTITUTIONS: Sequence[Tuple[str, Expression]] = (
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Function:
|
class Function:
|
||||||
forms: 'Sequence[Tuple[Pattern, Expression]]'
|
forms: 'Sequence[Tuple[patterns.Pattern, Expression]]'
|
||||||
|
|
||||||
def subst(self, expression: Expression, variable: str) -> Expression:
|
def subst(self, expression: Expression, variable: str) -> Expression:
|
||||||
return Function([
|
return Function([
|
||||||
|
@ -106,8 +104,12 @@ class Function:
|
||||||
def step(self) -> Option[Expression]:
|
def step(self) -> Option[Expression]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def function_to_tree(self) -> 'Result[Expression, MatchException]':
|
||||||
|
subtrees = (patt.match_tree([], LeafNode.from_value(expr)) for (patt, expr) in self.forms)
|
||||||
|
return reduce(merge_trees, subtrees, LeafNode(tuple()))
|
||||||
|
|
||||||
def eliminate(self, v: Expression) -> Result[Expression, MatchException]:
|
def eliminate(self, v: Expression) -> Result[Expression, MatchException]:
|
||||||
match_trees = tuple(pattern.match_tree(EMPTY_STRUCT_PATH, LeafNode.from_value(bindings_to_lets(pattern.bindings(), v, body))) for (pattern, body) in self.forms)
|
match_trees = tuple(pattern.match_tree([], LeafNode.from_value(bindings_to_lets(pattern.bindings(), v, body))) for (pattern, body) in self.forms)
|
||||||
unified_match_tree = merge_all_trees(match_trees)
|
unified_match_tree = merge_all_trees(match_trees)
|
||||||
return compile_tree(unified_match_tree, v)
|
return compile_tree(unified_match_tree, v)
|
||||||
|
|
||||||
|
@ -162,7 +164,7 @@ class LetBinding:
|
||||||
|
|
||||||
def codegen(self) -> str:
|
def codegen(self) -> str:
|
||||||
rhs_cg = self.rhs.codegen_named(self.lhs) if isinstance(self.rhs, Function) else self.rhs.codegen()
|
rhs_cg = self.rhs.codegen_named(self.lhs) if isinstance(self.rhs, Function) else self.rhs.codegen()
|
||||||
return f'({self.lhs}={rhs_cg},{self.body.codegen()})'
|
return f'(({self.lhs}) => {self.body.codegen()})({rhs_cg})'
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Application:
|
class Application:
|
||||||
|
@ -198,13 +200,6 @@ class Application:
|
||||||
if isinstance(self.first, Function | Builtin) and self.arg.is_value():
|
if isinstance(self.first, Function | Builtin) and self.arg.is_value():
|
||||||
return unwrap_opt(self.first.try_apply(self.arg)).codegen()
|
return unwrap_opt(self.first.try_apply(self.arg)).codegen()
|
||||||
else:
|
else:
|
||||||
match self.first:
|
|
||||||
case Application(Builtin('+', _, _), addend1):
|
|
||||||
return f'({addend1.codegen()} + {self.arg.codegen()})'
|
|
||||||
case Builtin('S', _, _):
|
|
||||||
return f'(1+{self.arg.codegen()})'
|
|
||||||
case Builtin('pred', _, _):
|
|
||||||
return f'({self.arg.codegen()}-1)'
|
|
||||||
return f'({self.first.codegen()})({self.arg.codegen()})'
|
return f'({self.first.codegen()})({self.arg.codegen()})'
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -262,7 +257,6 @@ class Switch:
|
||||||
def subst(self, expression: Expression, variable: str) -> Expression:
|
def subst(self, expression: Expression, variable: str) -> Expression:
|
||||||
return Switch(
|
return Switch(
|
||||||
{i: e.subst(expression, variable) for i, e in self.branches.items()},
|
{i: e.subst(expression, variable) for i, e in self.branches.items()},
|
||||||
self.fallback,
|
|
||||||
self.switching_on.subst(expression, variable))
|
self.switching_on.subst(expression, variable))
|
||||||
|
|
||||||
def is_value(self) -> bool:
|
def is_value(self) -> bool:
|
||||||
|
@ -271,14 +265,14 @@ class Switch:
|
||||||
def step(self) -> Option[Expression]:
|
def step(self) -> Option[Expression]:
|
||||||
match self.switching_on.step():
|
match self.switching_on.step():
|
||||||
case Some(switch_expr_stepped):
|
case Some(switch_expr_stepped):
|
||||||
return Some(Switch(self.branches, self.fallback, switch_expr_stepped))
|
return Switch(self.branches, switch_expr_stepped)
|
||||||
case None:
|
case None:
|
||||||
match self.switching_on:
|
match self.switching_on:
|
||||||
case Int(n):
|
case Int(n):
|
||||||
if n in self.branches:
|
if n in self.branches:
|
||||||
return Some(self.branches[n])
|
return Some(self.branches[n])
|
||||||
else:
|
else:
|
||||||
return Some(self.fallback)
|
return None
|
||||||
raise Exception('Attempted to switch on non-integer value')
|
raise Exception('Attempted to switch on non-integer value')
|
||||||
raise Exception('Unreachable')
|
raise Exception('Unreachable')
|
||||||
|
|
||||||
|
@ -288,11 +282,20 @@ class Switch:
|
||||||
def codegen(self) -> str:
|
def codegen(self) -> str:
|
||||||
switching_on_code = self.switching_on.codegen()
|
switching_on_code = self.switching_on.codegen()
|
||||||
return ':'.join(
|
return ':'.join(
|
||||||
f'{switching_on_code}=={val}?({branch.codegen()})'
|
f'{switching_on_code}==={val}?({branch.codegen()})'
|
||||||
for val, branch in self.branches.items()
|
for val, branch in self.branches.items()
|
||||||
) + f':{self.fallback.codegen()}'
|
) + f':{self.fallback.codegen()}'
|
||||||
|
|
||||||
def compile_tree(tree: 'MatchTree[Expression]', match_against: Expression) -> Result[Expression, MatchException]:
|
def compile_tree(tree: 'MatchTree[Expression]', match_against: Expression) -> Result[Expression, MatchException]:
|
||||||
|
|
||||||
|
def match_int_builtin(lookup: Callable[[int], Expression]) -> Callable[[Expression], Option[Expression]]:
|
||||||
|
def match_inner(i: Expression) -> Expression:
|
||||||
|
match i:
|
||||||
|
case Int(value):
|
||||||
|
return lookup(value)
|
||||||
|
raise Exception('Bad type! Eep!')
|
||||||
|
return match_inner
|
||||||
|
|
||||||
match tree:
|
match tree:
|
||||||
case LeafNode([match]):
|
case LeafNode([match]):
|
||||||
return Ok(match)
|
return Ok(match)
|
||||||
|
@ -311,12 +314,11 @@ def compile_tree(tree: 'MatchTree[Expression]', match_against: Expression) -> Re
|
||||||
return Err(e)
|
return Err(e)
|
||||||
case Ok(fallback):
|
case Ok(fallback):
|
||||||
return Ok(Switch(dict(zip(specific_trees.keys(), exprs)), fallback, match_against))
|
return Ok(Switch(dict(zip(specific_trees.keys(), exprs)), fallback, match_against))
|
||||||
raise Exception('Unreachable')
|
|
||||||
|
|
||||||
def location_to_ir(location: StructurePath) -> Callable[[Expression], Expression]:
|
def location_to_ir(location: StructurePath) -> Callable[[Expression], Expression]:
|
||||||
def access_location(part: int) -> Callable[[Expression], Expression]:
|
def access_location(part: int) -> Callable[[Expression], Expression]:
|
||||||
def remove(expr: Expression) -> Expression:
|
def remove(expr: Expression) -> Expression:
|
||||||
return Application(Builtin(f'pred', Builtin._PLUS_CONST(-1), f'$=>$-1'), expr)
|
return Application(Builtin(f'{part}', Builtin._PLUS_CONST(-1), f'$=>$-1'), expr)
|
||||||
def access_location_prime(expr: Expression) -> Expression:
|
def access_location_prime(expr: Expression) -> Expression:
|
||||||
if part < 1:
|
if part < 1:
|
||||||
return remove(expr)
|
return remove(expr)
|
||||||
|
@ -330,13 +332,12 @@ def location_to_ir(location: StructurePath) -> Callable[[Expression], Expression
|
||||||
return c(location_to_ir(StructurePath(rest_location)), access_location(part))
|
return c(location_to_ir(StructurePath(rest_location)), access_location(part))
|
||||||
raise Exception('Unreachable')
|
raise Exception('Unreachable')
|
||||||
|
|
||||||
def bindings_to_lets(bindings: Collection[Tuple[str, StructurePath]], deconstructing_term: Expression, body_expr: Expression) -> Expression:
|
def bindings_to_lets(bindings: Sequence[Tuple[str, StructurePath]], deconstructing_term: Expression, body_expr: Expression) -> Expression:
|
||||||
match bindings:
|
match bindings:
|
||||||
case []:
|
case []:
|
||||||
return body_expr
|
return body_expr
|
||||||
case [(binding_name, location), *rest]:
|
case [(binding_name, location), *rest]:
|
||||||
return LetBinding(binding_name, location_to_ir(location)(deconstructing_term), bindings_to_lets(rest, deconstructing_term, body_expr))
|
return LetBinding(binding_name, location_to_ir(location)(deconstructing_term), bindings_to_lets(rest, deconstructing_term, body_expr))
|
||||||
raise Exception('Unreachable')
|
|
||||||
|
|
||||||
def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression:
|
def subst_all(bindings: Sequence[Tuple[str, Expression]], body: Expression) -> Expression:
|
||||||
match bindings:
|
match bindings:
|
||||||
|
|
|
@ -13,8 +13,6 @@ PatternID = NewType('PatternID', 'int')
|
||||||
|
|
||||||
MatchTree: TypeAlias = 'LeafNode[A] | IntNode[A]'
|
MatchTree: TypeAlias = 'LeafNode[A] | IntNode[A]'
|
||||||
|
|
||||||
EMPTY_STRUCT_PATH: StructurePath = StructurePath(tuple())
|
|
||||||
|
|
||||||
class MatchException(IntEnum):
|
class MatchException(IntEnum):
|
||||||
Incomplete = auto()
|
Incomplete = auto()
|
||||||
Ambiguous = auto()
|
Ambiguous = auto()
|
||||||
|
@ -27,17 +25,16 @@ class LeafNode(Generic[A]):
|
||||||
def from_value(value: A) -> 'LeafNode[A]':
|
def from_value(value: A) -> 'LeafNode[A]':
|
||||||
return LeafNode((value,))
|
return LeafNode((value,))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fail() -> 'LeafNode[Any]':
|
||||||
|
return LeafNode([])
|
||||||
|
|
||||||
def is_complete(self) -> bool:
|
def is_complete(self) -> bool:
|
||||||
return len(self.matches) > 0
|
return len(matches) > 0
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '{..}' if len(self.matches) else 'X'
|
return '{..}' if len(self.matches) else 'X'
|
||||||
|
|
||||||
def fill_empty_leaves(self, with_values: Collection[A]) -> MatchTree:
|
|
||||||
return self if len(self.matches) else LeafNode(with_values)
|
|
||||||
|
|
||||||
FAIL_NODE: LeafNode[Any] = LeafNode(tuple())
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class IntNode(Generic[A]):
|
class IntNode(Generic[A]):
|
||||||
location: StructurePath
|
location: StructurePath
|
||||||
|
@ -51,13 +48,6 @@ class IntNode(Generic[A]):
|
||||||
merge_trees(self.fallback_tree, other)
|
merge_trees(self.fallback_tree, other)
|
||||||
)
|
)
|
||||||
|
|
||||||
def fill_empty_leaves(self, with_values: Collection[A]) -> MatchTree:
|
|
||||||
return IntNode(
|
|
||||||
self.location,
|
|
||||||
{i: tree.fill_empty_leaves(with_values) for (i, tree) in self.specific_trees.items()},
|
|
||||||
self.fallback_tree.fill_empty_leaves(with_values)
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_complete(self) -> bool:
|
def is_complete(self) -> bool:
|
||||||
return all(
|
return all(
|
||||||
subtree.is_complete() for subtree in self.specific_trees.values()
|
subtree.is_complete() for subtree in self.specific_trees.values()
|
||||||
|
@ -68,11 +58,6 @@ class IntNode(Generic[A]):
|
||||||
|
|
||||||
def merge_trees(t1: 'MatchTree[A]', t2: 'MatchTree[A]') -> 'MatchTree[A]':
|
def merge_trees(t1: 'MatchTree[A]', t2: 'MatchTree[A]') -> 'MatchTree[A]':
|
||||||
match (t1, t2):
|
match (t1, t2):
|
||||||
case (LeafNode(matches1), LeafNode(matches2)):
|
|
||||||
return LeafNode((*matches1, *matches2))
|
|
||||||
case (LeafNode(matches), other_node) | (other_node, LeafNode(matches)): #type: ignore
|
|
||||||
# For ignore, see: https://github.com/python/mypy/issues/13950
|
|
||||||
return other_node.fill_empty_leaves(matches)
|
|
||||||
case (IntNode(location1, specific_trees1, fallback_tree1) as tree1, IntNode(location2, specific_trees2, fallback_tree2) as tree2):
|
case (IntNode(location1, specific_trees1, fallback_tree1) as tree1, IntNode(location2, specific_trees2, fallback_tree2) as tree2):
|
||||||
if location1 == location2:
|
if location1 == location2:
|
||||||
return IntNode(
|
return IntNode(
|
||||||
|
@ -96,8 +81,11 @@ def merge_trees(t1: 'MatchTree[A]', t2: 'MatchTree[A]') -> 'MatchTree[A]':
|
||||||
return tree1.merge_each_child(tree2)
|
return tree1.merge_each_child(tree2)
|
||||||
else:
|
else:
|
||||||
return tree2.merge_each_child(tree1)
|
return tree2.merge_each_child(tree1)
|
||||||
|
case (IntNode() as int_node, match_node) | (match_node, IntNode() as int_node):
|
||||||
|
return int_node.merge_each_child(match_node)
|
||||||
|
case (LeafNode(matches1), LeafNode(matches2)):
|
||||||
|
return LeafNode((*matches1, *matches2))
|
||||||
raise Exception('Unreachable')
|
raise Exception('Unreachable')
|
||||||
|
|
||||||
def merge_all_trees(trees: 'Iterable[MatchTree[A]]') -> 'MatchTree[A]':
|
def merge_all_trees(trees: 'Iterable[MatchTree[A]]') -> 'MatchTree[A]':
|
||||||
fail_node: MatchTree[A] = FAIL_NODE # Annotation or type checking purposes
|
return reduce(merge_trees, trees, LeafNode.fail())
|
||||||
return reduce(merge_trees, trees, fail_node)
|
|
12
patterns.py
12
patterns.py
|
@ -3,7 +3,7 @@ from emis_funky_funktions import *
|
||||||
from typing import Collection, Mapping, Sequence, Tuple, TypeAlias
|
from typing import Collection, Mapping, Sequence, Tuple, TypeAlias
|
||||||
|
|
||||||
import types_
|
import types_
|
||||||
from match_tree import LeafNode, IntNode, MatchTree, StructurePath, FAIL_NODE, EMPTY_STRUCT_PATH
|
from match_tree import LeafNode, IntNode, MatchTree, StructurePath
|
||||||
|
|
||||||
Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern | IgnorePattern'
|
Pattern: TypeAlias = 'NamePattern | IntPattern | SPattern | IgnorePattern'
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class NamePattern:
|
||||||
return success_leaf
|
return success_leaf
|
||||||
|
|
||||||
def bindings(self) -> Collection[Tuple[str, StructurePath]]:
|
def bindings(self) -> Collection[Tuple[str, StructurePath]]:
|
||||||
return ((self.name, EMPTY_STRUCT_PATH),)
|
return ((self.name, tuple()),)
|
||||||
|
|
||||||
def binds(self, var: str) -> bool:
|
def binds(self, var: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -83,15 +83,15 @@ class SPattern:
|
||||||
# If the child check is also an int node on pred of our current location:
|
# If the child check is also an int node on pred of our current location:
|
||||||
# "raise" that node to be at our current level.
|
# "raise" that node to be at our current level.
|
||||||
if child_location == (*location, 0):
|
if child_location == (*location, 0):
|
||||||
return IntNode(location, dict(((0, FAIL_NODE), *((1 + v, mt) for v, mt in trees.items()))), fallback)
|
return IntNode(location, dict(((0, LeafNode.fail()), *((1 + v, mt) for v, mt in trees.items()))), fallback)
|
||||||
else:
|
else:
|
||||||
return IntNode(location, {0: FAIL_NODE}, child)
|
return IntNode(location, {0: LeafNode.fail()}, child)
|
||||||
case child:
|
case child:
|
||||||
return IntNode(location, {0: FAIL_NODE}, child)
|
return IntNode(location, {0: LeafNode.fail()}, child)
|
||||||
|
|
||||||
def bindings(self) -> Collection[Tuple[str, StructurePath]]:
|
def bindings(self) -> Collection[Tuple[str, StructurePath]]:
|
||||||
# Prepend each binding path of the child pattern with 0 (i.e. subtract one from an int)
|
# Prepend each binding path of the child pattern with 0 (i.e. subtract one from an int)
|
||||||
return tuple((name, StructurePath((0, *path))) for (name, path) in self.pred.bindings())
|
return tuple((name, (0, *path)) for (name, path) in self.pred.bindings())
|
||||||
|
|
||||||
def binds(self, var: str) -> bool:
|
def binds(self, var: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue