~jasper/type_stack_calc

ref: 6aca66fb1a7cf1e10f7dd3263f04537c8ff37590 type_stack_calc/type_stack_calc/tp_calc.py -rw-r--r-- 5.6 KiB
6aca66fb — Jasper den Ouden Apparently `to_c` can already read ahead one, hopefully its this simple... 11 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#  Copyright (C) 2021 Jasper den Ouden.
#
#  This is free software: you can redistribute it and/or modify
#  it under the terms of the Affero GNU General Public License as published
#  by the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.

from type_stack_calc.base.component import BaseComponent
from type_stack_calc.ib.scope import ScopeVariable

from type_stack_calc.tp.other_sets import Range
from type_stack_calc.tp.intersection import TpIntersection
from type_stack_calc.tp.str import ConstStr
import re

from type_stack_calc.util.stack_check import assert_acceptable_vals

def interpret_num_word(word):
    if re.match("-?\d+", word):  # Plain integer.
        return int(word)
    elif re.match("0x[abcdef\d]+", word):  # Hexadecimal.
        return int(word[2:], 16)
    elif re.match("\d+\.\d*", word): # Floating point number.
        return float(word)
    assert word not in "0123456789"

def interpret_word(word):
    """Interprets constant values."""
    x = interpret_num_word(word)
    if x is not None:
        # TODO for integers might want to indicate that.
        return TpIntersection((Range(x, x),))
    elif word[0] == '"':  # Constant string.
        return ConstStr(word[1:])

class StackCalc:
    """Calculates types, heart of the implementation, basically."""

    def __init__(self, origin=None):
        self.origin = origin 
        self.does = set()

    def deal_with_var(self, var, code, vs, stack, into):
        """Gets variable, applies as function if relevant."""
        val = var.val  # For instance a function immediately called.
        # If it has a type function, run it.
        if tp_fun := val and getattr(val, 'tp_calc', None):
            if val.fun_tp == 'plain':
                stack = stack[:-1]  # Ditch the origin.
#            elif val.fun_tp == 'class_method':
#                assert isinstance(stack[-1], Scope)
            else:
                assert val.fun_tp == 'method'

            # Strip to number of arguments if `n` indicates that.
            n = getattr(val, 'n', None)
            args, rest = (stack, []) if (n is None) else \
                (([], stack) if n==0 else (stack[-n:], stack[:-n]))

            if n is not None:
                assert len(args) == n, (stack, n, stack[-n:], args)
            # Figure out the local variables that might be relevant.
            relevant_comp = dict()
            for a in args:
                if isinstance(a, ScopeVariable):
                    relevant_comp[id(a)] = a
                if rcd := getattr(a, 'relevant_comp', None):
                    # Add relevant for that.
                    relevant_comp.update(rcd)

            # If it won't deal with variables, extract the values for it.
            if not getattr(val, 'with_vars', None):
                args = [(a.val if isinstance(a, BaseComponent) else a)
                        for a in args]
                assert not any(isinstance(a, BaseComponent) for a in args)
            # Actually calculate the type, return new stack and result.
            stack, *tc_code = (
                tp_fun(self, code, vs, args, into)
                if getattr(val, 'mac', None) else
                tp_fun(self, vs, args))

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

            if fun := getattr(val, 'deal_relevant_comp', None):
                fun(relevant_comp, args)
            for el in stack:  # Remember all the relevant local variables.
                if fun := getattr(el, 'deal_relevant_comp', None):
                    fun(relevant_comp, args)
                el.relevant_comp = relevant_comp

            return (rest + stack, *tc_code)

        assert_acceptable_vals(stack, val)

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

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

    def tp_calc(self, code, vs, stack, into):  # (non-babysitting version)
        """Calculates types and also collects the calls again, with their types, some origins."""
        code, inserted = iter(code), []
        while True:
            if len(inserted) > 0:  # Next word from inserts.
                word, inserted = inserted[0], inserted[1:]
            else:  # Next word from given code.
                word = next(code, None)
                if word is None:
                    return stack

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

            if word[:1] == '.' or word in self.special_fun:  # It's a component.
                assert len(stack) >= 1
                assert_acceptable_vals((stack[-1],), stack)

                got = stack[-1].get_comp(word[1:] if word[:1]=='.' else word)
            elif word == '\n':  # Newlines currently ignored.
                continue
            else:   # General variable.
                got = vs.get_comp(word)
                stack.append(vs)

            assert_acceptable_vals((stack[-1],), stack,
                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
                plus, stack = insert(self, code, vs, stack)
                inserted = plus + inserted
            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)
            assert_acceptable_vals(stack, got, code)