Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request boriel-basic#637 from boriel/feature/refactorize_s…
Browse files Browse the repository at this point in the history
…ymbols_to_use_base_ID

Feature/refactorize symbols to use base
  • Loading branch information
boriel committed Dec 4, 2022
2 parents 5ac5337 + c448014 commit 02081fe
Show file tree
Hide file tree
Showing 85 changed files with 1,480 additions and 1,282 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
branches: [ master ]

env:
PYTHON_VERSION: 3.10.1
PYTHON_VERSION: 3.10.8

jobs:
build:
Expand Down
59 changes: 23 additions & 36 deletions src/api/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

from typing import Dict, Union

import src.symbols as symbols
from src.api import config, errmsg, global_
from src.api.constants import CLASS, SCOPE
from src.symbols import sym as symbols
from src.symbols.type_ import Type

__all__ = [
Expand Down Expand Up @@ -108,14 +108,14 @@ def check_call_arguments(lineno: int, id_: str, args):
entry = global_.SYMBOL_TABLE.get_entry(id_)
named_args: Dict[str, symbols.ARGUMENT] = {}

param_names = set(x.name for x in entry.params)
param_names = set(x.name for x in entry.ref.params)
for arg in args:
if arg.name is not None and arg.name not in param_names:
errmsg.error(lineno, f"Unexpected argument '{arg.name}'", fname=entry.filename)
return False

last_arg_name = None
for arg, param in zip(args, entry.params):
for arg, param in zip(args, entry.ref.params):
if last_arg_name is not None and arg.name is None:
errmsg.error(
lineno, f"Positional argument cannot go after keyword argument '{last_arg_name}'", fname=entry.filename
Expand All @@ -129,8 +129,8 @@ def check_call_arguments(lineno: int, id_: str, args):

named_args[arg.name] = arg

if len(named_args) < len(entry.params): # try filling default params
for param in entry.params:
if len(named_args) < len(entry.ref.params): # try filling default params
for param in entry.ref.params:
if param.name in named_args:
continue
if param.default_value is None:
Expand All @@ -144,14 +144,16 @@ def check_call_arguments(lineno: int, id_: str, args):
errmsg.error(lineno, f"Too many arguments for Function '{id_}'", fname=entry.filename)
return False

if len(named_args) != len(entry.params):
c = "s" if len(entry.params) != 1 else ""
if len(named_args) != len(entry.ref.params):
c = "s" if len(entry.ref.params) != 1 else ""
errmsg.error(
lineno, f"Function '{id_}' takes {len(entry.params)} parameter{c}, not {len(args)}", fname=entry.filename
lineno,
f"Function '{id_}' takes {len(entry.ref.params)} parameter{c}, not {len(args)}",
fname=entry.filename,
)
return False

for param in entry.params:
for param in entry.ref.params:
arg = named_args[param.name]

if arg.class_ in (CLASS.var, CLASS.array) and param.class_ != arg.class_:
Expand All @@ -162,7 +164,7 @@ def check_call_arguments(lineno: int, id_: str, args):
return False

if param.byref:
if not isinstance(arg.value, symbols.VAR):
if not isinstance(arg.value, symbols.ID):
errmsg.error(
lineno, "Expected a variable name, not an expression (parameter By Reference)", fname=arg.filename
)
Expand Down Expand Up @@ -221,15 +223,14 @@ def check_pending_labels(ast):
for x in node.children:
pending.append(x)

if node.token != "VAR" or (node.token == "VAR" and node.class_ is not CLASS.unknown):
if node.token not in ("ID", "LABEL"):
continue

tmp = global_.SYMBOL_TABLE.get_entry(node.name)
if tmp is None or tmp.class_ is CLASS.unknown:
if tmp is None or tmp.class_ == CLASS.unknown:
errmsg.error(node.lineno, f'Undeclared identifier "{node.name}"')
else:
assert tmp.class_ == CLASS.label
node.to_label(node)

result = result and tmp is not None

Expand Down Expand Up @@ -278,7 +279,7 @@ def is_null(*symbols_):


def is_SYMBOL(token, *symbols_):
"""Returns True if ALL of the given argument are AST nodes
"""Returns True if ALL the given argument are AST nodes
of the given token (e.g. 'BINARY')
"""
assert all(isinstance(x, symbols.SYMBOL) for x in symbols_)
Expand All @@ -295,12 +296,12 @@ def is_string(*p):

def is_const(*p):
"""A constant in the program, like CONST a = 5"""
return is_SYMBOL("VAR", *p) and all(x.class_ == CLASS.const for x in p)
return is_SYMBOL("CONST", *p)


def is_CONST(*p):
"""Not to be confused with the above.
Check it's a CONSTant expression
Check it's a CONSTant EXPRession
"""
return is_SYMBOL("CONSTEXPR", *p)

Expand All @@ -313,33 +314,19 @@ def is_static(*p):


def is_number(*p):
"""Returns True if ALL of the arguments are AST nodes
"""Returns True if ALL the arguments are AST nodes
containing NUMBER or numeric CONSTANTS
"""
try:
return all(i.token == "NUMBER" or (i.token == "ID" and i.class_ == CLASS.const) for i in p)
except Exception:
pass

return False
return all(i.token in ("NUMBER", "CONST") for i in p)


def is_var(*p):
"""Returns True if ALL of the arguments are AST nodes
"""Returns True if ALL the arguments are AST nodes
containing ID
"""
return is_SYMBOL("VAR", *p)


def is_integer(*p):
try:
return all(i.is_basic and Type.is_integral(i.type_) for i in p)
except Exception:
pass

return False


def is_unsigned(*p):
"""Returns false unless all types in p are unsigned"""
try:
Expand Down Expand Up @@ -381,7 +368,7 @@ def is_type(type_, *p):


def is_dynamic(*p): # TODO: Explain this better
"""True if all args are dynamic (e.g. Strings, dynamic arrays, etc)
"""True if all args are dynamic (e.g. Strings, dynamic arrays, etc.)
The use a ptr (ref) and it might change during runtime.
"""
try:
Expand All @@ -394,7 +381,7 @@ def is_dynamic(*p): # TODO: Explain this better

def is_callable(*p):
"""True if all the args are functions and / or subroutines"""
return all(isinstance(x, symbols.FUNCTION) for x in p)
return all(x.token == "FUNCTION" for x in p)


def is_block_accessed(block):
Expand All @@ -411,7 +398,7 @@ def is_block_accessed(block):

def is_temporary_value(node) -> bool:
"""Returns if the AST node value is a variable or a temporary copy in the heap."""
return node.token not in ("PARAMDECL", "STRING", "VAR") and node.t[0] not in ("_", "#")
return node.token not in ("STRING", "VAR") and node.t[0] not in ("_", "#")


def common_type(a, b):
Expand Down
16 changes: 16 additions & 0 deletions src/api/dataref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from dataclasses import dataclass
from typing import Any, List

from src.symbols.id_ import SymbolID


@dataclass(frozen=True)
class DataRef:
label: SymbolID
datas: List[Any]

def __post_init__(self):
assert self.label.token == "LABEL"

def __iter__(self):
return (x for x in [self.label, self.datas])
File renamed without changes.
71 changes: 35 additions & 36 deletions src/api/optimize.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import symtable
from typing import NamedTuple, Optional, Set

import src.api.check as chk
import src.api.global_ as gl
import src.api.symboltable
import src.api.symboltable.symboltable
import src.api.utils
from src import symbols
from src.api import errmsg
from src.api.config import OPTIONS
from src.api.constants import CLASS, CONVENTION, SCOPE, TYPE
from src.api.debug import __DEBUG__
from src.api.errmsg import warning_not_used
from src.ast import NodeVisitor
from src.symbols import ref
from src.symbols import sym as symbols


class ToVisit(NamedTuple):
Expand Down Expand Up @@ -85,7 +86,8 @@ def _visit(self, node: ToVisit):
class UnreachableCodeVisitor(UniqueVisitor):
"""Visitor to optimize unreachable code (and prune it)."""

def visit_FUNCTION(self, node: symbols.FUNCTION):
def visit_FUNCTION(self, node: symbols.ID):
assert node._ref
if (
node.class_ == CLASS.function
and node.body.token == "BLOCK"
Expand Down Expand Up @@ -153,12 +155,8 @@ def visit_BLOCK(self, node):
class FunctionGraphVisitor(UniqueVisitor):
"""Mark FUNCALLS"""

def _get_calls_from_children(self, node):
return [
symbol
for symbol in self.filter_inorder(node, lambda x: isinstance(x, (symbols.FUNCCALL, symbols.CALL)))
if not isinstance(symbol, symbols.ARRAYACCESS)
]
def _get_calls_from_children(self, node: symtable.Symbol):
return [symbol for symbol in self.filter_inorder(node, lambda x: x.token in ("CALL", "FUNCCALL"))]

def _set_children_as_accessed(self, node: symbols.SYMBOL):
parent = node.get_parent(symbols.FUNCDECL)
Expand Down Expand Up @@ -281,9 +279,7 @@ def visit_CHR(self, node):
node = yield self.generic_visit(node)

if all(chk.is_static(arg.value) for arg in node.operand):
yield symbols.STRING(
"".join(chr(src.api.utils.get_final_value(x.value) & 0xFF) for x in node.operand), node.lineno
)
yield symbols.STRING("".join(chr(x.value.value & 0xFF) for x in node.operand), node.lineno)
else:
yield node

Expand All @@ -295,12 +291,12 @@ def visit_CONSTEXPR(self, node):

def visit_FUNCCALL(self, node):
node.args = yield self.generic_visit(node.args) # Avoid infinite recursion not visiting node.entry
self._check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(node.entry.params, node.args)
self._check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(node.entry.ref.params, node.args)
yield node

def visit_CALL(self, node):
node.args = yield self.generic_visit(node.args) # Avoid infinite recursion not visiting node.entry
self._check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(node.entry.params, node.args)
self._check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(node.entry.ref.params, node.args)
yield node

def visit_FUNCDECL(self, node):
Expand All @@ -310,7 +306,7 @@ def visit_FUNCDECL(self, node):
return

if self.O_LEVEL > 1 and node.params_size == node.locals_size == 0:
node.entry.convention = CONVENTION.fastcall
node.entry.ref.convention = CONVENTION.fastcall

node.children[1] = yield ToVisit(node.entry)
yield node
Expand All @@ -324,8 +320,8 @@ def visit_LET(self, node):
symbols.CALL(x.entry, x.args, x.lineno, lvalue.filename)
for x in self.filter_inorder(
node.children[1],
lambda x: isinstance(x, symbols.FUNCCALL),
lambda x: not isinstance(x, symbols.FUNCTION),
lambda x: x.token == "FUNCCALL",
lambda x: x.token != "FUNCTION",
)
]
)
Expand All @@ -342,8 +338,8 @@ def visit_LETARRAY(self, node):
symbols.CALL(x.entry, x.args, x.lineno, lvalue.filename)
for x in self.filter_inorder(
node.children[1],
lambda x: isinstance(x, symbols.FUNCCALL),
lambda x: not isinstance(x, symbols.FUNCTION),
lambda x: x.token == "FUNCCALL",
lambda x: x.token != "FUNCTION",
)
]
)
Expand Down Expand Up @@ -457,36 +453,39 @@ def _check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(
if not param.byref or param.class_ != CLASS.array:
continue

if arg.value.lbound_used and arg.value.ubound_used:
if arg.value.ref.lbound_used and arg.value.ref.ubound_used:
continue

self._update_bound_status(arg.value)

def _update_bound_status(self, arg: symbols.VARARRAY):
old_lbound_used = arg.lbound_used
old_ubound_used = arg.ubound_used
def _update_bound_status(self, arg: symbols.ID):
assert arg.token == "VARARRAY"
arg_ref = arg.ref
assert isinstance(arg_ref, ref.ArrayRef)
old_lbound_used = arg_ref.lbound_used
old_ubound_used = arg_ref.ubound_used

for p in arg.requires:
arg.lbound_used = arg.lbound_used or p.lbound_used
arg.ubound_used = arg.ubound_used or p.ubound_used
arg_ref.lbound_used = arg_ref.lbound_used or p.ref.lbound_used
arg_ref.ubound_used = arg_ref.ubound_used or p.ref.ubound_used

if old_lbound_used != arg.lbound_used or old_ubound_used != arg.ubound_used:
if old_lbound_used != arg_ref.lbound_used or old_ubound_used != arg_ref.ubound_used:
if arg.scope == SCOPE.global_:
return

if arg.scope == SCOPE.local and not arg.byref:
arg.scopeRef.owner.locals_size = src.api.symboltable.symboltable.SymbolTable.compute_offsets(
arg.scopeRef
if arg.scope == SCOPE.local and not arg_ref.byref:
arg.scope_ref.owner.locals_size = src.api.symboltable.symboltable.SymbolTable.compute_offsets(
arg.scope_ref
)


class VarDependency(NamedTuple):
parent: symbols.VAR
dependency: symbols.VAR
parent: symbols.ID
dependency: symbols.ID


class VariableVisitor(GenericVisitor):
_original_variable: Optional[symbols.VAR] = None
_original_variable: Optional[symbols.ID] = None
_parent_variable = None
_visited: Set[symbols.SYMBOL] = set()

Expand All @@ -510,19 +509,19 @@ def has_circular_dependency(self, var_dependency: VarDependency) -> bool:

return False

def get_var_dependencies(self, var_entry: symbols.VAR):
visited: Set[symbols.VAR] = set()
def get_var_dependencies(self, var_entry: symbols.ID):
visited: Set[symbols.ID] = set()
result = set()

def visit_var(entry):
if entry in visited:
return

visited.add(entry)
if not isinstance(entry, symbols.VAR):
if entry.token != "VAR":
for child in entry.children:
visit_var(child)
if isinstance(child, symbols.VAR):
if child.token in ("FUNCTION", "LABEL", "VAR", "VARARRAY"):
result.add(VarDependency(parent=VariableVisitor._parent_variable, dependency=child))
return

Expand Down
2 changes: 1 addition & 1 deletion src/api/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import json
from typing import Any, Dict, List

from src.api.errors import Error
from src.api.exception import Error

__all__ = ["Option", "Options", "ANYTYPE", "Action"]

Expand Down

0 comments on commit 02081fe

Please sign in to comment.