~jasper/type_stack_calc

bce4b0492ad30f01e87a366b63b6a45968762fa5 — Jasper den Ouden 2 years ago b68fbc8
Combined insert_code and macros.
M doc/other/type_calc_process.dot => doc/other/type_calc_process.dot +9 -7
@@ 26,8 26,9 @@ Note2: there is a lot of logic about types,\nnot shown here!"
        ufun [ label="user defined\nfunction" tooltip="Arguments and Code object used to make one" ] 
        ifun [ label="inbuilt function\ndefined by language"
               tooltip="some get the raw type stack, which include variable objects not-unpacked. So variables can be set." ]
        ifun -> "quite a bit\nwith types(sets)"
        macro [ label="macro\naccesses\nraw stuff" tooltip="Stuff like if, code-parser. Probably should merge with word editor."]
        ifun -> lot_w_types

        lot_w_types [ label="quite a bit\nwith types(sets)" shape=box ]

        stack [ shape=diamond
                label = "type stack"


@@ 49,17 50,18 @@ Note2: there is a lot of logic about types,\nnot shown here!"
        { component variable } -> actionable

        stack -> actionable [ style=dashed
            xlabel="(input)"
            tooltip="if method, the value the component is from is passed along." ]

        word_editor [ label="inbuilt word editor\n(rarely)"
            tooltip="basically just the components-of-object exposer" ]
        raw_tp_calc [ label="raw\ntype calc"
            tooltip="{} () code-section parser, components-of-object .expose" ]

        actionable [ label="actionable value" tooltip="Have to do more than just put on stack." ]

        actionable -> { ufun ifun word_editor macro }
        word_editor -> words [ taillabel="add/remove\ncoming words" style=dashed ]
        actionable -> { ufun ifun raw_tp_calc }
        raw_tp_calc -> words [ taillabel="add/remove\ncoming words" style=dashed ]

        {ifun ufun_calc} -> stack [ taillabel=result style=dashed ]
        {ifun ufun_calc raw_tp_calc} -> stack [ taillabel=result style=dashed ]

        "function variants" -> ufun_calc [ style=dashed dir=both tooltip="set or get" ]


M test/integermath.py => test/integermath.py +3 -4
@@ 40,7 40,6 @@ from type_stack_calc.sc_parser import parser
from type_stack_calc.intro import intro
from type_stack_calc.ib.scope import scopify

def ignore(_x): pass
vs = scopify(intro)
assert vs.get('Tp')



@@ 49,17 48,17 @@ def is_single(got, x):
    return tuple(got[0][:2]) == (x,x)

for i in range(10):
    got, = StackCalc().tp_calc(parser(f"{i}"), vs, [], ignore)
    got = list(StackCalc().tp_calc(parser(f"{i}"), vs, []))[-1][0]
    assert is_single(got, i), (i, got)

# Introduce this function.
StackCalc().tp_calc(parser("(x) {x x *}.fun sqr.set ;"), vs, [], ignore)
list(StackCalc().tp_calc(parser("(x) {x x *}.fun sqr.set ;"), vs, []))

sym_cnt, run_cnt = 0, 100
for i in range(run_cnt):
    val, s = add_n_stuff()
    sym_cnt += len(s.split())
    got = StackCalc().tp_calc(parser(s), vs, [], ignore)
    got = list(StackCalc().tp_calc(parser(s), vs, []))[-1]
    assert len(got) == 1, (got, s)
    got = got[0]
    assert is_single(got, val), f"Mismatch tc:{got} vs {val}\n{s}"

M type_stack_calc/Main.py => type_stack_calc/Main.py +2 -2
@@ 119,7 119,7 @@ class Main(StackCalc):
            self.want.remove(a[1:])
            self.max_stage_n = max(map(self.want, self.stages.get))
        else:  # An actual input file to deal with, with _current_ setup.
            vs, tc_code = scopify(intro), []
            vs = scopify(intro)

            with open(a) as fd:
                s = fd.read()


@@ 131,7 131,7 @@ class Main(StackCalc):
                code = list(parser(s))
                self.stage('parsed', a, " ".join(code))

                stack = self.tp_calc(code, vs, [], tc_code.append)
                *tc_code, stack = self.tp_calc(code, vs, [])
                tc_code = [c1 for c1 in tc_code if c1 is not None]
                for n, el in enumerate(tc_code):  # Note their existence.
                    if fun := getattr(el, 'notify_real', None): fun()

M type_stack_calc/call/c_fun.py => type_stack_calc/call/c_fun.py +1 -1
@@ 35,7 35,7 @@ class CFunDef:
        assert isinstance(c_name, str), f"Expected string for C function name, got {c_name}\n{inp}"
        assert type(args) != type(inp_tps), "`inp_tps` should be Code sections, presumably it's and Args section."
        # Calculate argument types.
        tps = sc.tp_calc(inp_tps, vs, [], lambda _dontcare:None)
        *_tc_code, tps = sc.tp_calc(inp_tps, vs, [])
        tps = [(t.val if isinstance(t, BaseComponent) else t) for t in tps]
        # TODO check if `tps` is all usable types.


M type_stack_calc/call/if_call.py => type_stack_calc/call/if_call.py +14 -15
@@ 14,9 14,9 @@ from type_stack_calc.tp.bool import TpBool

class IfCall:
    """Code objects have a `.if` are set to this. Produces an IfBlock if needed."""
    mac, n, fun_tp = True, 3, 'method'
    n, fun_tp = 3, 'method'

    def tp_calc(self, sc, _code, vs, stack, into):
    def tp_calc(self, sc, vs, stack):
        assert len(stack) >= 3, \
            f"If needs three blocks, the condition, if-true and if false. Got {stack}"
        then, otherwise, cond = stack[:3]


@@ 30,34 30,33 @@ class IfCall:

        assert not any(wrongs), "If:\n" + "\n".join(wrongs)

        tc_cond = []
        cond_got = sc.tp_calc(cond, vs, [], tc_cond.append)
        *tc_cond, cond_got = sc.tp_calc(cond, vs, [])
        assert len(cond_got) == 1, \
            f"Only one boolean-like value, got: {cond_got}"
        cond_val = cond_got[0]
        if isinstance(cond_val, BaseComponent):  # (not using variables)
            cond_val = cond_val.val
            cond_val = cond_val.val  # TODO can it even happen?

        if isinstance(cond_val, TpIntersection):
            assert len(cond_val) == 1
            cond_val = cond_val[0]
            assert isinstance(cond_val, TpBool), cond_val
            case = cond_val.case
            c_case = cond_val.case

        if isinstance(case, bool):  # Just one branch.
        # TODO ideally stack passed does not need to be empty.
        if isinstance(c_case, bool):  # Just one branch.
            # NOTE: conditions may have destructive statements in there.
            for el in tc_cond:
                if el is not None: into(el)  # Put in the conditions.
            # And put in one of the bodies.
            return sc.tp_calc(then if case else otherwise, vs, [], into),
            *tc_body, stack = sc.tp_calc(then if c_case else otherwise,
                                         vs, [])
            return stack, *tc_cond, *tc_body
        else:
            assert case is None
            tc_t, tc_f = [], []  # Run both sides.
            if_t = sc.tp_calc(then,      vs, [], tc_t.append)
            if_f = sc.tp_calc(otherwise, vs, [], tc_f.append)
            assert c_case is None, "Not definite True/False but not None?"
            *tc_t, if_t = sc.tp_calc(then,      vs, [])
            *tc_f, if_f = sc.tp_calc(otherwise, vs, [])
            assert len(if_f) == len(if_t), \
                "Outputs of `().if` must have same stack size."
            ret_stack = [f.smoosh(t) for f,t in zip(if_t, if_f)]

            return (ret_stack, IfBlock(tc_cond, tc_t, tc_f, ret_stack))
            return ret_stack, IfBlock(tc_cond, tc_t, tc_f, ret_stack)


M type_stack_calc/ib/args.py => type_stack_calc/ib/args.py +4 -4
@@ 14,14 14,14 @@ class Via(tuple):
    """Makes a variable refer to a component by inserting the component-accessing-code."""
    definitional = True

    def insert_code(self, _sc, _code, _vs, stack):
    def raw_tp_calc(self, _sc, _code, _vs, stack):
        return [self[0], '.' + self[1]], stack[:-1]

class ExposeInserts:
class ExposeInsertsCall:
    """Uses `Via` so that variables can access components instead.
Will defect if a temporary variable is needed."""

    def insert_code(self, _code, _sc, vs, stack):
    def raw_tp_calc(self, _sc, _code, vs, stack):
        obj, args = stack[:2]
        if getattr(obj, 'is_var', None):  # Already a variable, fine.
            varname, insert = obj.key, []


@@ 49,7 49,7 @@ class Args(BaseCode):
        """Access parts of the given object."""
        return [obj.get_comp(key) for key in self]

    param_expose = ExposeInserts()  # Exposes components as if variables.
    param_expose = ExposeInsertsCall()  # Exposes components as if variables.

    param_if = IfCall()  # If blocks. (blocks is only option)


M type_stack_calc/ib/loop.py => type_stack_calc/ib/loop.py +1 -3
@@ 42,14 42,12 @@ class LoopCall:  # (used in call)
        assert isinstance(body, list) and body.tp == '{', \
            f"Not block/wrong type for Loop: {body}"

        tc_code = []  # Typecalc it, gather code result.
        prev_vs = dict()
        while True:  # Do it until the type stabilizes.  # TODO will it?
            sc.tp_calc(body, vs, [], tc_code.append)
            *tc_code, _stack = sc.tp_calc(body, vs, [])
            # Exact same condition for all variables.
            if types_eq((e[1] for e in sorted(vs.items(), key=lambda e:e[0])),
                        (e[1] for e in sorted(prev_vs.items(), key=lambda e: e[0]))):
                return ([vs['lr']] if 'lr' in vs else [],
                        LoopBlock(tc_code, vs))
            prev_vs = vs.copy()  # Update types things end up with.
            tc_code.clear()  # Or one will appear for each time.

M type_stack_calc/ib/scope.py => type_stack_calc/ib/scope.py +4 -1
@@ 10,6 10,9 @@ from type_stack_calc.base.named import Named
from type_stack_calc.base.component import BaseComponent, BaseComponentSet
from type_stack_calc.ib.plain_component import PlainComponent 

def do_all(gen):
    for _e in gen: pass

class ScopeVariableSet(BaseComponentSet): pass

# TODO separate: what the variable might be, as set by different places,


@@ 96,7 99,7 @@ class Scope(dict, Named):
    def absorb(self, sc, code):
        """Run code to define constants in the space."""
        assert not self.locked, "Can only absorb one, to extend use derive"
        sc.tp_calc(code, self, [], lambda _x:None)
        do_all(sc.tp_calc(code, self, []))
        self.locked = True  # Cannot change afterwards.

    def get_comp(self, name):  # TODO provide some inbuilds?

M type_stack_calc/ib/tc_variant.py => type_stack_calc/ib/tc_variant.py +6 -8
@@ 98,8 98,6 @@ class TCVariant(Ided, BaseFunctionVariant):
        for arg in self.inp_stack:  # Notify input it was used as argument.
            if fun := getattr(arg, 'notify_arg', None): fun(self.id)

        tc_code = []  # Gather type-calculated code in this.

        # NOTE: it might be unclear what is being iterated, it is (should be) other functions that might call it itself back recurse with.

        try_cnt, tp = 0, []


@@ 110,18 108,19 @@ class TCVariant(Ided, BaseFunctionVariant):
        max_try_cnt = getattr(parent, f"max_try_cnt_{self.code_name}")
        while True:  # Calculate types it until the type stabilizes.
            # Variables, same ach time, other functions is what changes.
            vs = parent.scope.sub(True, zip(args, inp), depth=self.depth + 1)
            vs = parent.scope.sub(True, zip(args, inp),
                                  name=self.parent.name + '_variant',
                                  depth=self.depth + 1)
            vs.get_comp('~r').set(ReturnCatchType(self_id))

            # Type-calculate the code under these conditions & strip nonse.
            ret_stack = stack_calc.tp_calc(code, vs, [], tc_code.append)
            *tc_code, ret_stack = stack_calc.tp_calc(code, vs, [])
            tc_code = [c1 for c1 in tc_code if c1 is not None]

            # If stack left at the end, assume it is returned values.
            if len(ret_stack) > 0:
                extra = []
                stack_calc.tp_calc(['~r', '.set'], vs, ret_stack,
                                   extra.append)
                *extra, _tp = stack_calc.tp_calc(['~r', '.set'], vs,
                                                 ret_stack)
                tc_code = tc_code + [c1 for c1 in extra if c1 is not None]
            assert isinstance(vs['~r'].val, ReturnCatchType), vs['~r']
            tp = vs['~r'].val.ret_stack()   # Fill return type.


@@ 159,7 158,6 @@ class TCVariant(Ided, BaseFunctionVariant):
                    #  won't need it in any case?
                return
            self.unfinished = tp
            tc_code.clear()  # Reset code, or it will appear for each time.

            try_cnt += 1  # TODO log the number of rounds..
            assert try_cnt < max_try_cnt, \

M type_stack_calc/ib/use.py => type_stack_calc/ib/use.py +2 -3
@@ 5,8 5,7 @@ class UseTpCalc:
Arguments for run are (stack_calc, vs, args)"""
    fun_tp = 'method'

    def __init__(self, fun, with_vars=False, mac=False, n=None,
                 name='anUseTpCalc'):
    def __init__(self, fun, with_vars=False, n=None, name='anUseTpCalc'):
        self.tp_calc = fun
        self.with_vars, self.mac, self.n = with_vars, mac, n
        self.with_vars, self.n = with_vars, n
        self.name = name 

M type_stack_calc/intro/load.py => type_stack_calc/intro/load.py +4 -2
@@ 5,7 5,9 @@ from type_stack_calc.sc_parser import parser

from type_stack_calc.intro import intro
from type_stack_calc.ib.scope import scopify
def _ignore(_c1): pass

def do_all(gen):
    for _e in gen: pass

class LoadCall(dict):
    def __repr__(self): return "LoadCall{a dict}}"


@@ 20,7 22,7 @@ class LoadCall(dict):
                p = Path(origin_path + path + ".tsc")
                if p.exists():
                    new_vs = scopify(intro)
                    sc.tp_calc(parser(p.read_text()), new_vs, [], _ignore)
                    do_all(sc.tp_calc(parser(p.read_text()), new_vs, []))
                    got = new_vs
                    break
            assert got is not None, \

M type_stack_calc/intro/parse.py => type_stack_calc/intro/parse.py +3 -3
@@ 15,13 15,13 @@ def code_until_zero(code_iter, open_word, close_word):
class CodeParse(tuple):
    """Object that will parse a list-like code object.
Cuts right into the word stream, which are then not type calculated."""
    mac, n, is_const = True, 0, True
    n, is_const = 0, True
    fun_tp = 'plain'

    def tp_calc(self, _sc, code, _vs, _args, _into):
    def raw_tp_calc(self, _sc, code, _vs, inp):
        cls = self[0]
        obj = cls(code_until_zero(code, cls.tp, cls.close))
        return ([obj],)  # NOTE: code never appears in the tc code.
        return [], inp[:-1] + [obj]

    def __repr__(self): return "CodeParser"


M type_stack_calc/tp_calc.py => type_stack_calc/tp_calc.py +19 -20
@@ 42,7 42,7 @@ class StackCalc:
        self.paths = [] if paths is None else paths
        self.does = set()

    def deal_with_var(self, var, code, vs, stack, into):
    def deal_with_var(self, var, code, vs, stack):
        """Gets variable, applies as function if relevant."""
        val = var.val  # For instance a function immediately called.



@@ 64,9 64,9 @@ class StackCalc:

            if n is not None:
                assert len(inp) == n, \
                    f"Not enough arguments available {len(inp)} vs needed {n}\n  {stack} {inp}"
                    f"Not enough arguments available {len(inp)} vs needed {n}\n  {val}\n\n{stack} {inp}"
            # Figure out the local variables that might be relevant.
            relevant_comp = dict()
            relevant_comp = dict()  # TODO relevant for what.
            for a in inp:
                if isinstance(a, ScopeVariable):
                    relevant_comp[id(a)] = a


@@ 77,13 77,12 @@ class StackCalc:
            # If it won't deal with variables, extract the values for it.
            if not getattr(val, 'with_vars', None):
                inp = [(a.val if isinstance(a, BaseComponent) else a)
                        for a in inp]
                       for a in inp]
                assert not any(isinstance(a, BaseComponent) for a in inp)
            # Actually calculate the type, return new stack and result.
            stack, *tc_code = (
                tp_fun(self, code, vs, inp, into)
                if getattr(val, 'mac', None) else
                tp_fun(self, vs, inp))
            stack, *tc_code = tp_fun(self, vs, inp)
            assert isinstance(stack, list), (val)
            assert_acceptable_vals(tc_code, ".tp_calc output.")

            assert_acceptable_vals(stack, inp, rest, code, var)



@@ 98,9 97,9 @@ class StackCalc:

        return stack[:-1] + [var], var  # No calculation needed.

    special_fun= {'+', '-', '*', '/', '%', '>', '<'}
    special_fun = {'+', '-', '*', '/', '%', '>', '<'}

    def tp_calc(self, code, vs, stack, into):  # (non-babysitting version)
    def tp_calc(self, code, vs, stack):
        """Calculates types and also collects the calls again, with their types, some origins."""
        code, inserted = iter(code), []
        while True:


@@ 109,12 108,13 @@ class StackCalc:
            else:  # Next word from given code.
                word = next(code, None)
                if word is None:
                    return stack
                    yield stack
                    return

            val = interpret_word(word)
            if val is not None:  # It is a constant value.
                stack.append(val)
                into(val)  # Just put it in.
                yield val  # Just put it in.
                continue

            if word[:1] == '.' or word in self.special_fun:  # It's a component.


@@ 132,14 132,13 @@ class StackCalc:
                desc="Unacceptable value after component access.")

            # It is a variable, or accesses something.
            if insert := getattr(got.val, 'insert_code', None):
                assert getattr(got.val, 'tp_calc', None) is None, "BUG: cannot have both `insert_code` and `tp_calc`."
                plus, stack = insert(self, code, vs, stack)
                inserted = plus + inserted
            if raw_fun := getattr(got.val, 'raw_tp_calc', None):
                assert getattr(got.val, 'tp_calc', None) is None, "BUG: cannot have both `raw_tp_calc` and `tp_calc`."
                plus, stack, *tc_code = raw_fun(self, code, vs, stack)
                inserted = plus + inserted  # Can add-in some code.
                yield from tc_code
            else:
                stack, *otp = self.deal_with_var(got, code, vs, stack, into)
                assert_acceptable_vals(otp, "Producing type-calced-code.")
                for el in otp:  # Insert the tc code.
                    into(el)
                stack, *tc_code = self.deal_with_var(got, code, vs, stack)
                yield from tc_code # Insert the tc code.

            assert_acceptable_vals(stack, got, code)