smalltalk: working on lexical scoping for instance variables and class names
parent
4a0ef8d0bc
commit
5b6948aaa5
|
@ -14,5 +14,7 @@ TUPLE: ast-assignment { name ast-name } value ;
|
||||||
TUPLE: ast-local-variables { names array } ;
|
TUPLE: ast-local-variables { names array } ;
|
||||||
TUPLE: ast-method { name string } { body ast-block } ;
|
TUPLE: ast-method { name string } { body ast-block } ;
|
||||||
TUPLE: ast-class { name string } { superclass string } { ivars array } { methods array } ;
|
TUPLE: ast-class { name string } { superclass string } { ivars array } { methods array } ;
|
||||||
|
TUPLE: ast-foreign { class string } { name string } ;
|
||||||
|
|
||||||
TUPLE: symbol { name string } ;
|
TUPLE: symbol { name string } ;
|
||||||
MEMO: intern ( name -- symbol ) symbol boa ;
|
MEMO: intern ( name -- symbol ) symbol boa ;
|
|
@ -0,0 +1 @@
|
||||||
|
Slava Pestov
|
|
@ -0,0 +1,25 @@
|
||||||
|
! Copyright (C) 2009 Slava Pestov.
|
||||||
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
|
USING: kernel namespaces assocs accessors words sequences classes.tuple ;
|
||||||
|
IN: smalltalk.classes
|
||||||
|
|
||||||
|
SYMBOL: classes
|
||||||
|
|
||||||
|
classes [ H{ } clone ] initialize
|
||||||
|
|
||||||
|
: create-class ( class -- class )
|
||||||
|
"smalltalk.classes" create ;
|
||||||
|
|
||||||
|
ERROR: no-class name ;
|
||||||
|
|
||||||
|
: lookup-class ( class -- class )
|
||||||
|
classes get ?at [ ] [ no-class ] if ;
|
||||||
|
|
||||||
|
: define-class ( class superclass ivars -- class-word )
|
||||||
|
[ create-class ] [ lookup-class ] [ ] tri*
|
||||||
|
[ define-tuple-class ] [ 2drop dup dup name>> classes get set-at ] 3bi ;
|
||||||
|
|
||||||
|
: define-foreign ( class name -- )
|
||||||
|
classes get set-at ;
|
||||||
|
|
||||||
|
tuple "Object" define-foreign
|
|
@ -1,12 +1,10 @@
|
||||||
USING: smalltalk.compiler tools.test prettyprint smalltalk.ast
|
USING: smalltalk.compiler tools.test prettyprint smalltalk.ast
|
||||||
stack-checker locals.rewrite.closures kernel accessors
|
smalltalk.compiler.lexenv stack-checker locals.rewrite.closures
|
||||||
compiler.units sequences ;
|
kernel accessors compiler.units sequences ;
|
||||||
IN: smalltalk.compiler.tests
|
IN: smalltalk.compiler.tests
|
||||||
|
|
||||||
: test-compilation ( ast -- quot )
|
: test-compilation ( ast -- quot )
|
||||||
[
|
[ compile-smalltalk [ call ] append ] with-compilation-unit ;
|
||||||
compile-method rewrite-closures first
|
|
||||||
] with-compilation-unit ;
|
|
||||||
|
|
||||||
: test-inference ( ast -- in# out# )
|
: test-inference ( ast -- in# out# )
|
||||||
test-compilation infer [ in>> ] [ out>> ] bi ;
|
test-compilation infer [ in>> ] [ out>> ] bi ;
|
||||||
|
@ -31,7 +29,7 @@ IN: smalltalk.compiler.tests
|
||||||
T{ ast-assignment f
|
T{ ast-assignment f
|
||||||
T{ ast-name f "a" }
|
T{ ast-name f "a" }
|
||||||
T{ ast-message-send f
|
T{ ast-message-send f
|
||||||
T{ ast-name f "asmal" }
|
T{ ast-name f "c" }
|
||||||
"+"
|
"+"
|
||||||
{ T{ ast-name f "b" } }
|
{ T{ ast-name f "b" } }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
! See http://factorcode.org/license.txt for BSD license.
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
USING: accessors arrays assocs combinators.short-circuit
|
USING: accessors arrays assocs combinators.short-circuit
|
||||||
continuations fry kernel namespaces quotations sequences sets
|
continuations fry kernel namespaces quotations sequences sets
|
||||||
generalizations slots locals.types generalizations smalltalk.ast
|
generalizations slots locals.types generalizations splitting math
|
||||||
smalltalk.compiler.lexenv smalltalk.selectors ;
|
locals.rewrite.closures generic words smalltalk.ast
|
||||||
|
smalltalk.compiler.lexenv smalltalk.selectors
|
||||||
|
smalltalk.classes ;
|
||||||
IN: smalltalk.compiler
|
IN: smalltalk.compiler
|
||||||
|
|
||||||
SYMBOL: return-continuation
|
SYMBOL: return-continuation
|
||||||
|
@ -52,10 +54,11 @@ GENERIC: compile-ast ( lexenv ast -- quot )
|
||||||
|
|
||||||
M: object compile-ast nip 1quotation ;
|
M: object compile-ast nip 1quotation ;
|
||||||
|
|
||||||
|
M: self compile-ast drop self>> 1quotation ;
|
||||||
|
|
||||||
ERROR: unbound-local name ;
|
ERROR: unbound-local name ;
|
||||||
|
|
||||||
M: ast-name compile-ast
|
M: ast-name compile-ast name>> swap lookup-reader ;
|
||||||
name>> swap local-readers>> at 1quotation ;
|
|
||||||
|
|
||||||
M: ast-message-send compile-ast
|
M: ast-message-send compile-ast
|
||||||
[ arguments>> [ compile-ast ] with map [ ] join ]
|
[ arguments>> [ compile-ast ] with map [ ] join ]
|
||||||
|
@ -79,14 +82,11 @@ M: array compile-ast
|
||||||
dup contains-blocks? [
|
dup contains-blocks? [
|
||||||
[ [ compile-ast ] with map [ ] join ] [ length ] bi
|
[ [ compile-ast ] with map [ ] join ] [ length ] bi
|
||||||
'[ @ _ narray ]
|
'[ @ _ narray ]
|
||||||
] [
|
] [ call-next-method ] if ;
|
||||||
call-next-method
|
|
||||||
] if ;
|
|
||||||
|
|
||||||
GENERIC: compile-assignment ( lexenv name -- quot )
|
GENERIC: compile-assignment ( lexenv name -- quot )
|
||||||
|
|
||||||
M: ast-name compile-assignment
|
M: ast-name compile-assignment name>> swap lookup-writer ;
|
||||||
name>> swap local-writers>> at 1quotation ;
|
|
||||||
|
|
||||||
M: ast-assignment compile-ast
|
M: ast-assignment compile-ast
|
||||||
[ value>> compile-ast [ dup ] ] [ name>> compile-assignment ] 2bi 3append ;
|
[ value>> compile-ast [ dup ] ] [ name>> compile-assignment ] 2bi 3append ;
|
||||||
|
@ -102,30 +102,62 @@ M: ast-assignment compile-ast
|
||||||
dup
|
dup
|
||||||
[ nip local-reader? ] assoc-filter
|
[ nip local-reader? ] assoc-filter
|
||||||
[ <local-writer> ] assoc-map
|
[ <local-writer> ] assoc-map
|
||||||
<lexenv> ;
|
<lexenv> swap >>local-writers swap >>local-readers ;
|
||||||
|
|
||||||
M: ast-block compile-ast
|
: compile-block ( lexenv block -- vars body )
|
||||||
[
|
[
|
||||||
block-lexenv
|
block-lexenv
|
||||||
[ nip local-readers>> values ]
|
[ nip local-readers>> values ]
|
||||||
[ lexenv-union ] 2bi
|
[ lexenv-union ] 2bi
|
||||||
] [ body>> ] bi
|
] [ body>> ] bi
|
||||||
[ drop [ nil ] ] [
|
[ drop [ nil ] ] [ [ compile-ast ] with map [ drop ] join ] if-empty ;
|
||||||
unclip-last
|
|
||||||
[ [ compile-ast [ drop ] append ] with map [ ] join ]
|
|
||||||
[ compile-ast ]
|
|
||||||
bi-curry* bi
|
|
||||||
append
|
|
||||||
] if-empty
|
|
||||||
<lambda> '[ _ ] ;
|
|
||||||
|
|
||||||
: compile-method ( block -- quot )
|
M: ast-block compile-ast
|
||||||
[ [ empty-lexenv ] dip compile-ast [ call ] compose ]
|
compile-block <lambda> '[ _ ] ;
|
||||||
[ arguments>> length ]
|
|
||||||
[ need-return-continuation? ]
|
|
||||||
tri
|
|
||||||
[ '[ [ _ _ ncurry [ return-continuation set ] prepose callcc1 ] with-scope ] ] [ drop ] if ;
|
|
||||||
|
|
||||||
: compile-statement ( statement -- quot )
|
: make-return ( quot n block -- quot )
|
||||||
[ [ empty-lexenv ] dip compile-ast ] [ need-return-continuation? ] bi
|
need-return-continuation? [
|
||||||
[ '[ [ [ return-continuation set @ ] callcc1 ] with-scope ] ] when ;
|
'[
|
||||||
|
[
|
||||||
|
_ _ ncurry
|
||||||
|
[ return-continuation set ] prepose callcc1
|
||||||
|
] with-scope
|
||||||
|
]
|
||||||
|
] [ drop ] if
|
||||||
|
rewrite-closures first ;
|
||||||
|
|
||||||
|
GENERIC: compile-smalltalk ( ast -- quot )
|
||||||
|
|
||||||
|
M: object compile-smalltalk ( statement -- quot )
|
||||||
|
[ [ empty-lexenv ] dip compile-ast 0 ] keep make-return ;
|
||||||
|
|
||||||
|
: (compile-method-body) ( lexenv block -- lambda )
|
||||||
|
[ drop self>> ] [ compile-block ] 2bi [ swap suffix ] dip <lambda> ;
|
||||||
|
|
||||||
|
: compile-method-body ( lexenv block -- quot )
|
||||||
|
[ [ (compile-method-body) ] [ arguments>> length 1+ ] bi ] keep
|
||||||
|
make-return ;
|
||||||
|
|
||||||
|
: compile-method ( lexenv ast-method -- )
|
||||||
|
[ [ class>> ] [ name>> selector>generic ] bi* create-method ]
|
||||||
|
[ body>> compile-method-body ]
|
||||||
|
2bi define ;
|
||||||
|
|
||||||
|
: <class-lexenv> ( class -- lexenv )
|
||||||
|
<lexenv> swap >>class "self" <local-reader> >>self ;
|
||||||
|
|
||||||
|
M: ast-class compile-smalltalk ( ast-class -- quot )
|
||||||
|
[
|
||||||
|
[ name>> ] [ superclass>> ] [ ivars>> ] tri
|
||||||
|
define-class <class-lexenv>
|
||||||
|
]
|
||||||
|
[ methods>> ] bi
|
||||||
|
[ compile-method ] with each
|
||||||
|
[ nil ] ;
|
||||||
|
|
||||||
|
ERROR: no-word name ;
|
||||||
|
|
||||||
|
M: ast-foreign compile-smalltalk
|
||||||
|
[ class>> dup ":" split1 lookup [ ] [ no-word ] ?if ]
|
||||||
|
[ name>> ] bi define-foreign
|
||||||
|
[ nil ] ;
|
|
@ -0,0 +1,24 @@
|
||||||
|
USING: smalltalk.compiler.lexenv tools.test kernel namespaces accessors ;
|
||||||
|
IN: smalltalk.compiler.lexenv.tests
|
||||||
|
|
||||||
|
TUPLE: some-class x y z ;
|
||||||
|
|
||||||
|
SYMBOL: fake-self
|
||||||
|
|
||||||
|
SYMBOL: fake-local
|
||||||
|
|
||||||
|
<lexenv>
|
||||||
|
some-class >>class
|
||||||
|
fake-self >>self
|
||||||
|
H{ { "mumble" fake-local } } >>local-readers
|
||||||
|
H{ { "jumble" fake-local } } >>local-writers
|
||||||
|
lexenv set
|
||||||
|
|
||||||
|
[ [ fake-local ] ] [ "mumble" lexenv get lookup-reader ] unit-test
|
||||||
|
[ [ fake-self x>> ] ] [ "x" lexenv get lookup-reader ] unit-test
|
||||||
|
[ [ \ tuple ] ] [ "Object" lexenv get lookup-reader ] unit-test
|
||||||
|
|
||||||
|
[ [ fake-local ] ] [ "jumble" lexenv get lookup-writer ] unit-test
|
||||||
|
[ [ fake-self (>>y) ] ] [ "y" lexenv get lookup-writer ] unit-test
|
||||||
|
|
||||||
|
[ "blahblah" lexenv get lookup-writer ] must-fail
|
|
@ -1,6 +1,8 @@
|
||||||
! Copyright (C) 2009 Slava Pestov.
|
! Copyright (C) 2009 Slava Pestov.
|
||||||
! See http://factorcode.org/license.txt for BSD license.
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
USING: assocs kernel accessors ;
|
USING: assocs kernel accessors quotations slots words
|
||||||
|
sequences namespaces combinators combinators.short-circuit
|
||||||
|
smalltalk.classes ;
|
||||||
IN: smalltalk.compiler.lexenv
|
IN: smalltalk.compiler.lexenv
|
||||||
|
|
||||||
! local-readers: assoc string => word
|
! local-readers: assoc string => word
|
||||||
|
@ -10,11 +12,53 @@ IN: smalltalk.compiler.lexenv
|
||||||
! method: generic word or f for top-level forms
|
! method: generic word or f for top-level forms
|
||||||
TUPLE: lexenv local-readers local-writers self class method ;
|
TUPLE: lexenv local-readers local-writers self class method ;
|
||||||
|
|
||||||
: <lexenv> ( local-readers local-writers -- lexenv )
|
: <lexenv> ( -- lexenv ) lexenv new ; inline
|
||||||
f f f lexenv boa ; inline
|
|
||||||
|
|
||||||
CONSTANT: empty-lexenv T{ lexenv }
|
CONSTANT: empty-lexenv T{ lexenv }
|
||||||
|
|
||||||
: lexenv-union ( lexenv1 lexenv2 -- lexenv )
|
: lexenv-union ( lexenv1 lexenv2 -- lexenv )
|
||||||
[ [ local-readers>> ] bi@ assoc-union ]
|
[ <lexenv> ] 2dip {
|
||||||
[ [ local-writers>> ] bi@ assoc-union ] 2bi <lexenv> ;
|
[ [ local-readers>> ] bi@ assoc-union >>local-readers ]
|
||||||
|
[ [ local-writers>> ] bi@ assoc-union >>local-writers ]
|
||||||
|
[ [ self>> ] either? >>self ]
|
||||||
|
[ [ class>> ] either? >>class ]
|
||||||
|
[ [ method>> ] either? >>method ]
|
||||||
|
} 2cleave ;
|
||||||
|
|
||||||
|
: local-reader ( name lexenv -- local )
|
||||||
|
local-readers>> at dup [ 1quotation ] when ;
|
||||||
|
|
||||||
|
: ivar-reader ( name lexenv -- quot/f )
|
||||||
|
dup class>> [
|
||||||
|
[ class>> "slots" word-prop slot-named ] [ self>> ] bi
|
||||||
|
swap dup [ name>> reader-word [ ] 2sequence ] [ 2drop f ] if
|
||||||
|
] [ 2drop f ] if ;
|
||||||
|
|
||||||
|
: class-name ( name -- quot/f )
|
||||||
|
classes get at dup [ [ ] curry ] when ;
|
||||||
|
|
||||||
|
ERROR: bad-identifier name ;
|
||||||
|
|
||||||
|
: lookup-reader ( name lexenv -- reader-quot )
|
||||||
|
{
|
||||||
|
[ local-reader ]
|
||||||
|
[ ivar-reader ]
|
||||||
|
[ drop class-name ]
|
||||||
|
[ drop bad-identifier ]
|
||||||
|
} 2|| ;
|
||||||
|
|
||||||
|
: local-writer ( name lexenv -- local )
|
||||||
|
local-writers>> at dup [ 1quotation ] when ;
|
||||||
|
|
||||||
|
: ivar-writer ( name lexenv -- quot/f )
|
||||||
|
dup class>> [
|
||||||
|
[ class>> "slots" word-prop slot-named ] [ self>> ] bi
|
||||||
|
swap dup [ name>> writer-word [ ] 2sequence ] [ 2drop f ] if
|
||||||
|
] [ 2drop f ] if ;
|
||||||
|
|
||||||
|
: lookup-writer ( name lexenv -- writer-quot )
|
||||||
|
{
|
||||||
|
[ local-writer ]
|
||||||
|
[ ivar-writer ]
|
||||||
|
[ drop bad-identifier ]
|
||||||
|
} 2|| ;
|
|
@ -1,7 +1,7 @@
|
||||||
! Copyright (C) 2009 Slava Pestov.
|
! Copyright (C) 2009 Slava Pestov.
|
||||||
! See http://factorcode.org/license.txt for BSD license.
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
USING: kernel present io math sequences assocs math.ranges
|
USING: kernel present io math sequences assocs math.ranges
|
||||||
locals smalltalk.selectors smalltalk.ast ;
|
locals smalltalk.selectors smalltalk.ast smalltalk.classes ;
|
||||||
IN: smalltalk.library
|
IN: smalltalk.library
|
||||||
|
|
||||||
! Some unary selectors
|
! Some unary selectors
|
||||||
|
@ -73,3 +73,7 @@ M: object selector-value: call( input -- result ) ;
|
||||||
M: object selector-value:value: call( input input -- result ) ;
|
M: object selector-value:value: call( input input -- result ) ;
|
||||||
M: object selector-value:value:value: call( input input input -- result ) ;
|
M: object selector-value:value:value: call( input input input -- result ) ;
|
||||||
M: object selector-value:value:value:value: call( input input input input -- result ) ;
|
M: object selector-value:value:value:value: call( input input input input -- result ) ;
|
||||||
|
|
||||||
|
SELECTOR: new
|
||||||
|
|
||||||
|
M: object selector-new new ;
|
|
@ -7,7 +7,7 @@ IN: smalltalk.listener
|
||||||
|
|
||||||
: eval-smalltalk ( string -- )
|
: eval-smalltalk ( string -- )
|
||||||
[
|
[
|
||||||
parse-smalltalk-statement compile-statement rewrite-closures first
|
parse-smalltalk compile-smalltalk
|
||||||
] with-compilation-unit call( -- result )
|
] with-compilation-unit call( -- result )
|
||||||
dup nil? [ drop ] [ "Result: " write smalltalk>string print ] if ;
|
dup nil? [ drop ] [ "Result: " write smalltalk>string print ] if ;
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,13 @@ test = <foreign parse-smalltalk Literal>
|
||||||
]
|
]
|
||||||
[ "[ :i | i print ]" test-Literal ] unit-test
|
[ "[ :i | i print ]" test-Literal ] unit-test
|
||||||
|
|
||||||
|
[
|
||||||
|
T{ ast-block
|
||||||
|
{ body { 5 self } }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
[ "[5. self]" test-Literal ] unit-test
|
||||||
|
|
||||||
EBNF: test-FormalBlockArgumentDeclarationList
|
EBNF: test-FormalBlockArgumentDeclarationList
|
||||||
test = <foreign parse-smalltalk FormalBlockArgumentDeclarationList>
|
test = <foreign parse-smalltalk FormalBlockArgumentDeclarationList>
|
||||||
;EBNF
|
;EBNF
|
||||||
|
@ -207,4 +214,15 @@ test = <foreign parse-smalltalk KeywordMessageSend>
|
||||||
]
|
]
|
||||||
[ "3 factorial + 4 factorial between: 10 and: 100" test-KeywordMessageSend ] unit-test
|
[ "3 factorial + 4 factorial between: 10 and: 100" test-KeywordMessageSend ] unit-test
|
||||||
|
|
||||||
|
[ { 1 2 } ] [ "1. 2" parse-smalltalk ] unit-test
|
||||||
|
|
||||||
|
[
|
||||||
|
T{ ast-class
|
||||||
|
{ name "Test" }
|
||||||
|
{ superclass "Object" }
|
||||||
|
{ ivars { "a" } }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
[ "class Test [|a|]" parse-smalltalk ] unit-test
|
||||||
|
|
||||||
[ ] [ "vocab:smalltalk/parser/test.st" ascii file-contents parse-smalltalk drop ] unit-test
|
[ ] [ "vocab:smalltalk/parser/test.st" ascii file-contents parse-smalltalk drop ] unit-test
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
! Copyright (C) 2009 Slava Pestov.
|
! Copyright (C) 2009 Slava Pestov.
|
||||||
! See http://factorcode.org/license.txt for BSD license.
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
USING: peg peg.ebnf smalltalk.ast sequences sequences.deep strings
|
USING: peg peg.ebnf smalltalk.ast sequences sequences.deep strings
|
||||||
math.parser kernel arrays byte-arrays math assocs ;
|
math.parser kernel arrays byte-arrays math assocs accessors ;
|
||||||
IN: smalltalk.parser
|
IN: smalltalk.parser
|
||||||
|
|
||||||
! Based on http://chronos-st.blogspot.com/2007/12/smalltalk-in-one-page.html
|
! Based on http://chronos-st.blogspot.com/2007/12/smalltalk-in-one-page.html
|
||||||
|
@ -189,28 +189,23 @@ MethodDeclaration = OptionalWhiteSpace "method" OptionalWhiteSpace MethodHeader:
|
||||||
OptionalWhiteSpace "["
|
OptionalWhiteSpace "["
|
||||||
ExecutableCode:code
|
ExecutableCode:code
|
||||||
OptionalWhiteSpace "]"
|
OptionalWhiteSpace "]"
|
||||||
=> [[ header first2 "self" suffix code ast-block boa ast-method boa ]]
|
=> [[ header first2 code ast-block boa ast-method boa ]]
|
||||||
|
|
||||||
ClassDeclaration = OptionalWhiteSpace "class" OptionalWhiteSpace Identifier:name
|
ClassDeclaration = OptionalWhiteSpace "class" OptionalWhiteSpace Identifier:name
|
||||||
OptionalWhiteSpace
|
OptionalWhiteSpace
|
||||||
("extends" OptionalWhiteSpace Identifier:superclass OptionalWhiteSpace => [[ superclass ]])?:superclass
|
("extends" OptionalWhiteSpace Identifier:superclass OptionalWhiteSpace => [[ superclass ]])?:superclass
|
||||||
OptionalWhiteSpace "["
|
OptionalWhiteSpace "["
|
||||||
(OptionalWhiteSpace LocalVariableDeclarationList)?:ivars
|
(OptionalWhiteSpace LocalVariableDeclarationList:l => [[ l names>> ]])?:ivars
|
||||||
(MethodDeclaration:h (OptionalWhiteSpace MethodDeclaration:m => [[ m ]])*:t => [[ t h prefix >array ]])?:methods
|
(MethodDeclaration:h (OptionalWhiteSpace MethodDeclaration:m => [[ m ]])*:t => [[ t h prefix ]])?:methods
|
||||||
OptionalWhiteSpace "]"
|
OptionalWhiteSpace "]"
|
||||||
=> [[ name superclass "Object" or ivars methods ast-class boa ]]
|
=> [[ name superclass "Object" or ivars >array methods >array ast-class boa ]]
|
||||||
|
|
||||||
|
ForeignClassDeclaration = OptionalWhiteSpace "foreign"
|
||||||
|
OptionalWhiteSpace Identifier:name
|
||||||
|
OptionalWhiteSpace Literal:class
|
||||||
|
=> [[ class name ast-foreign boa ]]
|
||||||
End = !(.)
|
End = !(.)
|
||||||
|
|
||||||
Program = ClassDeclaration* End
|
Program = (ClassDeclaration|ForeignClassDeclaration|ExecutableCode) => [[ nil or ]] End
|
||||||
;EBNF
|
|
||||||
|
|
||||||
EBNF: parse-smalltalk-statement
|
|
||||||
|
|
||||||
Statement = <foreign parse-smalltalk Statement>
|
|
||||||
|
|
||||||
End = !(.)
|
|
||||||
|
|
||||||
Program = Statement? => [[ nil or ]] End
|
|
||||||
|
|
||||||
;EBNF
|
;EBNF
|
Loading…
Reference in New Issue