Last active
April 24, 2024 16:34
-
-
Save nerodono/d87aee7fb6f51efc88638b3b2ecfa576 to your computer and use it in GitHub Desktop.
Another thing that I did on my phone (omg this was fucking suffering)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import reduce as foldl | |
import string | |
import pprint | |
import builtins | |
class ParseFail(Exception): | |
... | |
def group_fails(message, *fails): | |
return ParseFail(f"{message}: {fails!r}") | |
def concat(x, y): | |
return x + y | |
def take_n(n): | |
def take_n_impl(src): | |
if len(src) < n: | |
raise ParseFail(f"{src!r} not enough data (needed {n})") | |
return src[:n], src[n:] | |
return take_n_impl | |
take1 = take_n(1) | |
def many(f, acc=concat): | |
def many_impl(src): | |
initial, left = f(src) | |
while True: | |
try: | |
cur, left = f(left) | |
except ParseFail: | |
break | |
initial = acc(initial, cur) | |
return initial, left | |
return many_impl | |
def skip(f): | |
return first(f, lambda _: None) | |
def ok_if(f, pred, fail_reason): | |
def ok_if_impl(src): | |
val, left = f(src) | |
if pred(val): | |
return val, left | |
raise ParseFail(f"{src!r} failed: {fail_reason}") | |
return ok_if_impl | |
def first(f, map): | |
def first_impl(src): | |
res = f(src) | |
return (map(res[0]), res[1]) | |
return first_impl | |
def compose(*funcs): | |
return foldl(lambda acc, curr: lambda x: acc(curr(x)), funcs) | |
def one_of(d): | |
return lambda x: x in d | |
def name(name_val): | |
return lambda x: (name_val, x) | |
def tag(t): | |
return ok_if(take_n(len(t)), lambda x: x == t, f"expected {t}") | |
def alt(group_name, *parsers): | |
if not isinstance(group_name, str): | |
parsers = (group_name, *parsers) | |
def alt_impl(src): | |
errs = [] | |
for f in parsers: | |
try: | |
return f(src) | |
except ParseFail as e: | |
errs.append(e) | |
raise group_fails( | |
f"{group_name} failed to parse {src!r}", | |
errs | |
) | |
return alt_impl | |
def opt(parser): | |
def opt_impl(src): | |
try: | |
return parser(src) | |
except ParseFail: | |
return (None, src) | |
return opt_impl | |
def pick_c(n): | |
return lambda parser: pick(n, parser) | |
def pick(n, parser): | |
return first(parser, lambda x: x[n]) | |
def trim(parser, label="no label"): | |
return pick(0, seq( | |
ws, | |
parser, | |
ws, | |
)) | |
def seq(*parsers): | |
def seq_impl(src): | |
buffer = () | |
for f in parsers: | |
item, src = f(src) | |
if item is None: | |
continue | |
buffer = (*buffer, item) | |
return buffer, src | |
return seq_impl | |
OPS = { | |
"+": lambda x, y: x + y, | |
"-": lambda x, y: x - y, | |
"*": lambda x, y: x * y, | |
"/": lambda x, y: x // y, | |
"^": lambda x, y: x**y, | |
} | |
def perform(op, *args): | |
return OPS[op](*args) | |
singleton = lambda x: (x,) | |
singleton_of = lambda f: first(f, singleton) | |
ident_begin_char = ok_if( | |
take1, | |
one_of(string.ascii_letters), | |
"Expected begin ident char" | |
) | |
ident_rest_char = ok_if( | |
take1, | |
one_of(string.ascii_letters + string.digits + "_?!"), | |
"Expected rest ident char" | |
) | |
ident = first( | |
seq(ident_begin_char, opt(many(ident_rest_char))), | |
lambda x: ('binding', x[0] + (x[1:] or ('',))[0]) | |
) | |
skip_opt = compose(opt, skip) | |
whitespace = ok_if(take1, one_of(" \n\t"), "whitespace expected") | |
whitespaces = many(whitespace) | |
ws = skip_opt(whitespaces) | |
digit = ok_if(take1, one_of("0123456789"), "must be valid digit") | |
number = first(many(digit), compose(name("int"), int)) | |
semicolon = tag(";") | |
add = tag("+") | |
sub = tag("-") | |
mul = tag("*") | |
div = tag("/") | |
lt = tag("<") | |
gt = tag(">") | |
lte = tag(">=") | |
gte = tag("<=") | |
def define_func(expr): | |
def define_func_impl(left): | |
(arg, _, body), left = seq( | |
ident, | |
trim(tag(":")), | |
expr | |
)(left) | |
return ('defunc', arg, body), left | |
return define_func_impl | |
def let_in_chain(expr): | |
def let_in_chain_impl(left): | |
assignment = seq( | |
ident, | |
trim(tag("=")), | |
expr, | |
semicolon, | |
) | |
let, left = seq( | |
tag("let"), | |
skip(whitespaces), | |
many(singleton_of(trim(assignment))), | |
trim(tag("in")), | |
expr | |
)(left) | |
assignments = tuple( | |
(bind, value) | |
for bind, _, value, _ in let[1] | |
) | |
bind_in = let[3] | |
return ('let', assignments, bind_in), left | |
return let_in_chain_impl | |
def make_item(expr): | |
return alt( | |
let_in_chain(expr), | |
define_func(expr), | |
pick(1, seq(tag("("), expr, tag(")"))), | |
number, | |
ident, | |
) | |
def right_associative(f, down): | |
def ra_impl(left): | |
x, left = down(left) | |
try: | |
op, left = f(left) | |
except ParseFail: | |
return x, left | |
y, left = ra_impl(left) | |
return (op, x, y), left | |
return ra_impl | |
def left_associative(f, down): | |
def la_impl(left): | |
x, left = down(left) | |
while True: | |
try: | |
(action, operand), left = f(down, left) | |
except ParseFail: | |
break | |
x = (action, x, operand) | |
return x, left | |
return la_impl | |
def binary(op): | |
def binary_impl(down, left): | |
operator, left = op(left) | |
rhs, left = down(left) | |
return (operator, rhs), left | |
return binary_impl | |
def application(down, left): | |
operand, left = down(left) | |
return ('apply', operand), left | |
# defer name lookup | |
item = make_item(lambda x: expr(x)) | |
expr = left_associative( | |
binary(trim(alt(add, sub))), | |
left_associative( | |
binary(trim(alt(div, mul))), | |
right_associative( | |
trim(tag("^")), | |
left_associative( | |
application, | |
trim(item), | |
) | |
), | |
) | |
) | |
def evaluate(tup, ctx): | |
tag, *args = tup | |
if tag in OPS: | |
return OPS[tag](*map(lambda x: evaluate(x, ctx), args)) | |
if tag == 'defunc': | |
(_, arg), body = args | |
return lambda x: evaluate(body, {**ctx, arg: x}) | |
elif tag == 'int': | |
return args[0] | |
elif tag == 'binding': | |
return ctx[args[0]] | |
elif tag == 'apply': | |
f, arg = args | |
x = evaluate(arg, ctx) | |
return evaluate(f, ctx)(x) | |
elif tag == 'let': | |
assignments, bind_in = args | |
for (_, name), value in assignments: | |
ctx = {**ctx, name: evaluate(value, ctx)} | |
return evaluate(bind_in, ctx) | |
else: | |
raise NotImplementedError(f"{tup} (ctx = {ctx})") | |
print("-- Хуйня --") | |
print("Может исполнять:") | |
for i, op in enumerate("+-*/"): | |
print(f"{i+1}. a {op} b") | |
print("Еще умеет") | |
print("1. Функции: (x: x + 10) 20") | |
print(" (Результат исполнения: 30)") | |
print("ВАЖНО!! функции могут быть только с одним аргументом, если надо больше — возвращайте функцию из функции, вот так:") | |
print(" (x: y: x + y) 2 2") | |
print("2. let-биндинги: let x = 10; y = 20; in x + y") | |
print("На выходе напечатается результат парсинга и исполнения") | |
while True: | |
inp = input("Enter expression: ") | |
parsed, left = expr(inp) | |
pprint.pprint(parsed) | |
if left: | |
print("Tail =", repr(left)) | |
else: | |
print("Результат исполнения:", evaluate(parsed, {})) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment