feat: runtime

This commit is contained in:
Dan Chen
2024-03-21 15:13:32 +08:00
parent e914aeee32
commit 49659dcc13
6 changed files with 339 additions and 4 deletions

164
runtime/ast/parser.py Normal file
View File

@ -0,0 +1,164 @@
from runtime.ast.tokenizer import Tokenizer
class Parser:
def __init__(self) -> None:
self.script = ""
self.tokenizer = Tokenizer()
self.current_token = None
def parse(self, script: str):
self.script = script
self.tokenizer.init(script)
self.current_token = self.tokenizer.get_next_token()
return self.program()
def program(self):
return {
"type": 'Program',
"body": {
"type": "BlockStatement",
"body": self.statement_list()
},
}
def return_statement(self):
self.eat('return')
return {
"type": 'ReturnStatement',
"value": self.expression_statement(),
}
def statement_list(self):
statment_list = [self.statement()]
while self.current_token != None:
statment_list.append(self.statement())
return statment_list
def statement(self):
if self.token_type() == "let":
return self.variable_statement()
return self.expression_statement()
def block_statement(self):
if self.token_type() != "{":
self.eat('{')
body = self.statement_list()
if self.token_type() != "}":
self.eat('}')
return {
"type": 'BlockStatement',
"body": body,
}
def expression_statement(self):
if self.token_type() == "return":
return self.return_statement()
if self._is_literal():
return self.literal()
if self.token_type() == "IDENTIFIER":
identifier = self.identifier()
if self.token_type() == "(":
return self.call_expression(identifier)
if self.token_type() == "SIMPLE_ASSIGN":
return self.assignment_expression(identifier)
return identifier
raise Exception("Unexpected token: " + self.token_type())
def assignment_expression(self,identifier):
self.eat('SIMPLE_ASSIGN')
return {
"type": 'AssignmentExpression',
"identifier": identifier,
"value": self.statement(),
}
def call_expression(self, identifier):
self.eat('(')
arguments = self.argument_list()
self.eat(')')
return {
"type": 'CallExpression',
"arguments": arguments,
"callee": identifier,
}
def token_type(self):
if self.current_token == None:
return None
return self.current_token["type"]
def _is_literal(self):
return self.current_token["type"] in ["NUMBER", "STRING", "FLOAT"]
# variable
def variable_statement(self):
self.eat('let')
identifier = self.identifier()
self.eat('SIMPLE_ASSIGN')
return {
"type": 'VariableDeclaration',
"identifier": identifier,
"value": self.statement(),
}
def eat(self, tokenType):
token = self.current_token
if token == None:
raise Exception("Unexpected EOF")
if token["type"] != tokenType:
raise Exception("Unexpected token: " + token["type"])
self.current_token = self.tokenizer.get_next_token()
return token
def identifier(self):
name = self.eat('IDENTIFIER')
return {
"type": 'Identifier',
"name": name["value"],
}
def literal(self):
token_type = self.current_token["type"]
if token_type == "NUMBER":
return self.numberic_literal()
if token_type == "STRING":
return self.string_literal()
if token_type == "FLOAT":
return self.float_literal()
raise Exception("Unexpected token: " + token_type)
def numberic_literal(self):
token = self.eat('NUMBER')
return {
"type": 'NumericLiteral',
"value": token["value"],
}
def string_literal(self):
token = self.eat('STRING')
return {
"type": 'StringLiteral',
"value": token["value"][1:-1],
}
def float_literal(self):
token = self.eat('FLOAT')
return {
"type": 'FloatLiteral',
"value": token["value"],
}
def argument_list(self):
args = []
while self.token_type() != ")":
args.append(self.statement())
if self.token_type() == ",":
self.eat(',')
return args

93
runtime/ast/runtime.py Normal file
View File

