1 ! Copyright (C) 2007 Chris Double.
\r
2 ! See http://factorcode.org/license.txt for BSD license.
\r
3 USING: kernel compiler.units words arrays strings math.parser
\r
4 sequences quotations vectors namespaces make math assocs
\r
5 continuations peg peg.parsers unicode.categories multiline
\r
6 splitting accessors effects sequences.deep peg.search
\r
7 combinators.short-circuit lexer io.streams.string stack-checker
\r
8 io combinators parser summary ;
\r
11 : rule ( name word -- parser )
\r
12 #! Given an EBNF word produced from EBNF: return the EBNF rule
\r
13 "ebnf-parser" word-prop at ;
\r
15 ERROR: no-rule rule parser ;
\r
17 : lookup-rule ( rule parser -- rule' )
\r
18 2dup rule [ 2nip ] [ no-rule ] if* ;
\r
20 TUPLE: tokenizer any one many ;
\r
22 : default-tokenizer ( -- tokenizer )
\r
26 [ [ = ] curry any-char swap semantic ]
\r
29 : parser-tokenizer ( parser -- tokenizer )
\r
31 [ swap [ = ] curry semantic ] curry dup \ tokenizer boa ;
\r
33 : rule-tokenizer ( name word -- tokenizer )
\r
34 rule parser-tokenizer ;
\r
36 : tokenizer ( -- word )
\r
37 \ tokenizer get-global [ default-tokenizer ] unless* ;
\r
39 : reset-tokenizer ( -- )
\r
40 default-tokenizer \ tokenizer set-global ;
\r
42 ERROR: no-tokenizer name ;
\r
44 M: no-tokenizer summary
\r
45 drop "Tokenizer not found" ;
\r
48 scan dup search [ nip ] [ no-tokenizer ] if*
\r
49 execute( -- tokenizer ) \ tokenizer set-global ;
\r
51 TUPLE: ebnf-non-terminal symbol ;
\r
52 TUPLE: ebnf-terminal symbol ;
\r
53 TUPLE: ebnf-foreign word rule ;
\r
54 TUPLE: ebnf-any-character ;
\r
55 TUPLE: ebnf-range pattern ;
\r
56 TUPLE: ebnf-ensure group ;
\r
57 TUPLE: ebnf-ensure-not group ;
\r
58 TUPLE: ebnf-choice options ;
\r
59 TUPLE: ebnf-sequence elements ;
\r
60 TUPLE: ebnf-repeat0 group ;
\r
61 TUPLE: ebnf-repeat1 group ;
\r
62 TUPLE: ebnf-optional group ;
\r
63 TUPLE: ebnf-whitespace group ;
\r
64 TUPLE: ebnf-tokenizer elements ;
\r
65 TUPLE: ebnf-rule symbol elements ;
\r
66 TUPLE: ebnf-action parser code ;
\r
67 TUPLE: ebnf-var parser name ;
\r
68 TUPLE: ebnf-semantic parser code ;
\r
71 C: <ebnf-non-terminal> ebnf-non-terminal
\r
72 C: <ebnf-terminal> ebnf-terminal
\r
73 C: <ebnf-foreign> ebnf-foreign
\r
74 C: <ebnf-any-character> ebnf-any-character
\r
75 C: <ebnf-range> ebnf-range
\r
76 C: <ebnf-ensure> ebnf-ensure
\r
77 C: <ebnf-ensure-not> ebnf-ensure-not
\r
78 C: <ebnf-choice> ebnf-choice
\r
79 C: <ebnf-sequence> ebnf-sequence
\r
80 C: <ebnf-repeat0> ebnf-repeat0
\r
81 C: <ebnf-repeat1> ebnf-repeat1
\r
82 C: <ebnf-optional> ebnf-optional
\r
83 C: <ebnf-whitespace> ebnf-whitespace
\r
84 C: <ebnf-tokenizer> ebnf-tokenizer
\r
85 C: <ebnf-rule> ebnf-rule
\r
86 C: <ebnf-action> ebnf-action
\r
87 C: <ebnf-var> ebnf-var
\r
88 C: <ebnf-semantic> ebnf-semantic
\r
91 : filter-hidden ( seq -- seq )
\r
92 #! Remove elements that produce no AST from sequence
\r
93 [ ebnf-ensure-not? not ] filter [ ebnf-ensure? not ] filter ;
\r
95 : syntax ( string -- parser )
\r
96 #! Parses the string, ignoring white space, and
\r
97 #! does not put the result in the AST.
\r
100 : syntax-pack ( begin parser end -- parser )
\r
101 #! Parse 'parser' surrounded by syntax elements
\r
103 [ syntax ] 2dip syntax pack ;
\r
105 #! Don't want to use 'replace' in an action since replace doesn't infer.
\r
106 #! Do the compilation of the peg at parse time and call (replace).
\r
107 PEG: escaper ( string -- ast )
\r
109 "\\t" token [ drop "\t" ] action ,
\r
110 "\\n" token [ drop "\n" ] action ,
\r
111 "\\r" token [ drop "\r" ] action ,
\r
112 "\\\\" token [ drop "\\" ] action ,
\r
113 ] choice* any-char-parser 2array choice repeat0 ;
\r
115 : replace-escapes ( string -- string )
\r
116 escaper sift [ [ tree-write ] each ] with-string-writer ;
\r
118 : insert-escapes ( string -- string )
\r
120 "\t" token [ drop "\\t" ] action ,
\r
121 "\n" token [ drop "\\n" ] action ,
\r
122 "\r" token [ drop "\\r" ] action ,
\r
123 ] choice* replace ;
\r
125 : 'identifier' ( -- parser )
\r
126 #! Return a parser that parses an identifer delimited by
\r
127 #! a quotation character. The quotation can be single
\r
128 #! or double quotes. The AST produced is the identifier
\r
129 #! between the quotes.
\r
131 [ CHAR: " = not ] satisfy repeat1 "\"" "\"" surrounded-by ,
\r
132 [ CHAR: ' = not ] satisfy repeat1 "'" "'" surrounded-by ,
\r
133 ] choice* [ >string replace-escapes ] action ;
\r
135 : 'non-terminal' ( -- parser )
\r
136 #! A non-terminal is the name of another rule. It can
\r
137 #! be any non-blank character except for characters used
\r
138 #! in the EBNF syntax itself.
\r
163 ] satisfy repeat1 [ >string <ebnf-non-terminal> ] action ;
\r
165 : 'terminal' ( -- parser )
\r
166 #! A terminal is an identifier enclosed in quotations
\r
167 #! and it represents the literal value of the identifier.
\r
168 'identifier' [ <ebnf-terminal> ] action ;
\r
170 : 'foreign-name' ( -- parser )
\r
171 #! Parse a valid foreign parser name
\r
177 ] satisfy repeat1 [ >string ] action ;
\r
179 : 'foreign' ( -- parser )
\r
180 #! A foreign call is a call to a rule in another ebnf grammar
\r
182 "<foreign" syntax ,
\r
183 'foreign-name' sp ,
\r
184 'foreign-name' sp optional ,
\r
186 ] seq* [ first2 <ebnf-foreign> ] action ;
\r
188 : 'any-character' ( -- parser )
\r
189 #! A parser to match the symbol for any character match.
\r
190 [ CHAR: . = ] satisfy [ drop <ebnf-any-character> ] action ;
\r
192 : 'range-parser' ( -- parser )
\r
193 #! Match the syntax for declaring character ranges
\r
195 [ "[" syntax , "[" token ensure-not , ] seq* hide ,
\r
196 [ CHAR: ] = not ] satisfy repeat1 ,
\r
198 ] seq* [ first >string <ebnf-range> ] action ;
\r
200 : ('element') ( -- parser )
\r
201 #! An element of a rule. It can be a terminal or a
\r
202 #! non-terminal but must not be followed by a "=".
\r
203 #! The latter indicates that it is the beginning of a
\r
214 [ dup , "*" token hide , ] seq* [ first <ebnf-repeat0> ] action ,
\r
215 [ dup , "+" token hide , ] seq* [ first <ebnf-repeat1> ] action ,
\r
216 [ dup , "?[" token ensure-not , "?" token hide , ] seq* [ first <ebnf-optional> ] action ,
\r
220 "=" syntax ensure-not ,
\r
221 "=>" syntax ensure ,
\r
223 ] seq* [ first ] action ;
\r
227 : 'element' ( -- parser )
\r
229 [ ('element') , ":" syntax , "a-zA-Z" range-pattern repeat1 [ >string ] action , ] seq* [ first2 <ebnf-var> ] action ,
\r
235 : grouped ( quot suffix -- parser )
\r
236 #! Parse a group of choices, with a suffix indicating
\r
237 #! the type of group (repeat0, repeat1, etc) and
\r
238 #! an quot that is the action that produces the AST.
\r
241 "(" [ 'choice' sp ] delay ")" syntax-pack
\r
243 [ first ] rot compose action ,
\r
244 "{" [ 'choice' sp ] delay "}" syntax-pack
\r
246 [ first <ebnf-whitespace> ] rot compose action ,
\r
249 : 'group' ( -- parser )
\r
250 #! A grouping with no suffix. Used for precedence.
\r
252 "*" token sp ensure-not ,
\r
253 "+" token sp ensure-not ,
\r
254 "?" token sp ensure-not ,
\r
255 ] seq* hide grouped ;
\r
257 : 'repeat0' ( -- parser )
\r
258 [ <ebnf-repeat0> ] "*" syntax grouped ;
\r
260 : 'repeat1' ( -- parser )
\r
261 [ <ebnf-repeat1> ] "+" syntax grouped ;
\r
263 : 'optional' ( -- parser )
\r
264 [ <ebnf-optional> ] "?" syntax grouped ;
\r
266 : 'factor-code' ( -- parser )
\r
268 "]]" token ensure-not ,
\r
269 "]?" token ensure-not ,
\r
270 [ drop t ] satisfy ,
\r
271 ] seq* repeat0 [ concat >string ] action ;
\r
273 : 'ensure-not' ( -- parser )
\r
274 #! Parses the '!' syntax to ensure that
\r
275 #! something that matches the following elements do
\r
276 #! not exist in the parse stream.
\r
280 ] seq* [ first <ebnf-ensure-not> ] action ;
\r
282 : 'ensure' ( -- parser )
\r
283 #! Parses the '&' syntax to ensure that
\r
284 #! something that matches the following elements does
\r
285 #! exist in the parse stream.
\r
289 ] seq* [ first <ebnf-ensure> ] action ;
\r
291 : ('sequence') ( -- parser )
\r
292 #! A sequence of terminals and non-terminals, including
\r
293 #! groupings of those.
\r
304 [ dup , ":" syntax , "a-zA-Z" range-pattern repeat1 [ >string ] action , ] seq* [ first2 <ebnf-var> ] action ,
\r
308 : 'action' ( -- parser )
\r
309 "[[" 'factor-code' "]]" syntax-pack ;
\r
311 : 'semantic' ( -- parser )
\r
312 "?[" 'factor-code' "]?" syntax-pack ;
\r
314 : 'sequence' ( -- parser )
\r
315 #! A sequence of terminals and non-terminals, including
\r
316 #! groupings of those.
\r
318 [ ('sequence') , 'action' , ] seq* [ first2 <ebnf-action> ] action ,
\r
319 [ ('sequence') , 'semantic' , ] seq* [ first2 <ebnf-semantic> ] action ,
\r
321 ] choice* repeat1 [
\r
322 dup length 1 = [ first ] [ <ebnf-sequence> ] if
\r
325 : 'actioned-sequence' ( -- parser )
\r
327 [ 'sequence' , "=>" syntax , 'action' , ] seq* [ first2 <ebnf-action> ] action ,
\r
331 : 'choice' ( -- parser )
\r
332 'actioned-sequence' sp repeat1 [ dup length 1 = [ first ] [ <ebnf-sequence> ] if ] action "|" token sp list-of [
\r
333 dup length 1 = [ first ] [ <ebnf-choice> ] if
\r
336 : 'tokenizer' ( -- parser )
\r
338 "tokenizer" syntax ,
\r
340 ">" token ensure-not ,
\r
341 [ "default" token sp , 'choice' , ] choice* ,
\r
342 ] seq* [ first <ebnf-tokenizer> ] action ;
\r
344 : 'rule' ( -- parser )
\r
346 "tokenizer" token ensure-not ,
\r
347 'non-terminal' [ symbol>> ] action ,
\r
349 ">" token ensure-not ,
\r
351 ] seq* [ first2 <ebnf-rule> ] action ;
\r
353 : 'ebnf' ( -- parser )
\r
354 [ 'tokenizer' sp , 'rule' sp , ] choice* repeat1 [ <ebnf> ] action ;
\r
356 GENERIC: (transform) ( ast -- parser )
\r
362 : transform ( ast -- object )
\r
363 H{ } clone dup dup [
\r
370 M: ebnf (transform) ( ast -- parser )
\r
371 rules>> [ (transform) ] map peek ;
\r
373 M: ebnf-tokenizer (transform) ( ast -- parser )
\r
374 elements>> dup "default" = [
\r
375 drop default-tokenizer \ tokenizer set-global any-char
\r
378 dup parser-tokenizer \ tokenizer set-global
\r
381 ERROR: redefined-rule name ;
\r
383 M: redefined-rule summary
\r
384 name>> "Rule '" "' defined more than once" surround ;
\r
386 M: ebnf-rule (transform) ( ast -- parser )
\r
389 swap symbol>> dup get parser? [ redefined-rule ] [ set ] if
\r
392 M: ebnf-sequence (transform) ( ast -- parser )
\r
393 #! If ignore-ws is set then each element of the sequence
\r
394 #! ignores leading whitespace. This is not inherited by
\r
395 #! subelements of the sequence.
\r
397 f ignore-ws [ (transform) ] with-variable
\r
398 ignore-ws get [ sp ] when
\r
399 ] map seq [ dup length 1 = [ first ] when ] action ;
\r
401 M: ebnf-choice (transform) ( ast -- parser )
\r
402 options>> [ (transform) ] map choice ;
\r
404 M: ebnf-any-character (transform) ( ast -- parser )
\r
405 drop tokenizer any>> call( -- parser ) ;
\r
407 M: ebnf-range (transform) ( ast -- parser )
\r
408 pattern>> range-pattern ;
\r
410 : transform-group ( ast -- parser )
\r
411 #! convert a ast node with groups to a parser for that group
\r
412 group>> (transform) ;
\r
414 M: ebnf-ensure (transform) ( ast -- parser )
\r
415 transform-group ensure ;
\r
417 M: ebnf-ensure-not (transform) ( ast -- parser )
\r
418 transform-group ensure-not ;
\r
420 M: ebnf-repeat0 (transform) ( ast -- parser )
\r
421 transform-group repeat0 ;
\r
423 M: ebnf-repeat1 (transform) ( ast -- parser )
\r
424 transform-group repeat1 ;
\r
426 M: ebnf-optional (transform) ( ast -- parser )
\r
427 transform-group optional ;
\r
429 M: ebnf-whitespace (transform) ( ast -- parser )
\r
430 t ignore-ws [ transform-group ] with-variable ;
\r
432 GENERIC: build-locals ( code ast -- code )
\r
434 M: ebnf-sequence build-locals ( code ast -- code )
\r
435 #! Note the need to filter out this ebnf items that
\r
436 #! leave nothing in the AST
\r
437 elements>> filter-hidden dup length 1 = [
\r
438 first build-locals
\r
440 dup [ ebnf-var? ] filter empty? [
\r
444 "USING: locals sequences ; [let* | " %
\r
448 " [ " % # " over nth ] " %
\r
460 M: ebnf-var build-locals ( code ast -- )
\r
462 "USING: locals kernel ; [let* | " %
\r
463 name>> % " [ dup ] " %
\r
469 M: object build-locals ( code ast -- )
\r
472 ERROR: bad-effect quot effect ;
\r
474 : check-action-effect ( quot -- quot )
\r
476 { [ dup (( a -- b )) effect<= ] [ drop ] }
\r
477 { [ dup (( -- b )) effect<= ] [ drop [ drop ] prepose ] }
\r
481 : ebnf-transform ( ast -- parser quot )
\r
482 [ parser>> (transform) ]
\r
483 [ code>> insert-escapes ]
\r
484 [ parser>> ] tri build-locals
\r
485 [ string-lines parse-lines ] call( string -- quot ) ;
\r
487 M: ebnf-action (transform) ( ast -- parser )
\r
488 ebnf-transform check-action-effect action ;
\r
490 M: ebnf-semantic (transform) ( ast -- parser )
\r
491 ebnf-transform semantic ;
\r
493 M: ebnf-var (transform) ( ast -- parser )
\r
494 parser>> (transform) ;
\r
496 M: ebnf-terminal (transform) ( ast -- parser )
\r
497 symbol>> tokenizer one>> call( symbol -- parser ) ;
\r
499 ERROR: ebnf-foreign-not-found name ;
\r
501 M: ebnf-foreign-not-found summary
\r
502 name>> "Foreign word '" "' not found" surround ;
\r
504 M: ebnf-foreign (transform) ( ast -- parser )
\r
505 dup word>> search [ word>> ebnf-foreign-not-found ] unless*
\r
506 swap rule>> [ main ] unless* over rule [
\r
509 execute( -- parser )
\r
512 ERROR: parser-not-found name ;
\r
514 M: ebnf-non-terminal (transform) ( ast -- parser )
\r
516 , \ dup , parser get , \ at , [ parser-not-found ] , \ unless* , \ nip ,
\r
519 : transform-ebnf ( string -- object )
\r
520 'ebnf' parse transform ;
\r
522 : check-parse-result ( result -- result )
\r
524 dup remaining>> [ blank? ] trim [
\r
526 "Unable to fully parse EBNF. Left to parse was: " %
\r
531 "Could not parse EBNF" throw
\r
534 : parse-ebnf ( string -- hashtable )
\r
535 'ebnf' (parse) check-parse-result ast>> transform ;
\r
537 : ebnf>quot ( string -- hashtable quot )
\r
538 parse-ebnf dup dup parser [ main swap at compile ] with-variable
\r
539 [ compiled-parse ] curry [ with-scope ast>> ] curry ;
\r
543 reset-tokenizer parse-multiline-string parse-ebnf main swap at
\r
544 parsed reset-tokenizer ;
\r
548 reset-tokenizer parse-multiline-string ebnf>quot nip
\r
549 parsed \ call parsed reset-tokenizer ;
\r
552 reset-tokenizer CREATE-WORD dup ";EBNF" parse-multiline-string
\r
554 (( input -- ast )) define-declared "ebnf-parser" set-word-prop
\r