rising-stretch 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ # Created by venv; see https://docs.python.org/3/library/venv.html
2
+ *
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: rising-stretch
3
+ Version: 0.0.3
4
+ Summary: a custom interpreter for a modular/extendable super simple programming language called stretch
5
+ Author-email: rising <risingwanabe@gmail.com>
6
+ Maintainer-email: rising <risingwanabe@gmail.com>
7
+ Requires-Dist: rising-garnish
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+
6
+ [project]
7
+ name = "rising-stretch"
8
+ version = "0.0.3"
9
+ description = "a custom interpreter for a modular/extendable super simple programming language called stretch"
10
+ authors = [
11
+ { name = "rising", email = "risingwanabe@gmail.com" },
12
+ ]
13
+ maintainers = [
14
+ { name = "rising", email = "risingwanabe@gmail.com" },
15
+ ]
16
+ dependencies = ["rising-garnish"]
17
+
18
+
19
+ [tool.hatch.envs.default]
20
+ dependencies = [ "rising-garnish", ]
21
+
22
+ [tool.hatch.build]
23
+ include = ["stretch"]
24
+
25
+ [project.scripts]
26
+ stretch = "stretch.command:run"
@@ -0,0 +1,10 @@
1
+ from .interpreter import CoreProcessor
2
+ from .scope_types import Module, Scope
3
+ from .core_types import DotPath, RawToken, Alias, StretchSkipline, StretchTerminate, Stack
4
+ import sys
5
+
6
+ def run_stretch(dot_path):
7
+ processor = CoreProcessor()
8
+ module = Module(processor, DotPath(processor, dot_path))
9
+ processor.run_scope(module)
10
+
@@ -0,0 +1,11 @@
1
+ import importlib
2
+
3
+ imported = {}
4
+
5
+ def __getattr__(extension_name):
6
+ if extension_name in imported:
7
+ extension = imported[extension_name]
8
+ else:
9
+ extension = importlib.import_module("." + extension_name, 'stretch.builtin_extensions')
10
+ imported[extension_name] = extension
11
+ return extension
@@ -0,0 +1,20 @@
1
+
2
+ from stretch.extension import Extender
3
+
4
+ __extensions__ = Extender()
5
+ with __extensions__ as extend:
6
+ @extend.term.use("in")
7
+ def get_input(proc, scope, tokens):
8
+ message = ""
9
+ if len(tokens) > 0:
10
+ message = tokens.pop(0)
11
+ if message != "":
12
+ message += "\n"
13
+ tokens.insert(0, input(message))
14
+ return tokens
15
+ @extend.term.use("int_in")
16
+ def get_int_input(proc, scope, tokens):
17
+ tokens = get_input(proc, scope, tokens)
18
+ tokens.insert(0, int(tokens.pop(0)))
19
+ return tokens
20
+
@@ -0,0 +1,14 @@
1
+ from stretch.extension import Extender
2
+
3
+ __extensions__ = Extender()
4
+ with __extensions__ as extend:
5
+
6
+
7
+
8
+ @extend.operator.use("*", "/")
9
+ def __mul__(proc, scope, l, r):
10
+ return l * r
11
+
12
+ @extend.operator.use("/", "+")
13
+ def __div__(proc, scope, l, r):
14
+ return l / r
@@ -0,0 +1,9 @@
1
+ from . import run_stretch
2
+ import sys
3
+
4
+
5
+ def run():
6
+ if len(sys.argv) > 1:
7
+ run_stretch(sys.argv[1])
8
+ else:
9
+ print("Please enter the name of a stretch file to interpret it.")
@@ -0,0 +1,193 @@
1
+ from garnish import garnish
2
+ from .core_types import DotPath, RawToken, StretchTerminate, Alias, StretchSkipline
3
+ from .scope_types import Scope, Module
4
+ import importlib
5
+ from .precedence import PrecedenceGraph
6
+ from . import builtin_extensions
7
+
8
+ __operators__ = {}
9
+ __operator_precedence__ = PrecedenceGraph()
10
+ __terms__ = {}
11
+ @garnish
12
+ def operator(func, symbol:str, low=None, high=None):
13
+ if low is not None:
14
+ low = RawToken(low)
15
+ if high is not None:
16
+ high = RawToken(high)
17
+ __operators__[RawToken(symbol)] = func
18
+ __operator_precedence__.add(RawToken(symbol), low, high)
19
+ return func
20
+ @garnish
21
+ def term(func, term:str):
22
+ __terms__[term] = func
23
+ return func
24
+
25
+
26
+ #OPERATORS
27
+ @operator.use('.')
28
+ def chain(proc, scope, l, r):
29
+ if isinstance(l, RawToken):
30
+ l = DotPath(l, scope=scope)
31
+ l.extend(r, scope=scope)
32
+ return l
33
+
34
+ @operator.use('+', "-")
35
+ def add(proc, scope, l, r):
36
+ return l + r
37
+
38
+ @operator.use('-', "==")
39
+ def subtract(proc, scope, l, r):
40
+ return l - r
41
+
42
+ @operator.use('==')
43
+ def is_eq(proc, scope, l, r):
44
+ return l == r
45
+ @operator.use('!=')
46
+ def not_eq(proc, scope, l, r):
47
+ return l != r
48
+ @operator.use('>')
49
+ def is_gt(proc, scope, l, r):
50
+ return l > r
51
+ @operator.use('<')
52
+ def is_lt(proc, scope, l, r):
53
+ return l < r
54
+
55
+
56
+ #TERMS
57
+ # error handling
58
+
59
+ @term.use("shutdown")
60
+ def shutdown(proc, scope, tokens):
61
+ proc.running = False
62
+ if len(tokens) > 0:
63
+ raise StretchTerminate(tokens.pop())
64
+ raise StretchTerminate()
65
+
66
+ @term.use("raise")
67
+ def error(proc, scope, tokens):
68
+ message = "error with no defined message"
69
+ if len(tokens) > 0:
70
+ message = tokens.pop(0)
71
+ message = f"[Line:'{scope.current_line}' in '{scope.name}'] {message}"
72
+ shutdown(proc, scope, [message])
73
+
74
+ def expects(proc, scope, owner:str, tokens, type = None):
75
+ if not len(tokens) > 0:
76
+ error(proc, scope, [f"{owner} expected token, recieved nothing"])
77
+ elif type is not None and not isinstance(tokens[0], type):
78
+ error(proc, scope, [f"{owner} expected {type.__name__} in tokens, recieved {tokens}"])
79
+
80
+
81
+ @term.use('as')
82
+ def alias(proc, scope, tokens):
83
+ expects(proc, scope, "alias", tokens, RawToken)
84
+ return Alias(tokens.pop())
85
+
86
+ @term.use("Stack")
87
+ def get_stack(proc, scope, tokens):
88
+ tokens.insert(0, proc.stack())
89
+ return tokens
90
+
91
+ @term.use("print")
92
+ def _print(proc, scope, tokens):
93
+ print(*tokens)
94
+ return tokens
95
+
96
+ # control scope
97
+
98
+ @term.use("import")
99
+ def import_module(proc, scope, tokens):
100
+ if len(tokens) > 0:
101
+ if isinstance(tokens[0], DotPath):
102
+ dotpath = tokens.pop(0)
103
+ elif isinstance(tokens[0], RawToken):
104
+ dotpath = DotPath(tokens.pop(0), scope)
105
+ else:
106
+ error(proc, scope, [f"import expected DotPath or Alias to module, recieved {type(tokens[0]).__name__}: '{tokens[0]}'"])
107
+ module = Module(proc, dotpath)
108
+ proc.init_scope(module)
109
+ if isinstance(tokens[0], Alias):
110
+ name = tokens.pop(0).name
111
+ scope.store(name, module)
112
+ tokens.insert(0, module)
113
+ return tokens
114
+ else:
115
+ error(proc, scope, [f"import expected DotPath to module, recieved nothing"])
116
+
117
+ @term.use("extend")
118
+ def import_extension(proc, scope, tokens):
119
+ expects(proc, scope, "extend", tokens, (DotPath, RawToken))
120
+ dotpath = tokens.pop(0)
121
+ if isinstance(dotpath, RawToken):
122
+ dotpath = DotPath(dotpath, scope)
123
+ name = dotpath.file_name()
124
+ if name.startswith('@'):
125
+ extension = getattr(builtin_extensions, name[1:])
126
+ else:
127
+ extension = __import__(name)
128
+ if hasattr(extension, '__extensions__'):
129
+ extension.__extensions__.extend(proc, scope)
130
+ return tokens
131
+
132
+ @term.use("enter")
133
+ def enter_scope(proc, scope, tokens):
134
+ expects(proc, scope, "enter", tokens, Module)
135
+ into = tokens.pop(0)
136
+ line = 0
137
+ if len(tokens) > 0 and isinstance(tokens[0], int):
138
+ line = tokens.pop(0) - 1
139
+ if into is scope:
140
+ scope.go_to(line)
141
+ raise StretchSkipline()
142
+
143
+ return proc.process_scope(into, line)
144
+
145
+ # control-flow
146
+
147
+ @term.use("Line")
148
+ def get_line(proc, scope, tokens):
149
+ return scope.current_line + 1
150
+
151
+ @term.use("True")
152
+ def ret_true(proc, scope, tokens):
153
+ tokens.insert(0, True)
154
+ return tokens
155
+
156
+ @term.use("False")
157
+ def ret_false(proc, scope, tokens):
158
+ tokens.insert(0, False)
159
+ return tokens
160
+
161
+ @term.use("if")
162
+ def if_stat(proc, scope, tokens):
163
+ expects(proc, scope, "if", tokens, bool)
164
+ if not tokens.pop(0):
165
+ raise StretchSkipline()
166
+ @term.use("unless")
167
+ def unless_stat(proc, scope, tokens):
168
+ expects(proc, scope, "unless", tokens, bool)
169
+ if tokens.pop(0):
170
+ raise StretchSkipline()
171
+
172
+ @term.use("goto")
173
+ def goto(proc, scope, tokens):
174
+ expects(proc, scope, "goto", tokens, int)
175
+
176
+ line = scope.current_line + 1
177
+ scope.current_line = tokens.pop() - 1
178
+ tokens.insert(0, line)
179
+ return tokens
180
+
181
+ @term.use("Scope")
182
+ def get_scope(proc, scope, tokens):
183
+ tokens.insert(0, scope)
184
+ return tokens
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
@@ -0,0 +1,159 @@
1
+ __types__ = {}
2
+ from garnish import garnish
3
+ @garnish
4
+ def add_type(type, determine=None):
5
+ if determine is None:
6
+ determine = type.__determine__
7
+ __types__[determine] = type
8
+
9
+ class StretchTerminate(Exception): pass
10
+ class StretchSkipline(Exception): pass
11
+
12
+ class Stack(list):
13
+ __slots__ = ("single_layer")
14
+ class StackError(StretchTerminate): pass
15
+ def __init__(self, *iterable, single_layer=False):
16
+ if single_layer:
17
+ if any([isinstance(obj, Stack) for obj in iterable]):
18
+ raise Stack.StackError(f"Cannot move vertically in a single layer stack")
19
+ self.single_layer = single_layer
20
+ super().__init__(iterable)
21
+ def level(self, index = 1):
22
+ if self.single_layer:
23
+ if index != 1:
24
+ raise Stack.StackError(f"Cannot move vertically in a single layer stack")
25
+ return self
26
+ current_stack = self
27
+ stacks = [current_stack]
28
+ while len(current_stack) > 0:
29
+ if isinstance(current_stack[-1], Stack):
30
+ current_stack = current_stack[-1]
31
+ stacks.append(current_stack)
32
+ else:
33
+ break
34
+ return stacks[-index]
35
+
36
+ def up(self, levels:int=1):
37
+ if self.single_layer:
38
+ raise Stack.StackError(f"Cannot move vertically in a single layer stack")
39
+ for _ in range(levels):
40
+ self.level().push(Stack())
41
+ def down(self, levels:int = 1):
42
+ if self.single_layer:
43
+ raise Stack.StackError(f"Cannot move vertically in a single layer stack")
44
+ return self.level(1 + levels).pop()
45
+
46
+ def push(self, value):
47
+ if self.single_layer:
48
+ if isinstance(value, Stack):
49
+ raise Stack.StackError(f"Cannot move vertically in a single layer stack")
50
+ self.level().append(value)
51
+ def pull(self, index=1):
52
+ return self.level().pop(-index)
53
+ def peek(self, index=1):
54
+ return self.level()[-index]
55
+ def empty(self, level:int=1):
56
+ self.level(level).clear()
57
+ def copy(self, index = 1):
58
+ self.push(self.peek(index))
59
+ def surface(self, index):
60
+ self.push(self.pull(index))
61
+
62
+ def extend(self, ls):
63
+ list.extend(self.level(), ls)
64
+
65
+ def take(self, *types):
66
+ taking = []
67
+ for type in types:
68
+ if len(self.level(1)) < 1:
69
+ sorted = None
70
+ if len(taking) > 0:
71
+ sorted = ', '.join([obj.__class__.__name__ + ":" + f"'{str(obj)}'" for obj in taking])
72
+ raise Stack.StackError(f"stack.take() expected ({", ".join([type.__name__ for type in types])}), recieved {{{sorted or ""}, '...'}} ")
73
+ if not isinstance(self.peek(), type):
74
+ sorted = None
75
+ if len(taking) > 0:
76
+ sorted = ', '.join([obj.__class__.__name__ + ":" + f"'{str(obj)}'" for obj in taking])
77
+ fin_val = self.peek(2)
78
+ final = fin_val.__class__.__name__ + ":" + f"'{str(fin_val)}'"
79
+ if sorted is not None:
80
+ final = ", " + final
81
+ raise Stack.StackError(f"stack.take() expected ({", ".join([type.__name__ for type in types])}), recieved {{{(sorted or "") + final}}} ")
82
+ taking.append(self.pull())
83
+ return taking
84
+
85
+ def take_if(self, *types):
86
+ taking = []
87
+ for type in types:
88
+ if len(self.level()) < 1:
89
+ return taking
90
+ if not isinstance(self.peek(), type):
91
+ return taking
92
+ taking.append(self.pull())
93
+ return taking
94
+
95
+ class RawToken:
96
+ __slots__ = ("literal",)
97
+ __raw__ = {}
98
+
99
+ def __new__(cls, literal:str, *args):
100
+ if literal in cls.__raw__:
101
+ instance = cls.__raw__[literal]
102
+ else:
103
+ instance = super().__new__(cls)
104
+ instance.literal = literal
105
+ cls.__raw__[literal] = instance
106
+ return instance
107
+
108
+ def __hash__(self):
109
+ return hash((RawToken, self.literal))
110
+
111
+ def __eq__(self, other):
112
+ if isinstance(other, RawToken):
113
+ return self.literal == other.literal
114
+ elif isinstance(other, str):
115
+ return False
116
+ return False
117
+
118
+ def __repr__(self):
119
+ return f"(Raw:'{self.literal}')"
120
+
121
+ class DotPath:
122
+ __slots__ = ("chain")
123
+ def __init__(self, *chain, scope=None):
124
+ self.chain = []
125
+ for segment in chain:
126
+ if isinstance(segment, str):
127
+ for str_seg in segment.strip().split('.'):
128
+ if scope is not None:
129
+ self.chain.append(scope.parse_raw(RawToken(str_seg)))
130
+ else:
131
+ self.chain.append(RawToken(str_seg))
132
+ elif isinstance(segment, RawToken):
133
+ self.chain.append(segment)
134
+
135
+ def __repr__(self):
136
+ return f"<{'.'.join([str(seg) if not isinstance(seg, RawToken) else seg.literal for seg in self.chain])}>"
137
+
138
+ def extend(self, *segments, scope=None):
139
+ for segment in segments:
140
+ if isinstance(segment, str):
141
+ for str_seg in segment.strip().split('.'):
142
+ if scope is not None:
143
+ self.chain.append(scope.parse_raw(RawToken(str_seg)))
144
+ else:
145
+ self.chain.append(RawToken(str_seg))
146
+ elif isinstance(segment, RawToken):
147
+ self.chain.append(segment)
148
+
149
+ def file_name(self, ext:str=None) -> str:
150
+ if ext is not None: ext = '.' + ext
151
+ return "/".join([name.literal for name in self.chain]) + (ext or "")
152
+ def end(self) -> RawToken:
153
+ return RawToken(self.chain[-1])
154
+
155
+ class Alias:
156
+ __slots__ = ("name")
157
+
158
+ def __init__(self, name):
159
+ self.name = name.literal
@@ -0,0 +1,51 @@
1
+ from garnish import garnish
2
+ from .core import __operators__, __terms__, __operator_precedence__
3
+ from .core_types import RawToken
4
+
5
+ class Extender:
6
+ def __init__(self):
7
+ self.__operators__ = __operators__
8
+ self.__operator_precedence__ = __operator_precedence__.copy()
9
+ self.__terms__ = __terms__
10
+ self.__types__ = {}
11
+
12
+ self.__direct__ = []
13
+
14
+
15
+ def extend(self, proc, scope):
16
+ for operator in self.__operator_precedence__.sort():
17
+ scope.__operators__[operator] = self.__operators__[operator]
18
+ scope.__terms__.update(self.__terms__)
19
+ scope.__types__.update(self.__types__)
20
+
21
+ for func in self.__direct__:
22
+ func(proc, scope)
23
+
24
+
25
+ def __enter__(self):
26
+ return self
27
+ def __exit__(self, *exc_args): pass
28
+
29
+ def __call__(self, func):
30
+ self.__direct__.append(func)
31
+
32
+ @garnish
33
+ def operator(self, func, symbol:str, low=None, high=None):
34
+ if low is not None:
35
+ low = RawToken(low)
36
+ if high is not None:
37
+ high = RawToken(high)
38
+ self.__operators__[RawToken(symbol)] = func
39
+ self.__operator_precedence__.add(RawToken(symbol), low, high)
40
+ return func
41
+
42
+ @garnish
43
+ def term(self, func, term:str):
44
+ self.__terms__[RawToken(term)] = func
45
+ return func
46
+
47
+ @garnish
48
+ def type(self, cls):
49
+ pass
50
+
51
+
@@ -0,0 +1,164 @@
1
+ from .core import __operators__ as core_operators, __terms__ as core_terms
2
+ from .core_types import Stack, DotPath, RawToken, StretchTerminate, StretchSkipline
3
+ from .scope_types import Scope, Module
4
+ from typing import Generator
5
+
6
+ class CoreProcessor:
7
+ def __init__(self):
8
+ self.__modules__ = {}
9
+ self.__stack__ = Stack()
10
+ self.running = True
11
+
12
+ self.__operators__ = core_operators
13
+ self.__terms__ = core_terms
14
+ self.__types__ = {}
15
+
16
+ def add_module(self, module:Module):
17
+ if not isinstance(module, Module):
18
+ raise TypeError(f"processor.add_module() expected a module, not {module.__class__.__name__} '{module}'")
19
+ self.__modules__[module.name] = module
20
+
21
+ def modules(self, name=None):
22
+ if name is None:
23
+ return self.__modules__
24
+ return self.__modules__[name]
25
+
26
+ # STACK
27
+ def stack(self):
28
+ return self.__stack__
29
+
30
+ def process_line(self, scope:Scope, line):
31
+ try:
32
+ tokens = Stack(self.tokenize_line(scope, line))
33
+
34
+ self.apply_operators(scope, scope.operators(), tokens) # apply scope / module level extension operators first
35
+ self.apply_operators(scope, self.__operators__, tokens) # apply builtin operators
36
+
37
+ self.apply_terms(scope, scope.terms(), tokens)
38
+ self.apply_terms(scope, self.__terms__, tokens)
39
+
40
+ self.save_vars(scope, tokens)
41
+ except StretchSkipline:
42
+ pass
43
+
44
+ def apply_operators(self, scope, operators, tokens):
45
+ for operator in operators:
46
+ while operator in tokens:
47
+ index = tokens.index(operator) - 1
48
+ l = tokens.pop(index)
49
+ _ = tokens.pop(index)
50
+ r = tokens.pop(index)
51
+
52
+ result = operators[operator](self, scope, l, r) # an operator operates on the tokens to its left and to its right, and has access to the processor
53
+ if result is not None:
54
+ tokens.insert(index, result)
55
+ def apply_terms(self, scope, terms, tokens): # terms don't have precedence, they are applied right to left on the tokens to their right
56
+ index = len(tokens) - 1
57
+ while index >= 0:
58
+ token = tokens[index]
59
+ if isinstance(token, RawToken) and token.literal in terms:
60
+ term_name = token.literal
61
+ tokens.pop(index) # remove term
62
+ r_toks = tokens[index:] # get the terms to the right
63
+ del tokens[index:] # remove them from the main list
64
+ # apply term
65
+ returned = terms[term_name](self, scope, r_toks)
66
+ if returned is None:
67
+ returned = []
68
+ elif not isinstance(returned, (list, tuple, Generator)):
69
+ returned = [returned] # append returned tokens
70
+ tokens.extend(returned)
71
+ index -= 1
72
+
73
+ def save_vars(self, scope, tokens):
74
+ index = len(tokens) - 1
75
+ while index >= 0:
76
+
77
+ token = tokens[index]
78
+
79
+ if isinstance(token, RawToken):
80
+ if token.literal.endswith(":"):
81
+ name = RawToken(token.literal[:-1])
82
+ tokens.pop(index)
83
+ if index > len(tokens):
84
+ raise StretchTerminate(f"attempt to store {name} recieved no value")
85
+ value = tokens.pop(index)
86
+ scope.store(name, value)
87
+
88
+ index -= 1
89
+
90
+
91
+ def tokenize_line(self, scope, line):
92
+ tokens = []
93
+ tok_str = []
94
+ start = None
95
+ for chunk in line.split():
96
+
97
+ if start is None:
98
+ if chunk.startswith('"') and not chunk.endswith('"'):
99
+ start = '"'
100
+ if start is not None:
101
+ tok_str.append(chunk)
102
+ if chunk.endswith(start):
103
+ tokens.append(" ".join(tok_str)[1:-1])
104
+ start = None
105
+ else:
106
+ for token in self.tokenize(chunk):
107
+ tokens.append(self.parse_token(scope, token))
108
+ return tokens
109
+
110
+ def tokenize(self, chunk):
111
+ tokens = []
112
+ for tok in chunk.split('.'):
113
+ tokens.append(tok)
114
+ tokens.append('.')
115
+ else:
116
+ tokens.pop()
117
+ for token in tokens:
118
+ yield token
119
+
120
+ def parse_token(self, scope, token:str):
121
+ match token:
122
+ case token if token.isdigit():
123
+ return int(token)
124
+ case token if token.startswith('-') and token[1:].isdigit():
125
+ return int(token)
126
+ case token if token.startswith('"') and token.endswith('"'):
127
+ return token.strip('"')
128
+ case token if token.startswith("'") and token.endswith("'"):
129
+ return token.strip("'")
130
+ case token:
131
+ raw_token = RawToken(token)
132
+ if scope.contains(raw_token):
133
+
134
+ return scope.retrieve(raw_token)
135
+ return raw_token
136
+
137
+ def run_scope(self, scope:Scope):
138
+ self.init_scope(scope)
139
+ self.process_scope(scope)
140
+
141
+ def init_scope(self, scope:Scope):
142
+ scope.go_to(0)
143
+
144
+ while scope.current_line < len(scope.__lines__):
145
+ if not self.running:
146
+ break
147
+ line = scope.__lines__[scope.current_line]
148
+ if line.startswith("$"):
149
+ self.process_line(scope, line[1:])
150
+ scope.current_line += 1
151
+
152
+ def process_scope(self, scope:Scope, start_line=0):
153
+
154
+ scope.go_to(start_line)
155
+
156
+ while scope.current_line < len(scope.__lines__):
157
+ if not self.running:
158
+ break
159
+ line = scope.__lines__[scope.current_line]
160
+ if not line.startswith("$"):
161
+ self.process_line(scope, line)
162
+ scope.current_line += 1
163
+
164
+
@@ -0,0 +1,64 @@
1
+ class PrecedenceGraph:
2
+ def __init__(self):
3
+ self.lower = {}
4
+ self.higher = {}
5
+ self.nodes = []
6
+
7
+ def add_operator(self, op):
8
+ if op not in self.nodes:
9
+ self.nodes.append(op)
10
+
11
+ def add(self, operator, low=None, high=None):
12
+ self.add_operator(operator)
13
+ if low is not None:
14
+ self.add_sub(operator, low)
15
+ if high is not None:
16
+ self.add_dom(operator, high)
17
+
18
+ def add_dom(self, low, high):
19
+ self.add_operator(low)
20
+ self.add_operator(high)
21
+
22
+ self.higher.setdefault(low, set()).add(high)
23
+ self.lower.setdefault(high, set()).add(low)
24
+
25
+ def add_sub(self, high, low):
26
+ self.add_dom(low, high)
27
+
28
+ def sort(self):
29
+ order = []
30
+ for op in self.nodes:
31
+ # find earliest valid insertion point
32
+ pos = 0
33
+ while pos < len(order):
34
+ before = order[:pos]
35
+ after = order[pos:]
36
+ # must come after all lower nodes
37
+ if any(l in after for l in self.lower.get(op, [])):
38
+ pos += 1
39
+ continue
40
+ # must come before all higher nodes
41
+ if any(h in before for h in self.higher.get(op, [])):
42
+ pos += 1
43
+ continue
44
+ break
45
+ order.insert(pos, op)
46
+ return order
47
+
48
+ def update(self, other): # Add all nodes
49
+ for op in other.nodes:
50
+ self.add_operator(op) # Add all dominance (low < high) relationships
51
+ for low, highs in other.higher.items():
52
+ for high in highs:
53
+ self.add_dom(low, high)
54
+ for high, lows in other.lower.items():
55
+ for low in lows:
56
+ self.add_sub(high, low)
57
+
58
+ def copy(self):
59
+ instance = PrecedenceGraph()
60
+ instance.nodes = self.nodes[:]
61
+ instance.lower = {symbol: set(lower) for symbol, lower in self.lower.items()}
62
+ instance.higher = {symbol: set(higher) for symbol, higher in self.higher.items()}
63
+ return instance
64
+
@@ -0,0 +1,91 @@
1
+
2
+ type DotPath = None #alias
3
+
4
+ class Scope:
5
+ def __init__(self, processor, name, parent):
6
+ self.processor = processor
7
+ self.name = name
8
+ self.parent = parent
9
+
10
+ self.__lines__ = []
11
+ self.current_line = 0
12
+ self.__scope__ = {}
13
+
14
+ def store(self, name, value):
15
+ self.__scope__[name] = value
16
+
17
+ def retrieve(self, name):
18
+ return self.__scope__[name]
19
+
20
+ def swap(self, name, value):
21
+ temp = self.__scope__[name]
22
+ self.__scope__[name] = value
23
+ return temp
24
+
25
+ def erase(self, name):
26
+ del self.__scope__[name]
27
+
28
+ def contains(self, name):
29
+ return name in self.__scope__
30
+
31
+ def clear_line(self, line:int):
32
+ del self.__lines__[line]
33
+ def clear_lines(self):
34
+ self.__lines__.clear()
35
+ def write_line(self, line:str):
36
+ self.__lines__.append(line.strip())
37
+ def write_lines(self, lines:list[str]):
38
+ for line in lines:
39
+ self.write_line(line)
40
+
41
+ def operators(self, symbol=None):
42
+ return self.parent.operators(symbol)
43
+ def terms(self, term=None):
44
+ return self.parent.terms(term)
45
+
46
+ def __iter__(self):
47
+ while self.current_line < len(self.__lines__):
48
+ yield self.__lines__[self.current_line]
49
+ self.current_line += 1
50
+
51
+ def go_to(self, line):
52
+ self.current_line = line
53
+
54
+ class Module(Scope):
55
+ @staticmethod
56
+ def load_module(file_path):
57
+ with open(file_path, 'r') as source:
58
+ return source.readlines()
59
+
60
+ def __new__(cls, processor, module_path:DotPath, parent=None):
61
+ name = module_path.end().literal
62
+ if name in processor.modules():
63
+ instance = processor.modules(name)
64
+ else:
65
+ instance = super().__new__(cls)
66
+ return instance
67
+
68
+ def register_operator(self, symbol:str, operation):
69
+ if symbol not in self.__operators__:
70
+ self.__operators__[symbol] = operation
71
+
72
+ def __init__(self, processor, module_path:DotPath, parent=None):
73
+ super().__init__(processor, module_path.file_name(), parent)
74
+ self.path = module_path.file_name('str')
75
+ self.write_lines(self.load_module(self.path))
76
+ self.processor.add_module(self)
77
+
78
+ self.__operators__ = processor.__operators__
79
+ self.__terms__ = processor.__terms__
80
+ self.__types__ = {}
81
+ self.__scope__ = {}
82
+
83
+ def operators(self, symbol=None):
84
+ if symbol is None:
85
+ return self.__operators__
86
+ return self.__operators__[symbol]
87
+
88
+ def terms(self, term=None):
89
+ if term is None:
90
+ return self.__terms__
91
+ return self.__terms__[term]