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.
12 COMMENT GST-style parser
14 COMMENT A new class is created using this syntax:
16 COMMENT superclass-name subclass: new-class-name | class vars | [
17 COMMENT | instance vars |
18 COMMENT message-pattern-1 [ statements ]
19 COMMENT message-pattern-2 [ statements ]
23 COMMENT if class has no class variables, the entire '| class variables |' clause
24 COMMENT can be omited.
26 COMMENT if message-pattern starts with ^, this is class message, not instance one.
29 COMMENT A similar syntax is used to define new methods in an existing class.
31 COMMENT class-expression extend [
36 COMMENT inStream must support 3 methods:
37 COMMENT readChar: returns char or nil on EOS
38 COMMENT unreadChar: unreads char (only one is needed)
39 COMMENT lineNum: returns current line number
42 CLASS GSTParser Object inStream errorBlock warningBlock lastMethodName lastMethodLine lastWordLine category
48 self in: obj var: #inStream put: inStream.
68 warningBlock: aBlock [
69 warningBlock := aBlock
74 (fn := inStream fileName) ifNil: [ ^nil ].
75 (li := fn lastIndexOf: '/') ifNil: [ ^fn ].
79 error: aString at: lineNum [
81 errorBlock ifNotNil: [ errorBlock value: aString value: lineNum ]
83 "comment the following to avoid duplicate error messages"
84 (aString := 'Compile error near line ' + lineNum + ': ' + aString) printNl.
85 (fn := self fileName) ifNotNil: [ aString := fn + ': ' + aString ].
91 ^self error: aString at: inStream lineNum
94 warning: aString at: lineNum [
96 warningBlock ifNotNil: [ warningBlock value: aString value: lineNum ]
98 aString := ('Compile warning near line ' + lineNum + ': ' + aString).
99 (fn := self fileName) ifNotNil: [ aString := fn + ': ' + aString ].
105 ^self warning: aString at: inStream lineNum
110 word ifNil: [ self error: 'unexpected end of file' ].
114 expectedWord: want got: word [
115 self error: ('"' + want +'" expected, but got "' + word + '"')
120 [ (c := inStream readChar) ifNil: [ ^nil ]. c == $" ] whileFalse: [ nil ].
123 skipBlanksAndComments [
124 "skip blanks and comments, return nil or first non-blank char (char is eaten)"
126 [ [ (c := inStream readChar) ifNil: [ ^nil ]. c isBlank ] whileTrue: [ nil ].
127 c == $" ] whileTrue: [ self skipComment. ].
132 "skip blanks, return nil or first non-blank char (char is not eaten)"
134 [ (c := inStream readChar) ifNil: [ ^nil ]. c isBlank ] whileTrue: [ nil ].
135 ^inStream unreadChar: c
138 collectUntil: terminator to: buf [
141 [ pc := c. buf << (self dontWantEof: (c := inStream readChar)). c == terminator ] whileFalse: [ nil ].
142 (terminator == $' and: [ pc == $\ ]) ifTrue: [ buf << terminator. ^self collectUntil: terminator to: buf ].
146 collectBlanksAndCommentsTo: buf [
148 [ [ (self dontWantEof: (c := inStream readChar)) isBlank ] whileTrue: [ buf << c ].
149 c == $" ] whileTrue: [ buf << c. self collectUntil: $" to: buf. ].
150 inStream unreadChar: c.
155 c := inStream readChar.
156 ^(inStream unreadChar: c)
157 "^(inStream unreadChar: (inStream readChar))"
161 "read one word from input"
163 (c := self skipBlanksAndComments) ifNil: [ ^nil ].
164 lastWordLine := inStream lineNum.
165 c isAlphanumeric ifFalse: [ ^c asString ].
166 (word := StringBuffer new) << c.
167 [ (c := inStream readChar) ifNil: [ ^word asString ].
168 c isAlphanumeric ] whileTrue: [ word << c ].
169 inStream unreadChar: c.
175 self dontWantEof: (w := self readWord).
176 w ~= word ifTrue: [ self expectedWord: word got: w ].
181 ^('.()[]#^$;{}' includesChar: c) or: [ c == $' ]
185 | c w body mname isKWord isUnary goon |
186 self dontWantEof: (mname := self readWord).
187 (self charIsSyntax: mname firstChar) ifTrue: [ self error: 'method name expected' at: lastWordLine ].
188 lastMethodLine := lastWordLine.
191 (body := StringBuffer new) << mname.
192 mname firstChar isAlphanumeric
194 self lookAhead == $: ifTrue: [
197 mname := mname + ':'.
199 ] ifFalse: [ isUnary := true ].
201 "read other chars of the method name"
202 [ (self charIsSyntax: (self dontWantEof: (c := inStream readChar))) ifTrue: [ self error: 'invalid binary method name' ].
203 c isAlphanumeric ] whileFalse: [ body << c ].
204 inStream unreadChar: c. "this is not ours"
206 lastMethodName := mname.
208 "now read args and rest keywords if any"
209 [ self collectBlanksAndCommentsTo: body.
210 "here we MUST have an argname"
211 (self dontWantEof: (w := self readWord)).
212 w firstChar isAlphanumeric ifFalse: [ self error: 'argument name expected' ].
213 self lookAhead == $: ifTrue: [ self error: 'unexpected keyword' ].
214 body << w. "save argument name"
216 "here must be keyword or sqp"
217 self collectBlanksAndCommentsTo: body.
218 (self dontWantEof: (c := self lookAhead)) isAlphanumeric ifTrue: [
219 "keyword or simple word"
220 body << (self dontWantEof: (w := self readWord)).
221 (self dontWantEof: inStream readChar) == $: ifFalse: [ self error: 'keyword expected' ].
223 lastMethodName := lastMethodName + w + ':'.
232 goon ] whileTrue: [ nil ].
235 self collectBlanksAndCommentsTo: body.
236 (self dontWantEof: inStream readChar) == $[ ifFalse: [ self error: '"[" expected' ].
237 "now skip until eol or so"
238 [ self dontWantEof: (c := inStream readChar) isEOL ] whileFalse: [
239 c isBlank ifFalse: [ inStream unreadChar: c. ^(body asString) removeTrailingBlanks ].
241 ^(body asString) removeTrailingBlanks
246 body := StringBuffer new.
248 [ (self dontWantEof: (c := inStream readChar)) == $[ ifTrue: [ sqcnt := sqcnt + 1 ].
249 (c == $] and: [ sqcnt = 0 ]) ]
251 c == $] ifTrue: [ sqcnt := sqcnt - 1 ].
253 (c == $" or: [ c == $' ]) ifTrue: [ self collectUntil: c to: body ]
254 ifFalse: [ c == $$ ifTrue: [ body << (self dontWantEof: inStream readChar) ]].
256 "remove trailing blanks"
257 body := (body asString) removeTrailingBlanks.
258 ^(body + String newline) "just in case"
262 | minspc lines pos mrg |
264 lines := s break: '\n'.
266 l := l removeTrailingBlanks.
269 [ (l at: pos) isBlank ] whileTrue: [ mrg := mrg + 1. pos := pos + 1 ].
270 "mrg print. '|' print. l printNl."
271 mrg < 1 ifTrue: [ ^0 ].
272 minspc := minspc min: mrg.
275 "'margin: ' print. minspc printNl."
279 removeLeftMargin: s [
281 mrg := (self findLeftMargin: s) + 1.
282 lines := s break: '\n'.
283 sbuf := StringBuffer new.
285 l := l removeTrailingBlanks.
286 l isEmpty ifFalse: [ l := l from: mrg ].
287 sbuf << ' ' << l << '\n'.
293 "single-quote char already skiped"
295 name := StringBuffer new.
296 [ (self dontWantEof: (c := inStream readChar)) == $' ] whileFalse: [ name << c ].
297 category := name asString.
300 compileMethod: aClass [
301 "return true if method was succesfully parsed or false if sqp found"
302 | c mtname isMeta hdr body p |
303 (self dontWantEof: (c := self skipBlanksAndComments)) == $] ifTrue: [ ^false ].
304 (isMeta := (c == $^)) ifFalse: [
305 c == $' ifTrue: [ self parseCategory. ^self compileMethod: aClass ].
306 (self charIsSyntax: c) ifTrue: [ self error: 'method name or "]" expected' ].
307 inStream unreadChar: c.
309 hdr := self readMethodHeader.
310 body := self readMethodBody.
311 body := self removeLeftMargin: body.
312 "hdr print. '|' printNl. body print. '||' printNl."
313 body := hdr + '\n' + body.
314 mtname := (aClass getName asString) + '>>' + (isMeta ifTrue: ['^'] ifFalse: ['']) + lastMethodName.
315 p := LstCompiler new.
316 p errorBlock: [ :msg :lineNum | self error: 'in method "' + mtname + '": ' + msg at: lastMethodLine + lineNum - 1 ].
317 p warningBlock: [ :msg :lineNum | self warning: 'in method "' + mtname + '": ' + msg at: lastMethodLine + lineNum - 1 ].
318 c := (isMeta ifTrue: [ aClass class ] ifFalse: [ aClass ]).
319 p category: category.
320 (c addMethod: body withCompiler: p) ifNil: [ self error: 'in method "' + mtname + '": compilation error' at: lastMethodLine ].
324 compileMethods: aClass [
326 [ self compileMethod: aClass ] whileTrue: [ nil ].
329 doExtend: className [
330 "parse 'extend' directive"
332 self expectWord: '['.
333 aClass := globals at: className asSymbol ifAbsent: [ self error: 'unknown class name: ' + className ].
334 self compileMethods: aClass.
338 "parse variable definitions between '|' (if any)"
341 self dontWantEof: (c := inStream readChar).
342 vars := (Array new: 0).
344 "wow! we have some vars!"
345 [ self dontWantEof: (c := self readWord).
348 c firstChar isAlphanumeric ifFalse: [ self error: 'invalid variable name: "' + c + '"' ].
349 vars := vars with: c asSymbol.
352 inStream unreadChar: c.
357 doSubclass: className asProxy: isProxy [
358 | aClass newClassName cvars ivars |
359 aClass := globals at: className asSymbol ifAbsent: [ self error: 'unknown class name: ' + className ].
360 newClassName := self readWord.
361 self dontWantEof: newClassName.
362 newClassName lastChar isAlphanumeric ifFalse: [ self error: 'invalid class name: "' + newClassName + '"' ].
363 (globals at: newClassName asSymbol ifAbsent: [ nil ]) ifNotNil: [ ('redefinition of class "' + newClassName + '"') printNl ].
364 "parse class variables (if any)"
365 cvars := self parseVarDefs.
366 self expectWord: '['.
367 "parse instance variables (if any)"
368 ivars := self parseVarDefs.
370 aClass := (aClass subclass: newClassName asSymbol variables: ivars classVariables: cvars asProxy: isProxy).
371 self compileMethods: aClass.
377 body := (self readMethodBody) removeTrailingBlanks.
378 body isEmpty ifFalse: [ body doItSeq. ]
383 "FIXME: word parsing must be strict; also process comments"
384 [ (self dontWantEof: (mname := self readWord)) = ']' ]
386 [ (self dontWantEof: (c := self lookAhead)) == $/ ] whileTrue: [
387 inStream readChar. "skip slash"
388 ('\t\n\r ]' includesChar: (self dontWantEof: (c := self lookAhead))) ifTrue: [
389 self error: 'invalid module name'
391 mname := mname + '/' + self readWord.
393 mname firstChar isAlphanumeric ifFalse: [ self error: 'module name expected' ].
394 System loadModule: mname.
400 (self dontWantEof: (aName := self readWord)) = ']' ifTrue: [ Package current: 'User'. ^self ].
401 aName firstChar isAlphanumeric ifFalse: [ self error: 'package name expected' ].
402 Package current: aName. "returns isNew"
403 (self dontWantEof: (aName := self readWord)) = ']' ifTrue: [ ^self ].
404 ((aName = 'requires') and: [ inStream readChar == $: ]) ifFalse: [ self error: '"requires:" expected' ].
405 self error: 'Package: no support for "requires:" yet!'.
406 self expectWord: ']'.
411 body := StringBuffer new.
412 [ (self dontWantEof: (c := inStream readChar)) == $} ]
415 (c == $" or: [ c == $' ]) ifTrue: [ self collectUntil: c to: body ].
417 ^(body asString) removeTrailingBlanks
423 case: 'class' do: [ self doSubclass: 'Object' asProxy: false ];
424 case: 'proxy' do: [ self doSubclass: 'Object' asProxy: true ];
425 else: [ self error: ('invalid command: ' + cmd) ].
430 case: 'Eval' do: [ self doEval ];
431 case: 'Requires' do: [ self doRequires ];
432 case: 'Package' do: [ self doSetPackage ];
433 else: [ self error: ('invalid command: ' + cmd) ].
436 doCommand2: cmd withClass: className [
437 cmd firstChar isAlphanumeric ifFalse: [ self error: 'command name expected' ].
439 case: 'extend' do: [ self doExtend: className ];
440 case: 'subclass' do: [
441 inStream readChar == $: ifFalse: [ self error: '":" expected after "subclass"' ].
442 self doSubclass: className asProxy: false.
444 else: [ self error: ('invalid command: ' + className) ].
448 | className cmd c oldpkg |
450 (c := inStream readChar) == $# ifTrue: [
451 "possible shebang, skip it"
452 [ (c := inStream readChar) ifNil: [ ^true ].
453 c isEOL ] whileFalse: [ nil ].
454 ] ifFalse: [ inStream unreadChar: c ].
456 oldpkg := Package current.
457 Package current: 'User'. "egg solution; should restore package on error"
459 [ className := self readWord ] whileNotNil: [
460 className firstChar isAlphanumeric ifTrue: [
461 (inStream unreadChar: (inStream readChar)) == $: ifTrue: [
462 self doCommand1: className
464 (self dontWantEof: (cmd := self readWord)) = '[' ifTrue: [
465 self doCommand0: className
467 self doCommand2: cmd withClass: className.
471 className = '{' ifFalse: [ self error: ('invalid command: ' + className) ].
473 c isEmpty ifFalse: [ c doItSeq ].
476 Package setCurrent: oldpkg.