jjinx 0.0.1__py3-none-any.whl
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.
- jinx/__init__.py +0 -0
- jinx/errors.py +41 -0
- jinx/execution/executor.py +60 -0
- jinx/execution/jax/__init__.py +45 -0
- jinx/execution/jax/adverbs.py +91 -0
- jinx/execution/jax/application.py +231 -0
- jinx/execution/jax/verbs.py +90 -0
- jinx/execution/numpy/__init__.py +29 -0
- jinx/execution/numpy/adverbs.py +334 -0
- jinx/execution/numpy/application.py +343 -0
- jinx/execution/numpy/conjunctions.py +437 -0
- jinx/execution/numpy/conversion.py +62 -0
- jinx/execution/numpy/helpers.py +158 -0
- jinx/execution/numpy/printing.py +179 -0
- jinx/execution/numpy/verbs.py +850 -0
- jinx/primitives.py +490 -0
- jinx/shell.py +68 -0
- jinx/vocabulary.py +181 -0
- jinx/word_evaluation.py +375 -0
- jinx/word_formation.py +229 -0
- jinx/word_spelling.py +118 -0
- jjinx-0.0.1.dist-info/METADATA +148 -0
- jjinx-0.0.1.dist-info/RECORD +27 -0
- jjinx-0.0.1.dist-info/WHEEL +5 -0
- jjinx-0.0.1.dist-info/entry_points.txt +2 -0
- jjinx-0.0.1.dist-info/licenses/LICENSE +21 -0
- jjinx-0.0.1.dist-info/top_level.txt +1 -0
jinx/vocabulary.py
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
"""J Vocabulary.
|
2
|
+
|
3
|
+
Building blocks / parts of speech for the J language.
|
4
|
+
|
5
|
+
The objects here are not tied to any implementation details needed for
|
6
|
+
execution (e.g. a verb is not tied to the code that will execute it).
|
7
|
+
|
8
|
+
The objects are just used to tag the words in the sentence so that they
|
9
|
+
can be evaluated at run time according to the context they are used in.
|
10
|
+
|
11
|
+
Resources:
|
12
|
+
- https://code.jsoftware.com/wiki/Vocabulary/Nouns
|
13
|
+
- https://code.jsoftware.com/wiki/Vocabulary/Words
|
14
|
+
- https://code.jsoftware.com/wiki/Vocabulary/Glossary
|
15
|
+
|
16
|
+
"""
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
from dataclasses import dataclass, field
|
21
|
+
from enum import Enum, auto
|
22
|
+
from typing import Callable, NamedTuple, Sequence
|
23
|
+
|
24
|
+
# Rank can be an integer or infinite (a float). It can't be any other float value
|
25
|
+
# but the type system does not make this easy to express.
|
26
|
+
RankT = int | float
|
27
|
+
|
28
|
+
|
29
|
+
class Word(NamedTuple):
|
30
|
+
"""Sequence of characters that can be recognised as a part of the J language."""
|
31
|
+
|
32
|
+
value: str
|
33
|
+
"""The string value of the word."""
|
34
|
+
|
35
|
+
is_numeric: bool
|
36
|
+
"""Whether the word represents a numeric value (e.g. an integer or float)."""
|
37
|
+
|
38
|
+
start: int
|
39
|
+
"""The start index of the word in the expression."""
|
40
|
+
|
41
|
+
end: int
|
42
|
+
"""The end index of the word in the expression (exclusive, so `expression[start:end]` is the value)."""
|
43
|
+
|
44
|
+
|
45
|
+
class DataType(Enum):
|
46
|
+
Integer = auto()
|
47
|
+
Float = auto()
|
48
|
+
Byte = auto()
|
49
|
+
Box = auto()
|
50
|
+
|
51
|
+
|
52
|
+
@dataclass
|
53
|
+
class Noun[T]:
|
54
|
+
data_type: DataType
|
55
|
+
"""Data type of value."""
|
56
|
+
|
57
|
+
data: Sequence[int | float | str] = field(default_factory=list)
|
58
|
+
"""Data to represent the value itself, parsed from the word."""
|
59
|
+
|
60
|
+
implementation: T = None # type: ignore[assignment]
|
61
|
+
"""Implementation of the noun, e.g. a NumPy array."""
|
62
|
+
|
63
|
+
|
64
|
+
@dataclass
|
65
|
+
class Monad[T]:
|
66
|
+
name: str
|
67
|
+
"""Name of the monadic verb."""
|
68
|
+
|
69
|
+
rank: RankT
|
70
|
+
"""Rank of monadic valence of the verb."""
|
71
|
+
|
72
|
+
function: Callable[[T], T] | Verb[T] = None # type: ignore[assignment]
|
73
|
+
"""Function to execute the monadic verb, or another Verb object. Initially
|
74
|
+
set to None and then updated at runtime."""
|
75
|
+
|
76
|
+
|
77
|
+
@dataclass
|
78
|
+
class Dyad[T]:
|
79
|
+
name: str
|
80
|
+
"""Name of the dyadic verb."""
|
81
|
+
|
82
|
+
left_rank: RankT
|
83
|
+
"""Left rank of the dyadic verb."""
|
84
|
+
|
85
|
+
right_rank: RankT
|
86
|
+
"""Right rank of the dyadic verb."""
|
87
|
+
|
88
|
+
function: Callable[[T, T], T] | Verb[T] = None # type: ignore[assignment]
|
89
|
+
"""Function to execute the monadic verb, or another Verb object. Initially
|
90
|
+
set to None and then updated at runtime."""
|
91
|
+
|
92
|
+
is_commutative: bool = False
|
93
|
+
"""Whether the dyadic verb is commutative."""
|
94
|
+
|
95
|
+
|
96
|
+
@dataclass
|
97
|
+
class Verb[T]:
|
98
|
+
spelling: str
|
99
|
+
"""The symbolic spelling of the verb, e.g. `+`."""
|
100
|
+
|
101
|
+
name: str
|
102
|
+
"""The name of the verb, e.g. `PLUS`, or its spelling if not a primitive J verb."""
|
103
|
+
|
104
|
+
monad: Monad[T] | None = None
|
105
|
+
"""The monadic form of the verb, if it exists."""
|
106
|
+
|
107
|
+
dyad: Dyad[T] | None = None
|
108
|
+
"""The dyadic form of the verb, if it exists."""
|
109
|
+
|
110
|
+
obverse: Verb[T] | str | None = None
|
111
|
+
"""The obverse of the verb, if it exists. This is typically the inverse of the verb."""
|
112
|
+
|
113
|
+
def __str__(self):
|
114
|
+
return self.spelling
|
115
|
+
|
116
|
+
def __repr__(self):
|
117
|
+
return self.spelling
|
118
|
+
|
119
|
+
|
120
|
+
@dataclass
|
121
|
+
class Adverb[T]:
|
122
|
+
spelling: str
|
123
|
+
"""The symbolic spelling of the adverb, e.g. `/`."""
|
124
|
+
|
125
|
+
name: str
|
126
|
+
"""The name of the adverb, e.g. `SLASH`."""
|
127
|
+
|
128
|
+
monad: Monad[T] | None = None
|
129
|
+
"""The monadic form of the adverb, if it exists."""
|
130
|
+
|
131
|
+
dyad: Dyad[T] | None = None
|
132
|
+
"""The dyadic form of the adverb, if it exists."""
|
133
|
+
|
134
|
+
function: Callable[[Verb[T] | Noun[T]], Verb[T]] = None # type: ignore[assignment]
|
135
|
+
"""Function of a single argument to implement the adverb."""
|
136
|
+
|
137
|
+
|
138
|
+
@dataclass
|
139
|
+
class Conjunction[T]:
|
140
|
+
spelling: str
|
141
|
+
"""The symbolic spelling of the conjunction, e.g. `@:`."""
|
142
|
+
|
143
|
+
name: str
|
144
|
+
"""The name of the conjunction, e.g. `ATCO`."""
|
145
|
+
|
146
|
+
function: Callable[[Verb[T] | Noun[T], Verb[T] | Noun[T]], Verb[T] | Noun[T]] = None # type: ignore[assignment]
|
147
|
+
"""Function of a two arguments to implement the conjunction."""
|
148
|
+
|
149
|
+
|
150
|
+
@dataclass
|
151
|
+
class Copula:
|
152
|
+
spelling: str
|
153
|
+
"""The symbolic spelling of the copula, e.g. `=.`."""
|
154
|
+
|
155
|
+
name: str
|
156
|
+
"""The name of the copula, e.g. `EQCO`."""
|
157
|
+
|
158
|
+
|
159
|
+
@dataclass
|
160
|
+
class Punctuation:
|
161
|
+
spelling: str
|
162
|
+
"""The symbolic spelling of the punctuation symbol, e.g. `(`."""
|
163
|
+
|
164
|
+
name: str
|
165
|
+
"""The name of the punctuation, e.g. `LPAREN`."""
|
166
|
+
|
167
|
+
|
168
|
+
@dataclass
|
169
|
+
class Comment:
|
170
|
+
spelling: str
|
171
|
+
"""The string value of the comment."""
|
172
|
+
|
173
|
+
|
174
|
+
@dataclass
|
175
|
+
class Name:
|
176
|
+
spelling: str
|
177
|
+
"""The string value of the name."""
|
178
|
+
|
179
|
+
|
180
|
+
PunctuationT = Punctuation | Comment
|
181
|
+
PartOfSpeechT = Noun | Verb | Adverb | Conjunction | PunctuationT | Copula | Name
|
jinx/word_evaluation.py
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
"""Parsing and evaluation.
|
2
|
+
|
3
|
+
In J parsing and evaluation happen simultaneously. A fragment of the sentence is matched
|
4
|
+
against a set of 8 patterns. When a match is found the corresponding operation is executed
|
5
|
+
and the fragment is replaced with the result. This continues until no more matches are found.
|
6
|
+
|
7
|
+
In Jinx the execution may be done using different backends. An Executor instance with methods
|
8
|
+
for executing on each pattern is passed by the caller.
|
9
|
+
|
10
|
+
https://www.jsoftware.com/ioj/iojSent.htm#Parsing
|
11
|
+
https://www.jsoftware.com/help/jforc/parsing_and_execution_ii.htm
|
12
|
+
https://code.jsoftware.com/wiki/Vocabulary/Modifiers
|
13
|
+
|
14
|
+
"""
|
15
|
+
|
16
|
+
from typing import cast
|
17
|
+
|
18
|
+
from jinx.errors import EvaluationError, JinxNotImplementedError, JSyntaxError
|
19
|
+
from jinx.execution.executor import Executor
|
20
|
+
from jinx.primitives import PRIMITIVES
|
21
|
+
from jinx.vocabulary import (
|
22
|
+
Adverb,
|
23
|
+
Comment,
|
24
|
+
Conjunction,
|
25
|
+
Copula,
|
26
|
+
Name,
|
27
|
+
Noun,
|
28
|
+
PartOfSpeechT,
|
29
|
+
Punctuation,
|
30
|
+
Verb,
|
31
|
+
)
|
32
|
+
from jinx.word_formation import form_words
|
33
|
+
from jinx.word_spelling import spell_words
|
34
|
+
|
35
|
+
|
36
|
+
def str_(executor: Executor, word: PartOfSpeechT | str) -> str:
|
37
|
+
if isinstance(word, str):
|
38
|
+
return word
|
39
|
+
if isinstance(word, Noun):
|
40
|
+
return executor.noun_to_string(word)
|
41
|
+
elif isinstance(word, Verb | Adverb | Conjunction):
|
42
|
+
return word.spelling
|
43
|
+
elif isinstance(word, Name):
|
44
|
+
return word.spelling
|
45
|
+
elif isinstance(word, Punctuation | Copula):
|
46
|
+
return word.spelling
|
47
|
+
else:
|
48
|
+
raise NotImplementedError(f"Cannot print word of type {type(word)}")
|
49
|
+
|
50
|
+
|
51
|
+
def print_words(
|
52
|
+
executor: Executor, word: PartOfSpeechT, variables: dict[str, PartOfSpeechT]
|
53
|
+
) -> None:
|
54
|
+
value = (
|
55
|
+
str_(executor, variables[word.spelling])
|
56
|
+
if isinstance(word, Name)
|
57
|
+
else str_(executor, word)
|
58
|
+
)
|
59
|
+
print(value)
|
60
|
+
|
61
|
+
|
62
|
+
def evaluate_single_verb_sentence(
|
63
|
+
executor: Executor, sentence: str, variables: dict[str, PartOfSpeechT]
|
64
|
+
) -> Verb:
|
65
|
+
tokens = form_words(sentence)
|
66
|
+
words = spell_words(tokens)
|
67
|
+
result = _evaluate_words(executor, words, variables)
|
68
|
+
if not isinstance(result, Verb):
|
69
|
+
raise EvaluationError(f"Expected a verb, got {type(result).__name__}")
|
70
|
+
return result
|
71
|
+
|
72
|
+
|
73
|
+
def build_verb_noun_phrase(
|
74
|
+
executor: Executor,
|
75
|
+
words: list[Verb | Noun | Adverb | Conjunction],
|
76
|
+
) -> Verb | Noun | None:
|
77
|
+
"""Build the verb or noun phrase from a list of words, or raise an error."""
|
78
|
+
while len(words) > 1:
|
79
|
+
match words:
|
80
|
+
case [left, Adverb(), *remaining]:
|
81
|
+
result = executor.apply_adverb(left, words[1]) # type: ignore[arg-type]
|
82
|
+
words = [result, *remaining]
|
83
|
+
|
84
|
+
case [left, Conjunction(), right, *remaining]:
|
85
|
+
result = executor.apply_conjunction(left, words[1], right) # type: ignore[arg-type]
|
86
|
+
words = [result, *remaining]
|
87
|
+
|
88
|
+
case _:
|
89
|
+
raise EvaluationError("Unable to build verb/noun phrase")
|
90
|
+
|
91
|
+
if not words:
|
92
|
+
return None
|
93
|
+
|
94
|
+
if isinstance(words[0], Verb | Noun):
|
95
|
+
return words[0]
|
96
|
+
|
97
|
+
raise EvaluationError("Unable to build verb/noun phrase")
|
98
|
+
|
99
|
+
|
100
|
+
def evaluate_words(
|
101
|
+
executor: Executor,
|
102
|
+
words: list[PartOfSpeechT],
|
103
|
+
variables: dict[str, PartOfSpeechT] | None = None,
|
104
|
+
level: int = 0,
|
105
|
+
) -> PartOfSpeechT:
|
106
|
+
if variables is None:
|
107
|
+
variables = {}
|
108
|
+
|
109
|
+
# Ensure noun and verb implementations are set according to the chosen execution
|
110
|
+
# framework (this is just NumPy for now).
|
111
|
+
for word in words:
|
112
|
+
if isinstance(word, Noun):
|
113
|
+
executor.ensure_noun_implementation(word)
|
114
|
+
|
115
|
+
all_primitives = (
|
116
|
+
executor.primitive_verb_map
|
117
|
+
| executor.primitive_adverb_map
|
118
|
+
| executor.primitive_conjuction_map
|
119
|
+
)
|
120
|
+
|
121
|
+
for primitive in PRIMITIVES:
|
122
|
+
if primitive.name not in all_primitives:
|
123
|
+
continue
|
124
|
+
if isinstance(primitive, Verb):
|
125
|
+
monad, dyad = executor.primitive_verb_map[primitive.name]
|
126
|
+
if primitive.monad is not None and monad is not None:
|
127
|
+
primitive.monad.function = monad
|
128
|
+
if primitive.dyad is not None and dyad is not None:
|
129
|
+
primitive.dyad.function = dyad
|
130
|
+
if isinstance(primitive, Adverb):
|
131
|
+
primitive.function = executor.primitive_adverb_map[primitive.name] # type: ignore[assignment]
|
132
|
+
if isinstance(primitive, Conjunction):
|
133
|
+
primitive.function = executor.primitive_conjuction_map[primitive.name]
|
134
|
+
|
135
|
+
# Verb obverses are converted from strings to Verb objects.
|
136
|
+
for word in words:
|
137
|
+
if isinstance(word, Verb) and isinstance(word.obverse, str):
|
138
|
+
verb = evaluate_single_verb_sentence(executor, word.obverse, variables)
|
139
|
+
word.obverse = verb
|
140
|
+
|
141
|
+
return _evaluate_words(executor, words, variables, level=level)
|
142
|
+
|
143
|
+
|
144
|
+
def get_parts_to_left(
|
145
|
+
executor: Executor,
|
146
|
+
word: PartOfSpeechT,
|
147
|
+
words: list[PartOfSpeechT],
|
148
|
+
current_level: int,
|
149
|
+
variables: dict[str, PartOfSpeechT],
|
150
|
+
) -> list[Noun | Verb | Adverb | Conjunction]:
|
151
|
+
"""Get the parts of speach to the left of the current word, modifying list of remaining words.
|
152
|
+
|
153
|
+
This method is called when the last word we encountered is an adverb or conjunction and
|
154
|
+
a verb or noun phrase is expected to the left of it.
|
155
|
+
"""
|
156
|
+
parts_to_left: list[Noun | Verb | Adverb | Conjunction] = []
|
157
|
+
|
158
|
+
while words:
|
159
|
+
word = resolve_word(word, variables)
|
160
|
+
# A verb/noun phrase starts with a verb/noun which does not have a conjunction to its left.
|
161
|
+
if isinstance(word, Noun | Verb):
|
162
|
+
if not isinstance(words[-1], Conjunction):
|
163
|
+
parts_to_left = [word, *parts_to_left]
|
164
|
+
break
|
165
|
+
else:
|
166
|
+
conjunction = cast(Conjunction, words.pop())
|
167
|
+
parts_to_left = [conjunction, word, *parts_to_left]
|
168
|
+
|
169
|
+
elif isinstance(word, Adverb | Conjunction):
|
170
|
+
parts_to_left = [word, *parts_to_left]
|
171
|
+
|
172
|
+
elif isinstance(word, Punctuation) and word.spelling == ")":
|
173
|
+
word = evaluate_words(executor, words, level=current_level + 1)
|
174
|
+
continue
|
175
|
+
|
176
|
+
else:
|
177
|
+
break
|
178
|
+
|
179
|
+
if words:
|
180
|
+
word = words.pop()
|
181
|
+
|
182
|
+
return parts_to_left
|
183
|
+
|
184
|
+
|
185
|
+
def resolve_word(
|
186
|
+
word: PartOfSpeechT, variables: dict[str, PartOfSpeechT]
|
187
|
+
) -> PartOfSpeechT:
|
188
|
+
"""Find the Verb/Adverb/Conjunction/Noun that a name is assigned to.
|
189
|
+
|
190
|
+
If we encounter a cycle of names, return the original name.
|
191
|
+
"""
|
192
|
+
if not isinstance(word, Name):
|
193
|
+
return word
|
194
|
+
|
195
|
+
original_name = word
|
196
|
+
visited = set()
|
197
|
+
while True:
|
198
|
+
visited.add(word.spelling)
|
199
|
+
if word.spelling not in variables:
|
200
|
+
return word
|
201
|
+
assignment = variables[word.spelling]
|
202
|
+
if not isinstance(assignment, Name):
|
203
|
+
return assignment
|
204
|
+
word = assignment
|
205
|
+
if word.spelling in visited:
|
206
|
+
return original_name
|
207
|
+
|
208
|
+
|
209
|
+
def _evaluate_words(
|
210
|
+
executor: Executor, words: list[PartOfSpeechT], variables, level: int = 0
|
211
|
+
) -> PartOfSpeechT:
|
212
|
+
# If the first word is None, prepend a None to the list denoting the left-most
|
213
|
+
# edge of the expression.
|
214
|
+
if words[0] is not None:
|
215
|
+
words = [None, *words] # type: ignore[list-item]
|
216
|
+
|
217
|
+
fragment: list[PartOfSpeechT] = []
|
218
|
+
result: PartOfSpeechT
|
219
|
+
|
220
|
+
while words:
|
221
|
+
word = words.pop()
|
222
|
+
|
223
|
+
if isinstance(word, Comment):
|
224
|
+
continue
|
225
|
+
|
226
|
+
elif isinstance(word, (Punctuation, Copula)):
|
227
|
+
word = word.spelling # type: ignore[assignment]
|
228
|
+
|
229
|
+
# If the next word closes a parenthesis, we need to evaluate the words inside it
|
230
|
+
# first to get the next word to prepend to the fragment.
|
231
|
+
if word == ")":
|
232
|
+
word = evaluate_words(executor, words, variables, level=level + 1)
|
233
|
+
|
234
|
+
# If the fragment has a modifier (adverb/conjunction) at the start, we need to find the
|
235
|
+
# entire verb/noun phrase to the left as the next word to prepend to the fragment.
|
236
|
+
# Contrary to usual parsing and evaluation, the verb/noun phrase is evaluated left-to-right.
|
237
|
+
if fragment and isinstance(
|
238
|
+
resolve_word(fragment[0], variables), Adverb | Conjunction
|
239
|
+
):
|
240
|
+
parts_to_left = get_parts_to_left(
|
241
|
+
executor, word, words, level, variables=variables
|
242
|
+
)
|
243
|
+
# parts_to_left may be empty if the conjunction is the target of an assignment
|
244
|
+
# or enclosed in parentheses.
|
245
|
+
if parts_to_left:
|
246
|
+
word = build_verb_noun_phrase(executor, parts_to_left) # type: ignore[assignment]
|
247
|
+
|
248
|
+
fragment = [word, *fragment]
|
249
|
+
|
250
|
+
# fmt: off
|
251
|
+
while True:
|
252
|
+
|
253
|
+
# 7. Is
|
254
|
+
# This case (assignment) is checked separately, before names are substituted with their values.
|
255
|
+
# For now we treat =. and =: the same.
|
256
|
+
match fragment:
|
257
|
+
case Name(), "=." | "=:", Conjunction() | Adverb() | Verb() | Noun() | Name(), *_:
|
258
|
+
name, _, cavn, *last = fragment
|
259
|
+
name = cast(Name, name)
|
260
|
+
variables[name.spelling] = cavn
|
261
|
+
fragment = [name, *last]
|
262
|
+
continue
|
263
|
+
|
264
|
+
# Substitute variable names with their values and do pattern matching. If a match occurs
|
265
|
+
# the original fragment (list of unsubstituted names) is modified.
|
266
|
+
fragment_ = [resolve_word(word, variables) for word in fragment]
|
267
|
+
|
268
|
+
match fragment_:
|
269
|
+
|
270
|
+
# 0. Monad
|
271
|
+
case None | "=." | "=:" | "(", Verb(), Noun():
|
272
|
+
edge, verb, noun = fragment_
|
273
|
+
result = executor.apply_monad(verb, noun) # type: ignore[arg-type]
|
274
|
+
if edge == "(" and level > 0:
|
275
|
+
return result
|
276
|
+
fragment[1:] = [result]
|
277
|
+
|
278
|
+
# 1. Monad
|
279
|
+
case None | "=." | "=:" | "(" | Adverb() | Verb() | Noun(), Adverb() | Verb(), Verb(), Noun():
|
280
|
+
edge, _, verb, noun = fragment_
|
281
|
+
result = executor.apply_monad(verb, noun) # type: ignore[arg-type]
|
282
|
+
fragment[2:] = [result]
|
283
|
+
|
284
|
+
# 2. Dyad
|
285
|
+
case None | "=." | "=:" | "(" | Adverb() | Verb() | Noun(), Noun(), Verb(), Noun():
|
286
|
+
edge, noun, verb, noun_2 = fragment_
|
287
|
+
result = executor.apply_dyad(verb, noun, noun_2) # type: ignore[arg-type]
|
288
|
+
if edge == "(" and level > 0:
|
289
|
+
return result
|
290
|
+
fragment[1:] = [result]
|
291
|
+
|
292
|
+
# 3. Adverb
|
293
|
+
case (
|
294
|
+
None | "=." | "=:" | "(" | Adverb() | Verb() | Noun(),
|
295
|
+
Verb() | Noun(),
|
296
|
+
Adverb(),
|
297
|
+
*_,
|
298
|
+
):
|
299
|
+
edge, verb, adverb, *last = fragment_
|
300
|
+
result = executor.apply_adverb(verb, adverb) # type: ignore[arg-type]
|
301
|
+
if edge == "(" and last == [")"] and level > 0:
|
302
|
+
return result
|
303
|
+
fragment[1:3] = [result]
|
304
|
+
|
305
|
+
# 4. Conjunction
|
306
|
+
case (
|
307
|
+
None | "=." | "=:" | "(" | Adverb() | Verb() | Noun(),
|
308
|
+
Verb() | Noun(),
|
309
|
+
Conjunction(),
|
310
|
+
Verb() | Noun(),
|
311
|
+
*_,
|
312
|
+
):
|
313
|
+
edge, verb_or_noun_1, conjunction, verb_or_noun_2, *last = fragment_
|
314
|
+
result = executor.apply_conjunction(verb_or_noun_1, conjunction, verb_or_noun_2) # type: ignore[arg-type]
|
315
|
+
if edge == "(" and last == [")"] and level > 0:
|
316
|
+
return result
|
317
|
+
fragment[1:4] = [result]
|
318
|
+
|
319
|
+
# 5. Fork
|
320
|
+
case (
|
321
|
+
None | "=." | "=:" | "(" | Adverb() | Verb() | Noun(),
|
322
|
+
Verb() | Noun(),
|
323
|
+
Verb(),
|
324
|
+
Verb(),
|
325
|
+
):
|
326
|
+
edge, verb_or_noun_1, verb_2, verb_3 = fragment_
|
327
|
+
result = executor.build_fork(verb_or_noun_1, verb_2, verb_3) # type: ignore[arg-type]
|
328
|
+
if edge == "(" and level > 0:
|
329
|
+
return result
|
330
|
+
fragment[1:4] = [result]
|
331
|
+
|
332
|
+
# 6. Hook/Adverb
|
333
|
+
case (
|
334
|
+
None | "=." | "=:" | "(",
|
335
|
+
Conjunction() | Adverb() | Verb() | Noun(),
|
336
|
+
Conjunction() | Adverb() | Verb() | Noun(),
|
337
|
+
*_,
|
338
|
+
):
|
339
|
+
edge, cavn1, cavn2, *last = fragment_
|
340
|
+
match [cavn1, cavn2]:
|
341
|
+
case [Verb(), Verb()]:
|
342
|
+
result = executor.build_hook(cavn1, cavn2) # type: ignore[arg-type]
|
343
|
+
case [Adverb(), Adverb()] | [Conjunction() , Noun()] | [Conjunction(), Verb()] | [Noun(), Conjunction()] | [Verb(), Conjunction()]:
|
344
|
+
# These are valid combinations but not implemented yet.
|
345
|
+
raise JinxNotImplementedError(
|
346
|
+
f"Jinx error: currently only 'Verb Verb' is implemented for hook/adverb matching, got "
|
347
|
+
f"({type(cavn1).__name__} {type(cavn2).__name__})"
|
348
|
+
)
|
349
|
+
case _:
|
350
|
+
raise JSyntaxError(f"syntax error: unexecutable fragment ({type(cavn1).__name__} {type(cavn2).__name__})")
|
351
|
+
if edge == "(" and level > 0:
|
352
|
+
return result
|
353
|
+
fragment[1:] = [result]
|
354
|
+
|
355
|
+
# 8. Parentheses
|
356
|
+
# Differs from the J source as it does not match ")" and instead checks
|
357
|
+
# the level to ensure that "(" is balanced.
|
358
|
+
case ["(", Conjunction() | Adverb() | Verb() | Noun()]:
|
359
|
+
_, cavn = fragment_
|
360
|
+
if level > 0:
|
361
|
+
return cast(PartOfSpeechT, cavn)
|
362
|
+
raise EvaluationError("Unbalanced parentheses")
|
363
|
+
|
364
|
+
# Non-executable fragment.
|
365
|
+
case _:
|
366
|
+
break
|
367
|
+
|
368
|
+
# fmt: on
|
369
|
+
|
370
|
+
if len(fragment) > 2:
|
371
|
+
raise EvaluationError(
|
372
|
+
f"Unexecutable fragment: {[str_(executor, w) for w in fragment if w is not None]}"
|
373
|
+
)
|
374
|
+
|
375
|
+
return fragment[1]
|