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.
- rising_stretch-0.0.3/.gitignore +2 -0
- rising_stretch-0.0.3/PKG-INFO +7 -0
- rising_stretch-0.0.3/pyproject.toml +26 -0
- rising_stretch-0.0.3/stretch/__init__.py +10 -0
- rising_stretch-0.0.3/stretch/builtin_extensions/__init__.py +11 -0
- rising_stretch-0.0.3/stretch/builtin_extensions/input.py +20 -0
- rising_stretch-0.0.3/stretch/builtin_extensions/mathematics.py +14 -0
- rising_stretch-0.0.3/stretch/command.py +9 -0
- rising_stretch-0.0.3/stretch/core.py +193 -0
- rising_stretch-0.0.3/stretch/core_types.py +159 -0
- rising_stretch-0.0.3/stretch/extension.py +51 -0
- rising_stretch-0.0.3/stretch/interpreter.py +164 -0
- rising_stretch-0.0.3/stretch/precedence.py +64 -0
- rising_stretch-0.0.3/stretch/scope_types.py +91 -0
|
@@ -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,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]
|