11"""
2- Encoder states
2+ NFA implementation for context-aware tokenization with lookahead
33"""
44
55
@@ -16,26 +16,29 @@ class TokenizerState:
1616 Each state represents some encoding context which affects tokenization.
1717 """
1818
19- mode : int = 0
20- """
21- Whether to munch maximally (``0``) or minimally (``-1``)
22- """
23-
2419 max_length : int = None
2520 """
2621 The maximum number of tokens to emit before leaving this state
2722 """
2823
29- def __init__ (self , length : int = 0 ):
24+ def __init__ (self , mode : int , accept : bool = True , length : int = 0 ):
25+ """
26+ :param mode: Whether to munch maximally (``0``) or minimally (``-1``)
27+ :param accept: Whether this state can end a timeline (defaults to ``True``)
28+ :param length: The current length of the input this state is going to process (defaults to ``0``)
29+ """
30+
31+ self .mode = mode
32+ self .accept = accept
3033 self .length = length
3134
32- def munch (self , string : str , trie : TITokenTrie ) -> tuple [TIToken , str , list ['TokenizerState' ]]:
35+ def munch (self , string : str , trie : TITokenTrie ) -> tuple [TIToken , str , list [list [ 'TokenizerState' ] ]]:
3336 """
34- Munch the input string and determine the resulting token, encoder state , and remainder of the string
37+ Munch the input string and determine the resulting token, tokenizer timelines , and remainder of the string
3538
3639 :param string: The text string to tokenize
3740 :param trie: The `TokenTrie` object to use for tokenization
38- :return: A tuple of the output `Token`, the remainder of ``string``, and a list of states to add to the stack
41+ :return: A tuple of the output `Token`, the remainder of ``string``, and a list of timelines
3942 """
4043
4144 # Is this a byte literal?
@@ -44,7 +47,7 @@ def munch(self, string: str, trie: TITokenTrie) -> tuple[TIToken, str, list['Tok
4447 string , remainder = string [:length ], string [length :]
4548 token = IllegalToken (bytes .fromhex (string .lstrip (r"\ux" )))
4649
47- return token , remainder , self .next (token )
50+ return token , remainder , self .next (token , remainder )
4851
4952 # Is this a var prefix?
5053 for leading_byte , prefix in TIToken .var_prefixes .items ():
@@ -53,95 +56,106 @@ def munch(self, string: str, trie: TITokenTrie) -> tuple[TIToken, str, list['Tok
5356 string , remainder = string [:length ], string [length :]
5457 token = IllegalToken (bytes ([leading_byte , int (string [- 2 :], 16 )]))
5558
56- return token , remainder , self .next (token )
59+ return token , remainder , self .next (token , remainder )
5760
5861 # Is there a token separator?
5962 if string .startswith (("␟" , " " , "" )):
6063 string = string [1 :]
6164
6265 # Is there a backslash?
6366 if string .startswith ("\\ " ):
64- string = string [1 :]
65- self . mode = 0
67+ tokens = trie . match ( string [1 :])
68+ token , remainder = tokens [ 0 ]
6669
67- tokens = trie .match (string )
68- if not tokens :
69- raise ValueError ("no tokenization options exist" )
70+ else :
71+ tokens = trie .match (string )
7072
71- # Is this a glyph?
72- if string [0 ] in punctuation and len (tokens ) > 1 :
73- tokens .pop ()
73+ # Is this a glyph?
74+ if string [0 ] in punctuation and len (tokens ) > 1 :
75+ tokens .pop ()
7476
75- token , remainder = tokens [self .mode ]
77+ token , remainder = tokens [self .mode ]
7678
7779 # Are we out of tokens?
7880 if self .length == self .max_length :
79- return token , remainder , []
81+ return token , remainder , [[] ]
8082
81- return token , remainder , self .next (token )
83+ return token , remainder , self .next (token , remainder )
8284
83- def next (self , token : TIToken ) -> list ['TokenizerState' ]:
85+ def next (self , token : TIToken , remainder : str ) -> list [list [ 'TokenizerState' ] ]:
8486 """
85- Determines the next tokenizer state given a token
87+ Determines the next tokenizer timelines given a token
8688
8789 The current state is popped from the stack, and the states returned by this method are pushed.
8890
89- If the list of returned states is...
90- - empty, then the tokenizer is exiting the current state.
91- - length one, then the tokenizer's current state is being replaced by a new state.
92- - length two, then the tokenizer is entering a new state, able to exit back to this one.
91+ 1. The current state is popped from the stack.
92+ 2. All possible timelines are determined, each a list of states.
93+ 3. For each separate timeline, those states are added its stack.
94+
95+ If a list of states in a timeline is...
96+ - empty, then the timeline is exiting the current state.
97+ - length one, then the timeline's current state is being replaced by a new state.
98+ - length two, then the timeline is entering a new state, able to exit back to this one.
9399
94100 :param token: The current token
95- :return: A list of tokenizer states to add to the stack
101+ :param remainder: The remaining string content to tokenize
102+ :return: A list of timelines (each a list of states)
96103 """
97104
98- return [type (self )(self .length + 1 )]
105+ return [[type (self )(self .mode , self .accept , self .length + 1 )]]
106+
107+
108+ class IllegalState (TokenizerState ):
109+ """
110+ Tokenizer state which indicates its timeline must be pruned
111+ """
99112
100113
101114class MaxMode (TokenizerState ):
102115 """
103116 Maximal munching mode
104117 """
105118
106- mode = 0
119+ def __init__ (self , mode : int = 0 , accept : bool = True , length : int = 0 ):
120+ super ().__init__ (mode , accept , length )
107121
108122
109123class MinMode (TokenizerState ):
110124 """
111125 Minimal munching mode
112126 """
113127
114- mode = - 1
128+ def __init__ (self , mode : int = - 1 , accept : bool = True , length : int = 0 ):
129+ super ().__init__ (mode , accept , length )
115130
116131
117132class Line (TokenizerState ):
118133 """
119134 State which is always exited after a line break or STO
120135 """
121136
122- def next (self , token : TIToken ) -> list [TokenizerState ]:
137+ def next (self , token : TIToken , remainder : str ) -> list [list [ TokenizerState ] ]:
123138 match token .bits :
139+ # STO (→) Line break
124140 case b'\x04 ' | b'\x3F ' :
125- return []
141+ return [[] ]
126142
127143 case _:
128- return super ().next (token )
144+ return super ().next (token , remainder )
129145
130146
131- class Name (Line ):
147+ class Name (MinMode , Line ):
132148 """
133149 Valid var identifiers
134150 """
135151
136- mode = - 1
137-
138- def next (self , token : TIToken ) -> list [TokenizerState ]:
152+ def next (self , token : TIToken , remainder : str ) -> list [list [TokenizerState ]]:
139153 # Digits Uppercase letters (and theta)
140154 if b'\x30 ' <= token .bits <= b'\x39 ' or b'\x41 ' <= token .bits <= b'\x5B ' :
141- return super ().next (token )
155+ return super ().next (token , remainder )
142156
143157 else :
144- return []
158+ return [[] ]
145159
146160
147161class ListName (Name ):
@@ -165,72 +179,90 @@ class String(Line):
165179 Strings
166180 """
167181
168- mode = - 1
169-
170- def next (self , token : TIToken ) -> list [TokenizerState ]:
182+ def next (self , token : TIToken , remainder : str ) -> list [list [TokenizerState ]]:
171183 match token .bits :
184+ case b'\x04 ' :
185+ return [[StringTarget (self .mode , self .accept )]]
186+
172187 case b'\x2A ' :
173- return []
188+ return [[ StringSto ( self . mode , self . accept )] ]
174189
175190 case _:
176- return super ().next (token )
191+ return super ().next (token , remainder )
177192
178193
179- class MaxString ( String ):
194+ class StringStart ( Line ):
180195 """
181- Maximally munched string
196+ Opening quote of a string
182197 """
183198
184- mode = 0
199+ def next (self , token : TIToken , remainder : str ) -> list [list [TokenizerState ]]:
200+ match token .bits :
201+ case b'\x2A ' :
202+ return [[String (self .mode , self .accept )]]
203+
204+ case _:
205+ return [[]]
185206
186207
187- class MaxStart (Line ):
208+ class StringSto (Line ):
188209 """
189- State to initialize `MaxString`
190-
191- If any token besides ``"`` is encountered, this state is immediately exited to avoid cluttering the stack.
210+ STO immediately following a string
192211 """
193212
194- mode = 0
195-
196- def next (self , token : TIToken ) -> list [TokenizerState ]:
213+ def next (self , token : TIToken , remainder : str ) -> list [list [TokenizerState ]]:
197214 match token .bits :
198- case b'\x2A ' :
199- return [MaxString ()]
215+ case b'\x04 ' :
216+ return [[StringTarget (self .mode , self .accept )]]
217+
218+ case _:
219+ return [[]] if self .accept else [[IllegalState (0 )]]
220+
221+
222+ class StringTarget (Line ):
223+ """
224+ STO target of a string
225+ """
226+
227+ def next (self , token : TIToken , remainder : str ) -> list [list [TokenizerState ]]:
228+ match self .mode , token .bits .startswith (b'\x5E ' ), self .accept :
229+ case (0 , True , _) | (0 , _, True ) | (- 1 , False , _):
230+ return [[]]
200231
201232 case _:
202- return []
233+ return [[ IllegalState ( 0 )] ]
203234
204235
205236class SmartMode (TokenizerState ):
206237 """
207238 Smart tokenization mode
208239 """
209240
210- mode = 0
241+ def __init__ (self , mode : int = 0 , accept : bool = True , length : int = 0 ):
242+ super ().__init__ (mode , accept , length )
211243
212- def next (self , token : TIToken ) -> list [TokenizerState ]:
244+ def next (self , token : TIToken , remainder : str ) -> list [list [ TokenizerState ] ]:
213245 match token .bits :
214246 # "
215247 case b'\x2A ' :
216- return [self , String () ]
248+ return [[ self , String (0 , False )], [ self , String ( - 1 )] ]
217249
218250 # prgm
219251 case b'\x5F ' :
220- return [self , ProgramName ()]
252+ return [[ self , ProgramName ()] ]
221253
222254 # Send( String>Equ(
223255 case b'\xE7 ' | b'\xBB \x56 ' :
224- return [self , MaxStart () ]
256+ return [[ self , StringStart ( 0 )] ]
225257
226258 # |L
227259 case b'\xEB ' :
228- return [self , ListName ()]
260+ return [[ self , ListName ()] ]
229261
230262 case _:
231- return super ().next (token )
263+ return super ().next (token , remainder )
232264
233265
234- __all__ = ["TokenizerState" , "MaxMode" , "MinMode" , "SmartMode" ,
266+ __all__ = ["TokenizerState" , "IllegalState" , " MaxMode" , "MinMode" , "SmartMode" ,
235267 "Line" , "Name" , "ListName" , "ProgramName" ,
236- "String" , "MaxString " , "MaxStart " ]
268+ "String" , "StringStart " , "StringSto" , "StringTarget " ]
0 commit comments