Yakl
Jump to navigation
Jump to search
Yakl is a expression based object oriented interpreted language.
Interpreter (yakl.py)
from yakl_objects import *
import importlib.util
import sys
def import_python_file(path):
name = path.replace("/", "_").replace("\\", "_")
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
def get_file(place):
content = ""
with open(place, "r") as f:
content = f.read()
return content
code = get_file("main.yakl")
class Node:
def __init__(self, k, v, e, c):
self.kind = k
self.value = v
self.extra = e
self.children = c
def __repr__(self):
return f"Node({repr(self.kind)}, {repr(self.value)}, {repr(self.extra)}, {repr(self.children)})"
class Result:
def __init__(self, ok, backtrack, value, message=None, index=None):
self.ok = ok
self.backtrack = backtrack
self.value = value
self.message = message
self.index = index
def failed(self):
return not self.ok
def backtracked(self):
return self.backtrack
def errored(self):
return not self.ok and not self.backtrack
def __repr__(self):
return f"Result({repr(self.ok)}, {repr(self.backtrack)}, {repr(self.value)}, {repr(self.message)}, {repr(self.index)})"
class Parser:
def __init__(self, code):
self.code = code
self.i = 0
def Ok(self, item):
return Result(True, False, item)
def Backtrack(self):
return Result(False, True, None)
def Error(self, msg):
return Result(False, False, None, msg, self.i)
def is_end(self):
return self.i >= len(self.code)
def get_character(self):
return self.code[self.i] if not self.is_end() else "\0"
def skip_whitespace(self):
while not self.is_end() and self.get_character() in " \t\n\r":
self.i += 1
def advance(self):
c = self.get_character()
self.i += 1
return c
def match(self, c):
i = 0
save = self.i
while self.advance() == c[i]:
i += 1
if i == len(c):
return True
self.i = save
return False
def parse_number(self):
self.skip_whitespace()
save = self.i
num = ""
# Optional sign
c = self.get_character()
if c in "+-":
num += self.advance()
digits_before = False
digits_after = False
# Digits before decimal
while self.get_character().isdigit():
digits_before = True
num += self.advance()
# Decimal point
if self.get_character() == ".":
num += self.advance()
# Digits after decimal
while self.get_character().isdigit():
digits_after = True
num += self.advance()
# If no digits at all, not a number
if not digits_before and not digits_after:
self.i = save
return self.Backtrack()
# Determine type
is_float = "." in num
# Convert
value = float(num) if is_float else int(num)
return self.Ok(Node("NUMBER", value, {"float": is_float}, []))
def parse_string(self):
self.skip_whitespace()
save = self.i
if not self.match('"'):
return self.Backtrack()
s = ""
while not self.is_end():
c = self.advance()
if c == '"':
return self.Ok(Node("STRING", s, {}, []))
if c == "\\":
if self.is_end():
return self.Error("Unterminated escape")
esc = self.advance()
if esc == "n":
s += "\n"
elif esc == "t":
s += "\t"
elif esc == '"':
s += '"'
elif esc == "\\":
s += "\\"
else:
return self.Error("Unknown escape: \\" + esc)
else:
s += c
self.i = save
return self.Error("Unterminated string")
def parse_identifier(self):
self.skip_whitespace()
save = self.i
c = self.get_character()
if not (c.isalpha() or c == "_"):
return self.Backtrack()
ident = ""
ident += self.advance()
while True:
c = self.get_character()
if c.isalnum() or c == "_":
ident += self.advance()
else:
break
if ident in ["bound", "function", "if", "while", "object"]:
self.i = save
return self.Backtrack()
return self.Ok(Node("IDENT", ident, {}, []))
def parse_params(self):
# SIMILAR RULE, REPLACE FEW STUFF
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_identifier()
if multiplicative.failed():
if multiplicative.errored():
return multiplicative
return self.Ok(Node("ITEMS", None, {}, []))
base = multiplicative
next_ = self.Ok(None)
children = [base.value]
while True:
save = self.i
self.skip_whitespace()
if self.match(","):
OP = "COMMA"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_identifier()
if next_.failed():
self.i = save
break
children.append(next_.value)
if next_.errored():
return next_
return self.Ok(Node("ITEMS", None, {}, children))
def parse_items(self):
# SIMILAR RULE, REPLACE FEW STUFF
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_expression()
if multiplicative.failed():
if multiplicative.errored():
return multiplicative
return self.Ok(Node("ITEMS", None, {}, []))
base = multiplicative
next_ = self.Ok(None)
children = [base.value]
while True:
save = self.i
self.skip_whitespace()
if self.match(","):
OP = "COMMA"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_expression()
if next_.failed():
self.i = save
break
children.append(next_.value)
if next_.errored():
return next_
return self.Ok(Node("ITEMS", None, {}, children))
def parse_object(self):
self.skip_whitespace()
save = self.i
if self.match("object"):
self.skip_whitespace()
if self.match("{"):
self.skip_whitespace()
code = self.parse_block()
if not code.failed():
self.skip_whitespace()
if self.match("}"):
return self.Ok(Node("OBJ", None, {}, [code.value]))
self.i = save
return self.Backtrack()
def parse_function(self):
self.skip_whitespace()
save = self.i
if self.match("function"):
self.skip_whitespace()
if self.match("("):
self.skip_whitespace()
parameters = self.parse_params()
if not parameters.failed():
self.skip_whitespace()
if self.match(")"):
self.skip_whitespace()
if self.match("{"):
self.skip_whitespace()
code = self.parse_block()
if not code.failed():
self.skip_whitespace()
if self.match("}"):
return self.Ok(
Node(
"FUNC",
None,
{},
[parameters.value, code.value],
)
)
self.i = save
return self.Backtrack()
def parse_primary(self):
while True:
self.skip_whitespace()
value = None
string = self.parse_string()
if not string.failed():
value = string
break
ident = self.parse_identifier()
if not ident.failed():
value = ident
break
number = self.parse_number()
if not number.failed():
value = number
break
object_ = self.parse_object()
if not object_.failed():
value = object_
break
save = self.i
if self.match("("):
self.skip_whitespace()
expr = self.parse_expression()
self.skip_whitespace()
if not expr.failed():
if self.match(")"):
value = expr
break
self.i = save
if self.match("["):
self.skip_whitespace()
items = self.parse_items()
if not items.failed():
self.skip_whitespace()
if self.match("]"):
value = self.Ok(Node("LIST", None, {}, [items.value]))
break
self.i = save
function = self.parse_function()
if not function.failed():
value = function
break
self.i = save
return self.Backtrack()
while True:
self.skip_whitespace()
save = self.i
if self.match("("):
self.skip_whitespace()
params = self.parse_items()
if not params.failed():
self.skip_whitespace()
if self.match(")"):
value = self.Ok(
Node("CALL", None, {}, [value.value, params.value])
)
continue
else:
print(params)
self.i = save
if self.match("["):
self.skip_whitespace()
params = self.parse_items()
if not params.failed():
self.skip_whitespace()
if self.match("]"):
value = self.Ok(
Node("INDEX", None, {}, [value.value, params.value])
)
continue
self.i = save
break
return value
def parse_multiplicative(self):
# SIMILAR RULE, REPLACE FEW STUFF
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_primary()
if multiplicative.failed():
return multiplicative
base = multiplicative
next_ = self.Ok(None)
while True:
save = self.i
self.skip_whitespace()
if self.match("*"):
OP = "MUL"
elif self.match("/"):
OP = "DIV"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_primary()
if next_.failed():
self.i = save
break
base = self.Ok(Node(OP, None, {}, [base.value, next_.value]))
if next_.errored():
return next_
return base
def parse_additive(self):
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_multiplicative()
if multiplicative.failed():
return multiplicative
base = multiplicative
next_ = self.Ok(None)
while True:
save = self.i
self.skip_whitespace()
if self.match("+"):
OP = "ADD"
elif self.match("-"):
OP = "SUB"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_multiplicative()
if next_.failed():
self.i = save
break
base = self.Ok(Node(OP, None, {}, [base.value, next_.value]))
if next_.errored():
return next_
return base
def parse_relational(self):
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_additive()
if multiplicative.failed():
return multiplicative
base = multiplicative
next_ = self.Ok(None)
while True:
save = self.i
self.skip_whitespace()
if self.match(">"):
OP = "GRE"
elif self.match("<"):
OP = "LES"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_additive()
if next_.failed():
self.i = save
break
base = self.Ok(Node(OP, None, {}, [base.value, next_.value]))
if next_.errored():
return next_
return base
def parse_equality(self):
# additive := multiplicative (("+" | "-") multiplicative)*
# ELSE backtrack
multiplicative = self.parse_relational()
if multiplicative.failed():
return multiplicative
base = multiplicative
next_ = self.Ok(None)
while True:
save = self.i
self.skip_whitespace()
if self.match("=="):
OP = "EQU"
elif self.match("!="):
OP = "NEQ"
else:
self.i = save
break
self.skip_whitespace()
next_ = self.parse_relational()
if next_.failed():
self.i = save
break
base = self.Ok(Node(OP, None, {}, [base.value, next_.value]))
if next_.errored():
return next_
return base
def parse_control(self):
# if := "if" expr "{" block "}" |OR| equality
self.skip_whitespace()
save = self.i
if self.match("if"):
self.skip_whitespace()
condition = self.parse_expression()
if not condition.failed():
self.skip_whitespace()
if self.match("{"):
self.skip_whitespace()
code = self.parse_block()
if not code.failed():
self.skip_whitespace()
if self.match("}"):
self.skip_whitespace()
save2 = self.i
if self.match("else"):
self.skip_whitespace()
if self.match("{"):
self.skip_whitespace()
code2 = self.parse_block()
if not code2.failed():
self.skip_whitespace()
if self.match("}"):
return self.Ok(
Node(
"IF",
None,
{},
[
condition.value,
code.value,
code2.value,
],
)
)
return self.Ok(
Node("IF", None, {}, [condition.value, code.value])
)
self.i = save
if self.match("while"):
self.skip_whitespace()
condition = self.parse_expression()
if not condition.failed():
self.skip_whitespace()
if self.match("{"):
self.skip_whitespace()
code = self.parse_block()
if not code.failed():
self.skip_whitespace()
if self.match("}"):
return self.Ok(
Node("WHILE", None, {}, [condition.value, code.value])
)
self.i = save
result = self.parse_equality()
return result
def parse_assignment(self):
# assignment := ident/index "=" assignment |OR| logic_or
save = self.i
skip = True
while skip:
skip = False
ident = self.parse_primary()
if ident.failed():
if ident.errored():
return ident
self.i = save
break
self.skip_whitespace()
if not self.match("="):
self.i = save
break
self.skip_whitespace()
assign = self.parse_assignment()
if assign.failed():
if assign.errored():
return assign
self.i = save
break
return self.Ok(Node("ASSIGN", None, {}, [ident.value, assign.value]))
result = self.parse_control()
return result
def parse_expression(self):
save = self.i
result = self.parse_assignment()
return result
def parse_statement(self):
result = self.parse_expression()
self.skip_whitespace()
if self.match(";"):
return result
return self.Error("No semicolon")
def parse_block(self):
children = []
while True:
self.skip_whitespace()
if self.match("}"):
self.i -= 1
break
stmt = self.parse_statement()
if stmt.failed():
return stmt
children.append(stmt.value)
return self.Ok(Node("PROGRAM", None, {}, children))
def parse_program(self):
children = []
while True:
self.skip_whitespace()
if self.is_end():
break
result = self.parse_statement()
if result.failed():
return result
children.append(result.value)
return self.Ok(Node("PROGRAM", None, {}, children))
class Api:
def __init__(self, context):
self.context = context
def call(self, function, params):
name = function
args = params
if name.type == "python-function":
return name.value(*args)
else:
backup = self.context.env.copy()
self.context.env = name.value.env + [{}]
params = {
k: v
for k, v in zip(
name.value.params, args
)
}
self.context.env[-1].update(params)
res = self.context.execute(name.value.code)
self.context.env = backup
return res
def get(self, var):
return self.context.get(var)
class Interpreter:
def __init__(self, ast):
self.ast = ast
self.env = [
{
"print": Value("python-function", lambda *x: print(*x) or Value("nothing", Nothing())),
"input": Value("python-function", lambda: Value("string", String(input()))),
"with": Value("python-function", lambda x: self.with_(x)),
"len": Value("python-function", lambda x: Value("number", Number(len(x.value.value)))),
"nothing": Value("nothing", Nothing()),
"false": Value("bool", Boolean(False)),
"true": Value("bool", Boolean(True)),
}
]
def run(self):
return self.execute(self.ast)
def with_(self, file):
if file.value.value[-5:] == ".yakl":
fi = get_file(file.value.value)
p = Parser(fi)
root = p.parse_program().value
i = Interpreter(root)
return i.run()
else:
fi = import_python_file(file.value.value)
api = Api(self)
return fi.load(api)
def get(self, name):
x = len(self.env) - 1
while x >= 0:
if name in self.env[x]:
return self.env[x][name]
x -= 1
self.env[-1][name] = Value("nothing", Nothing())
return self.env[-1][name]
def set_(self, x, y):
x.type = y.type
x.value = y.value
def execute(self, ast):
if ast.kind == "PROGRAM":
last = None
for c in ast.children:
last = self.execute(c)
return last
elif ast.kind == "NUMBER":
return Value("number", Number(ast.value))
elif ast.kind == "STRING":
return Value("string", String(ast.value))
elif ast.kind == "LIST":
return Value("list", List([self.execute(c) for c in ast.children[0].children]))
elif ast.kind == "ASSIGN":
val = self.execute(ast.children[1])
self.set_(self.execute(ast.children[0]), val)
return val
elif ast.kind == "IDENT":
return self.get(ast.value)
elif ast.kind == "ADD":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("number", Number(left.value.value+right.value.value))
if left.type == "string" and right.type == "string":
return Value("string", String(left.value.value+right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "SUB":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("number", Number(left.value.value-right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "MUL":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("number", Number(left.value.value*right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "DIV":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("number", Number(left.value.value/right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "EQU":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
return Value("bool", Boolean(left.value==right.value))
elif ast.kind == "NEQ":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
return Value("bool", Boolean(left.value==right.value))
elif ast.kind == "GRE":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("bool", Boolean(left.value.value>right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "LES":
left = self.execute(ast.children[0])
right = self.execute(ast.children[1])
if left.type == "number" and right.type == "number":
return Value("bool", Boolean(left.value.value<right.value.value))
raise TypeError("Can't add "+left.type+" and "+right.type+".")
elif ast.kind == "FUNC":
return Value("function", Function(
[c.value for c in ast.children[0].children],
ast.children[1],
self.env.copy(),
))
elif ast.kind == "CALL":
name = self.execute(ast.children[0])
args = [self.execute(c) for c in ast.children[1].children]
if name.type == "python-function":
return name.value(*args)
else:
backup = self.env.copy()
self.env = name.value.env + [{}]
params = {
k: v
for k, v in zip(
name.value.params, args
)
}
self.env[-1].update(params)
res = self.execute(name.value.code)
self.env = backup
return res
elif ast.kind == "INDEX":
name = self.execute(ast.children[0])
index = [self.execute(c).value.value for c in ast.children[1].children]
return (
name.value[*index]
if len(index) > 1
else (
name.value[index[0]]
if not name.type == "object"
else name.value.env[index[0]]
)
)
elif ast.kind == "IF":
condition = self.execute(ast.children[0])
if condition:
code = self.execute(ast.children[1])
return code
elif len(ast.children) > 2:
code2 = self.execute(ast.children[2])
return code2
return None
elif ast.kind == "WHILE":
code = None
while self.execute(ast.children[0]):
code = self.execute(ast.children[1])
return code
elif ast.kind == "OBJ":
obj_scope = {}
self.env.append(obj_scope)
obj_scope["this"] = Value("object", Object(obj_scope))
self.execute(ast.children[0])
self.env.pop()
return obj_scope["this"]
p = Parser(code)
result = p.parse_program()
if not result.failed():
i = Interpreter(result.value)
i.run()
else:
print(result.value, result.ok, result.backtrack, result.message, result.index)
Yakl Objects (yakl_objects.py)
class Function:
def __init__(self, params, code, env):
self.params = params
self.code = code
self.env = env
def __repr__(self):
args = ", ".join(self.params)
return f"Function({args})"
class Object:
def __init__(self, scope):
self.env = scope
def __repr__(self):
return repr(self.env)
class BaseValue:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"{repr(self.value)}"
class List(BaseValue):
pass
class Number(BaseValue):
pass
class String(BaseValue):
pass
class Nothing(BaseValue):
def __init__(self):
pass
class Value:
def __init__(self, kind, value):
self.type = kind
self.value = value
def __repr__(self):
return f"{repr(self.value)} (of type {repr(self.type)})"
class Boolean(BaseValue):
pass