@ -0,0 +1,93 @@
class Runtime:
def __init__(self, records={}, parent=None):
self.parent = parent
self.records = records
def run(self, ast):
if ast["type"] == "Program":
return self.program(ast)
def program(self, ast):
return self.block(ast.get("body"))
def block(self, ast):
for statement in ast.get("body"):
s = self.switch(statement)
if s != None:
return s
def switch(self, ast):
t = ast["type"]
if t == "VariableDeclaration":
self.variable_declaration(ast)
if t == "AssignmentExpression":
self.assignment_expression(ast)
if t == "CallExpression":
self.call_function(ast)
if t == "ReturnStatement":
return self.exec_return(ast)
def assignment_expression(self, ast):
id = ast.get("identifier").get("name")
v = ast.get("value")
l = self.literal(v)
self.records[id] = self.unquote(v)
def _is_call_function(self, ast):
return ast["type"] == "CallExpression"
def call_function(self,ast):
id = ast.get("callee").get("name")
args = ast.get("arguments")
unquoted_args = []
for arg in args:
unquoted_args.append(self.unquote(arg))
fu = self.records.get(id)
return fu(*unquoted_args)
def unquote(self, ast):
if self._is_identifier(ast):
return self.records.get(ast.get("name"))
if self._is_literal(ast):
return self.literal(ast)
if self._is_call_function(ast):
return self.call_function(ast)
def variable_declaration(self, ast):
id = ast.get("identifier").get("name")
v = ast.get("value")
if self._is_literal(v):
l = self.literal(v)
if l != None:
self.records[id] = l
else:
raise Exception("Unknown literal type: " + v.get("type"))
if self._is_identifier(v):
self.records[id] = self.records.get(v.get("name"))
def literal(self, ast):
if ast.get("type") == "StringLiteral":
return ast.get("value")
elif ast.get("type") == "NumericLiteral":
return int(ast.get("value"))
elif ast.get("type") == "FloatLiteral":
return float(ast.get("value"))
def _is_identifier(self, ast):
return ast["type"] == "Identifier"
def _is_literal(self, ast):
return ast["type"] in ["NumericLiteral", "StringLiteral", "FloatLiteral"]
def exec_return(self, ast):
v = ast.get("value")
if self._is_literal(v):
return self.literal(v)
if self._is_identifier(v):
return self.records.get(v.get("name"))
if self._is_call_function(v):
return self.call_function(v)
def debug_print_records(self):
print(self.records)

78
runtime/ast/tokenizer.py Normal file
View File

@ -0,0 +1,78 @@
import re
specs = (
# Space:
(re.compile(r"^\s"), None),
# Comments:
(re.compile(r"^//.*"), None),
# Keywords:
(re.compile(r"^\blet\b"), "let"),
(re.compile(r"^\breturn\b"), "return"),
(re.compile(r"^;"), ";"),
# Floats:
(re.compile(r"^[-+]?[0-9]+\.[0-9]+"), "FLOAT"),
# Numbers:
(re.compile(r"^[-+]?[0-9]+"), "NUMBER"),
# Identifiers:
(re.compile(r"^\w+"), "IDENTIFIER"),
# Assignment:
(re.compile(r"^="), "SIMPLE_ASSIGN"),
# Double-quoted strings
(re.compile(r"^\"[^\"]*\""), "STRING"),
# Symbols:
(re.compile(r"^\("), "("),
(re.compile(r"^\)"), ")"),
(re.compile(r"^\,"), ","),
(re.compile(r"^\{"), "{"),
(re.compile(r"^\}"), "}"),
)
class Tokenizer:
def __init__(self):
self.script = ""
self.cursor = 0
def init(self, script: str):
self.script = script
self.cursor = 0
def isEOF(self):
return self.cursor == len(self.script)
def has_more_tokens(self):
return self.cursor < len(self.script)
def get_next_token(self):
if not self.has_more_tokens():
return None
_string = self.script[self.cursor:]
for spec in specs:
tokenValue = self.match(spec[0], _string)
if tokenValue == None:
continue
if (spec[1] == None):
return self.get_next_token()
return {
"type": spec[1],
"value": tokenValue,
}
raise Exception("Unknown token: " + _string[0])
def match(self, reg: re, _script):
matched = reg.search(_script)
if matched == None:
return None
self.cursor += matched.span(0)[1]
return matched[0]

View File

@ -15,7 +15,7 @@ class ASR(Blackbox):
self.paraformer = RapidParaformer(config)
super().__init__(config)
async def processing(self, data: any):
async def processing(self, data: bytes):
results = self.paraformer([BytesIO(data)])
if len(results) == 0:
return None

View File

@ -18,14 +18,14 @@ class Blackbox(ABC):
Output same as above.
"""
@abstractmethod
async def processing(self, data: any) -> any:
async def processing(self, *args, **kwargs) -> any:
pass
"""
valid method should return True if the data is valid and False if the data is invalid
"""
@abstractmethod
def valid(self, data: any) -> bool:
def valid(self, *args, **kwargs) -> bool:
pass
"""

View File

@ -10,7 +10,7 @@ class Calculator(Blackbox):
def valid(self, data: any) -> bool:
return isinstance(data, dict) and "op" in data and "left" in data and "right" in data
def processing(self, data: dict) -> any:
def processing(self, data: dict) -> int | float:
if not self.valid(data):
raise ValueError("Invalid data")
a = data["left"]