X11 sample rewritten; now it works again
[k8lst.git] / modules / rex.st
blob4b6a96dd7a4a42db1350c3ee0c9b84a32c2a0859
2  coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
3  Understanding is not required. Only obedience.
5  This program is free software. It comes without any warranty, to
6  the extent permitted by applicable law. You can redistribute it
7  and/or modify it under the terms of the Do What The Fuck You Want
8  To Public License, Version 2, as published by Sam Hocevar. See
9  http://sam.zoy.org/wtfpl/COPYING for more details.
11 " simple regular expression class
12   syntax:
13     [...]   range
14     [^...]  not-range
15     []...]  range with ']'
16     .       any char
17     ^       at the start of re: match should start at the start of the line
18     $       at the end of re: match should end at the end of the line
19     (...)   capture
20     any atom can be followed by '?', '+' or '*'; add '?' to non-greedy mode
22 Package [ RegExp ]
24 class: SimpleRegExp [
25 | rexStr
26   atoms string position hasSOL hasEOL
27   matchStart matchEnd captures captureCount |
29 ^new [
30   self error: 'use "SimpleRegExp new:" to create SimpleRegExp instance'
33 ^new: aStr [
34   | obj |
35   obj := self basicNew.
36   obj parseString: aStr.
37   ^obj
40 ^match: aRex for: aStr [
41   | obj |
42   obj := self new: aRex.
43   ^obj matchFor: aStr
47 "-------- getters --------"
48 matchStart [
49   ^matchStart
52 matchEnd [
53   ^matchEnd
57 captures [
58   ^captures
61 captureAt: idx [
62   ^captures at: idx
65 captureStartAt: idx [
66   ^(captures at: idx) at: 1
69 captureEndAt: idx [
70   ^(captures at: idx) at: 2
73 asString [
74   ^rexStr
77 "-------- atom adder --------"
78 addAtom: aSymbol [
79   atoms := atoms with: aSymbol
82 addAtom: aSymbol arg: aArg [
83   atoms := atoms with: (Array with: aSymbol with: aArg)
87 "-------- char eater --------"
88 peekChar [
89   position > string size ifTrue: [ ^nil ]
90   ifFalse: [^ string at: position ].
93 nextChar [
94   | c |
95   position > string size ifTrue: [ ^nil ]
96   ifFalse: [
97     c := string at: position.
98     position := position + 1.
99     ^c
100   ]
103 "-------- parser --------"
104 parseString: aStr [
105   rexStr := string := aStr.
106   position := 1.
107   atoms := Array new: 0.
108   ^self parse
111 isNonGreedy [
112   self peekChar == $? ifTrue: [
113     self nextChar.
114     ^true
115   ].
116   ^false
119 parserAddWild: aName arg: aArg [
120   self isNonGreedy ifTrue: [ aName := aName + 'NonGreedy' ].
121   aName := aName + ':from:'.
122   self addAtom: aName asSymbol arg: aArg
125 parse [
126   | c curAtom cpt cptClosed |
127   captureCount := 0.
128   hasSOL := false. hasEOL := false.
129   self peekChar == $^ ifTrue: [
130     self nextChar.
131     hasSOL := true.
132   ].
133   [ c := self nextChar ] whileNotNil: [
134     (cpt := c == $() ifTrue: [
135       (c := self nextChar) ifNil: [ self error: 'unexpected end of RegExp' ].
136       captureCount := captureCount + 1.
137       self addAtom: #captureStart: arg: captureCount.
138     ].
139     Case test: c;
140       case: $[ do: [ curAtom := self parseRange ];
141       case: $. do: [ curAtom := Array with: #any: with: nil ];
142       case: $$ do: [
143         position > string size ifTrue: [ hasEOL := true. ^self ].
144         curAtom := Array with: #char: with: $$.
145       ];
146       else: [:c | curAtom := Array with: #char: with: c ].
147     cptClosed := false.
148     self peekChar == $) ifTrue: [
149       position > (string size + 1) ifFalse: [
150         self nextChar.
151         ('?*+' includes: self peekChar) ifTrue: [ cptClosed := true. ] ifFalse: [ position := position - 1 ]
152       ].
153     ].
154     Case test: self peekChar;
155       case: $? do: [ self nextChar. self parserAddWild: 'zeroOrOne' arg: curAtom ];
156       case: $* do: [ self nextChar. self parserAddWild: 'zeroOrMore' arg: curAtom ];
157       case: $+ do: [ self nextChar.
158         self addAtom: #one:from: arg: curAtom.
159         self parserAddWild: 'zeroOrMore' arg: curAtom.
160       ];
161       else: [:c | self addAtom: #one:from: arg: curAtom. ].
162     cpt ifTrue: [
163       "capture end"
164       cptClosed ifFalse: [ self nextChar == $) ifFalse: [ self error: 'missing ")"' ]].
165       self addAtom: #captureEnd: arg: captureCount.
166     ].
167   ].
170 parseRange [
171   | c ce isNot range |
172   (c := self peekChar) ifNil: [ self error: 'unexpected end of RegExp' ].
173   (isNot := c == $^) ifTrue: [ self nextChar ].
174   (c := self peekChar) ifNil: [ self error: 'unexpected end of RegExp' ].
175   range := ''.
176   (c == $]) ifTrue: [ self nextChar. range := range + ']' ].
177   [ c := self nextChar ] whileNotNil: [
178     c == $] ifTrue: [
179       ^Array with: (isNot ifTrue: [ #rangeNot: ] ifFalse: [ #range: ]) with: range.
180     ].
181     self peekChar == $- ifTrue: [
182       self nextChar.
183       (ce := self nextChar) ifNil: [ self error: 'unexpected end of RegExp' ].
184       ce == $] ifTrue: [
185         range := range + '-'.
186         ^Array with: (isNot ifTrue: [ #rangeNot: ] ifFalse: [ #range: ]) with: range.
187       ].
188       "interval"
189       c value to: ce value do: [:c | range := range + (Char new: c) ].
190     ] ifFalse: [ range := range + c ].
191   ].
192   self error: 'unexpected end of RegExp'
196 "-------- captures --------"
197 captureStart: aNo [
198   | c |
199   (c := captures at: aNo) ifNil: [ captures at: aNo put: (c := Array new: 2) ].
200   c at: 1 put: position.
203 captureEnd: aNo [
204   | c |
205   (c := captures at: aNo) ifNil: [ captures at: aNo put: (c := Array new: 2) ].
206   c at: 2 put: position - 1.
210 "-------- simple matchers --------"
211 any: aChar [
212   ^self nextChar notNil
215 char: aChar [
216   ^self nextChar = aChar
219 range: aString [
220   | c |
221   (c := self nextChar) ifNil: [ ^false ].
222   ^aString includes: c
225 rangeNot: aString [
226   | c |
227   (c := self nextChar) ifNil: [ ^false ].
228   ^(aString includes: c) not
232 "-------- repeat matchers --------"
233 one: aAtom from: aPosNext [
234   (self perform: (aAtom at: 1) with: (aAtom at: 2)) ifFalse: [ ^nil ].
235   ^false
238 zeroOrOne: aAtom from: aPosNext [
239   (self perform: (aAtom at: 1) with: (aAtom at: 2))
240     ifTrue: [
241       "try to match the rest"
242       (self matchFrom: aPosNext) ifTrue: [ ^true ].
243     ].
244   "i need a backup!"
245   position := position - 1.
246   ^false
249 zeroOrMore: aAtom from: aPosNext [
250   | stpos |
251   stpos := position.
252   self findLastMatch: aAtom.
253   [ position > stpos ] whileTrue: [
254     (self matchFrom: aPosNext) ifTrue: [ ^true ].
255     position := position - 1.
256   ].
257   ^false
260 findLastMatch: aAtom [
261   "find the last match for aAtom"
262   | sym arg |
263   sym := aAtom at: 1.
264   arg := aAtom at: 2.
265   [ position > string size ] whileFalse: [
266     (self perform: sym with: arg) ifFalse: [
267       position := position - 1.
268       ^position
269     ].
270   ].
271   ^position
274 zeroOrOneNonGreedy: aAtom from: aPosNext [
275   "try to match the rest"
276   (self matchFrom: aPosNext) ifTrue: [ ^true ].
277   (self perform: (aAtom at: 1) with: (aAtom at: 2)) ifFalse: [ ^nil ].  "failure"
278   "i need a backup!"
279   position := position - 1.
280   ^false
283 zeroOrMoreNonGreedy: aAtom from: aPosNext [
284   | sym arg |
285   sym := aAtom at: 1.
286   arg := aAtom at: 2.
287   [ (self matchFrom: aPosNext) ifTrue: [ ^true ].
288     position > string size ]
289    whileFalse: [
290     (self perform: sym with: arg) ifFalse: [ ^nil ].  "failure"
291    ].
292   ^nil
296 "-------- sequence matcher --------"
297 matchFrom: aPos [
298   | opos res atom sym arg |
299   opos := position.
300   [ aPos > atoms size ] whileFalse: [
301     sym := (atom := atoms at: aPos) at: 1.
302     arg := atom at: 2.
303     aPos := aPos + 1.
304     sym == #captureStart: ifTrue: [ self captureStart: arg ]
305     ifFalse: [
306       sym == #captureEnd: ifTrue: [ self captureEnd: arg ]
307       ifFalse: [
308         (res := (self perform: sym with: arg with: aPos)) ifNil: [ position := opos. ^false ].
309         res ifTrue: [ ^true ].
310       ].
311     ].
312   ].
313   hasEOL ifTrue: [ res := position > string size ] ifFalse: [ res := true ].
314   res ifTrue: [ matchEnd := position - 1 ].
315   position := opos.
316   ^res
320 "-------- matcher entry point --------"
321 matchFor: aStr [
322   | stpos |
323   string := aStr.
324   position := matchStart := 1.
325   captures := Array new: captureCount.
326   (self matchFrom: 1) ifTrue: [ ^true ].
327   hasSOL ifTrue: [ ^false ].
328   stpos := 2.
329   [ stpos > string size ] whileFalse: [
330     matchStart := position := stpos.
331     (self matchFrom: 1) ifTrue: [ ^true ].
332     stpos := stpos + 1.
333   ].
334   ^false