1
votes

I am writing simple smalltalk-like grammar using antlr. It is simplified version of smalltalk, but basic ideas are the same (message passing for example).

Here is my grammar so far:

grammar GAL;

options {
    //k=2;
    backtrack=true;
}

ID  :   ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT :   '0'..'9'+
    ;

FLOAT
    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
    |   '.' ('0'..'9')+ EXPONENT?
    |   ('0'..'9')+ EXPONENT
    ;

COMMENT
    :   '"' ( options {greedy=false;} : . )* '"' {$channel=HIDDEN;}
    ;

WS  :   ( ' '
        | '\t'
        ) {$channel=HIDDEN;}
    ;

NEW_LINE
    :   ('\r'?'\n')
    ;

STRING
    :  '\'' ( ESC_SEQ | ~('\\'|'\'') )* '\''
    ;

fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

BINARY_MESSAGE_CHAR
    :   ('~' | '!' | '@' | '%' | '&' | '*' | '-' | '+' | '=' | '|' | '\\' | '<' | '>' | ',' | '?' | '/')
        ('~' | '!' | '@' | '%' | '&' | '*' | '-' | '+' | '=' | '|' | '\\' | '<' | '>' | ',' | '?' | '/')?
    ;

// parser

program
    :   NEW_LINE* (statement (NEW_LINE+ | EOF))*
    ;

statement

    :   message_sending
    |   return_statement
    |   assignment
    |   temp_variables
    ;

return_statement
    :   '^' statement
    ;

assignment
    :   identifier ':=' statement
    ;

temp_variables
    :   '|' identifier+ '|'
    ;

object
    :   raw_object
    ;

raw_object
    :   number
    |   string
    |   identifier
    |   literal
    |   block
    |   '(' message_sending ')'
    ;

message_sending
    :   keyword_message_sending
    ;

keyword_message_sending
    :   binary_message_sending keyword_message?
    ;

binary_message_sending
    :   unary_message_sending binary_message*
    ;

unary_message_sending
    :   object (unary_message)*
    ;

unary_message
    :   unary_message_selector
    ;

binary_message
    :   binary_message_selector unary_message_sending
    ;

keyword_message
    :   (NEW_LINE? single_keyword_message_selector NEW_LINE? binary_message_sending)+
    ;

block 
    : 
      '[' (block_signiture

      )? NEW_LINE* 
      block_body

      NEW_LINE* ']'
    ;

block_body 
    :  (statement 

      )?
      (NEW_LINE+ statement 

      )*
    ;


block_signiture 
    : 
      (':' identifier

      )+ '|'
    ;

unary_message_selector
    :   identifier
    ;

binary_message_selector
    :   BINARY_MESSAGE_CHAR
    ;

single_keyword_message_selector
    :   identifier ':'
    ;

keyword_message_selector
    :   single_keyword_message_selector+
    ;

symbol
    :   '#' (string | identifier | binary_message_selector | keyword_message_selector)
    ; 

literal
    :   symbol block? // if there is block then this is method
    ;

number
    : /*'-'?*/
    ( INT | FLOAT )
    ;

string
    :   STRING
    ;

identifier
    :   ID
    ;

1. Unary Minus

I have a problem with unary minus for numbers (commented part for rule number). The problem is that minus is valid binary message. To make things worse two minus signs are also valid binary message. What I need is unary minus in case where there is no object to send binary message to (for example, -3+4 should be unary minus because there is nothing in frot of -3). Also, (-3) should be binary minus too. It would be great if 1 -- -2 would be binary message '--' with parameter -2, but I can live without that. How can I do this?

If I uncomment unary minus I get error MismatchedSetException(0!=null) when parsing something like 1-2.

2. Message chaining

What would be best way to implement message chainging like in smalltalk? What I mean by this is something like this:

obj message1 + 3; 
    message2; 
    + 3; 
    keyword: 2+3

where every message would be sent to the same object, in this case obj. Message precedence should be kept (unary > binary > keyword).

3. Backtrack

Most of this grammar can be parsed with k=2, but when input is something like this:

1 + 2
Obj message: 
    1 + 2
    message2: 'string'

parser tries to match Obj as single_keyword_message_selector and raises UnwantedTokenExcaption on token message. If remove k=2 and set backtrack=true (as I did) everything works as it should. How can I remove backtrack and get desired behaviour?

Also, most of the grammar can be parsed using k=1, so I tried to set k=2 only for rules that require it, but that is ignored. I did something like this:

rule
    options { k = 2; }
    : // rule definition
    ;

but it doesn't work until I set k in global options. What am I missing here?


Update:

It is not ideal solution to write grammar from scratch, because I have a lot of code that depends on it. Also, some features of smalltalk that are missing - are missing by design. This is not intended to be another smalltalk implementation, smalltalk was just an inspiration.

I would be more then happy to have unary minus working in cases like this: -1+2 or 2+(-1). Cases like 2 -- -1 are just not so important.

Also, message chaining is something that should be done as simple as posible. That means that I don't like idea of changeing AST I am generating.

About backtrack - I can live with it, just asked here out of personal curiosity.

This is little modified grammar that generates AST - maybe it will help to better understand what I don't want to change. (temp_variables are probably going to be deleted, I havent made that decision).

grammar GAL;

options {
    //k=2;
    backtrack=true;
    language=CSharp3;
    output=AST;
}

tokens {
    HASH     = '#';
    COLON    = ':';
    DOT      = '.';
    CARET    = '^';
    PIPE     = '|';
    LBRACKET = '[';
    RBRACKET = ']';
    LPAREN   = '(';
    RPAREN   = ')';
    ASSIGN   = ':=';
}

// generated files options
@namespace { GAL.Compiler }
@lexer::namespace { GAL.Compiler}

// this will disable CLSComplaint warning in ANTLR generated code
@parser::header { 
// Do not bug me about [System.CLSCompliant(false)]
#pragma warning disable 3021 
}

@lexer::header { 
// Do not bug me about [System.CLSCompliant(false)]
#pragma warning disable 3021 
}

ID  :   ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT :   '0'..'9'+
    ;

FLOAT
    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
    |   '.' ('0'..'9')+ EXPONENT?
    |   ('0'..'9')+ EXPONENT
    ;

COMMENT
    :   '"' ( options {greedy=false;} : . )* '"' {$channel=Hidden;}
    ;

WS  :   ( ' '
        | '\t'
        ) {$channel=Hidden;}
    ;

NEW_LINE
    :   ('\r'?'\n')
    ;

STRING
    :  '\'' ( ESC_SEQ | ~('\\'|'\'') )* '\''
    ;

fragment
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

BINARY_MESSAGE_CHAR
    :   ('~' | '!' | '@' | '%' | '&' | '*' | '-' | '+' | '=' | '|' | '\\' | '<' | '>' | ',' | '?' | '/')
        ('~' | '!' | '@' | '%' | '&' | '*' | '-' | '+' | '=' | '|' | '\\' | '<' | '>' | ',' | '?' | '/')?
    ;

// parser

public program returns [ AstProgram program ]
    : { $program = new AstProgram(); }
    NEW_LINE* 
    ( statement (NEW_LINE+ | EOF)
        { $program.AddStatement($statement.stmt); }
    )*
    ;

statement returns [ AstNode stmt ]
    : message_sending
        { $stmt = $message_sending.messageSending; } 
    | return_statement
        { $stmt = $return_statement.ret; }
    | assignment
        { $stmt = $assignment.assignment; }
    | temp_variables
        { $stmt = $temp_variables.tempVars; }
    ;

return_statement returns [ AstReturn ret ]
    : CARET statement
        { $ret = new AstReturn($CARET, $statement.stmt); }
    ;

assignment returns [ AstAssignment assignment ]
    : dotted_expression ASSIGN statement
        { $assignment = new AstAssignment($dotted_expression.dottedExpression, $ASSIGN, $statement.stmt); }
    ;

temp_variables returns [ AstTempVariables tempVars ]
    : p1=PIPE 
        { $tempVars = new AstTempVariables($p1); }
    ( identifier
        { $tempVars.AddVar($identifier.identifier); }
    )+ 
    p2=PIPE
        { $tempVars.EndToken = $p2; }
    ;

object returns [ AstNode obj ]
    : number
        { $obj = $number.number; }
    | string
        { $obj = $string.str; }
    | dotted_expression
        { $obj = $dotted_expression.dottedExpression; }
    | literal
        { $obj = $literal.literal; }
    | block
        { $obj = $block.block; }
    | LPAREN message_sending RPAREN
        { $obj = $message_sending.messageSending; }
    ;

message_sending returns [ AstKeywordMessageSending messageSending ]
    : keyword_message_sending
        { $messageSending = $keyword_message_sending.keywordMessageSending; }
    ;

keyword_message_sending returns [ AstKeywordMessageSending keywordMessageSending ]
    : binary_message_sending 
        { $keywordMessageSending = new AstKeywordMessageSending($binary_message_sending.binaryMessageSending); }
    ( keyword_message
        { $keywordMessageSending = $keywordMessageSending.NewMessage($keyword_message.keywordMessage); }
    )?
    ;

binary_message_sending returns [ AstBinaryMessageSending binaryMessageSending ]
    : unary_message_sending
        { $binaryMessageSending = new AstBinaryMessageSending($unary_message_sending.unaryMessageSending); }
    ( binary_message
        { $binaryMessageSending = $binaryMessageSending.NewMessage($binary_message.binaryMessage); }
    )*
    ;

unary_message_sending returns [ AstUnaryMessageSending unaryMessageSending ]
    : object 
        { $unaryMessageSending = new AstUnaryMessageSending($object.obj); }
    (
      unary_message
        { $unaryMessageSending = $unaryMessageSending.NewMessage($unary_message.unaryMessage); }
    )*
    ;

unary_message returns [ AstUnaryMessage unaryMessage ]
    : unary_message_selector
        { $unaryMessage = new AstUnaryMessage($unary_message_selector.unarySelector); }
    ;

binary_message returns [ AstBinaryMessage binaryMessage ]
    : binary_message_selector unary_message_sending
        { $binaryMessage = new AstBinaryMessage($binary_message_selector.binarySelector, $unary_message_sending.unaryMessageSending); }
    ;

keyword_message returns [ AstKeywordMessage keywordMessage ]
    : 
    { $keywordMessage = new AstKeywordMessage(); }
    (
      NEW_LINE? 
      single_keyword_message_selector 
      NEW_LINE? 
      binary_message_sending
        { $keywordMessage.AddMessagePart($single_keyword_message_selector.singleKwSelector, $binary_message_sending.binaryMessageSending); }
    )+
    ;

block returns [ AstBlock block ]
    : LBRACKET 
        { $block = new AstBlock($LBRACKET); }
    (
      block_signiture
        { $block.Signiture = $block_signiture.blkSigniture; }
    )? NEW_LINE* 
      block_body
        { $block.Body = $block_body.blkBody; }
      NEW_LINE* 
      RBRACKET
        { $block.SetEndToken($RBRACKET); }
    ;

block_body returns [ IList<AstNode> blkBody ]
    @init { $blkBody = new List<AstNode>(); }
    : 
    ( s1=statement 
        { $blkBody.Add($s1.stmt); }
    )?
    ( NEW_LINE+ s2=statement 
        { $blkBody.Add($s2.stmt); }
    )*
    ;


block_signiture returns [ AstBlockSigniture blkSigniture ]
    @init { $blkSigniture = new AstBlockSigniture(); }
    : 
    ( COLON identifier
        { $blkSigniture.AddIdentifier($COLON, $identifier.identifier); }
    )+ PIPE
        { $blkSigniture.SetEndToken($PIPE); }
    ;

unary_message_selector returns [ AstUnaryMessageSelector unarySelector ]
    : identifier
        { $unarySelector = new AstUnaryMessageSelector($identifier.identifier); }
    ;

binary_message_selector returns [ AstBinaryMessageSelector binarySelector ]
    : BINARY_MESSAGE_CHAR
        { $binarySelector = new AstBinaryMessageSelector($BINARY_MESSAGE_CHAR); }
    ;

single_keyword_message_selector returns [ AstIdentifier singleKwSelector ]
    : identifier COLON
        { $singleKwSelector = $identifier.identifier; }
    ;

keyword_message_selector returns [ AstKeywordMessageSelector keywordSelector ]
    @init { $keywordSelector = new AstKeywordMessageSelector(); }
    : 
    ( single_keyword_message_selector
        { $keywordSelector.AddIdentifier($single_keyword_message_selector.singleKwSelector); }
    )+
    ;

symbol returns [ AstSymbol symbol ]
    : HASH 
    ( string 
        { $symbol = new AstSymbol($HASH, $string.str); }
    | identifier 
        { $symbol = new AstSymbol($HASH, $identifier.identifier); }
    | binary_message_selector 
        { $symbol = new AstSymbol($HASH, $binary_message_selector.binarySelector); }
    | keyword_message_selector
        { $symbol = new AstSymbol($HASH, $keyword_message_selector.keywordSelector); }
    )
    ; 

literal returns [ AstNode literal ]
    : symbol
        { $literal = $symbol.symbol; }
    ( block
        { $literal = new AstMethod($symbol.symbol, $block.block); }
    )? // if there is block then this is method
    ;

number returns [ AstNode number ]
    : /*'-'?*/
    ( INT
        { $number = new AstInt($INT); }
    | FLOAT 
        { $number = new AstInt($FLOAT); }
    )
    ;

string returns [ AstString str ]
    : STRING
        { $str = new AstString($STRING); }
    ;

dotted_expression returns [ AstDottedExpression dottedExpression ]
    : i1=identifier 
        { $dottedExpression = new AstDottedExpression($i1.identifier); }
    (DOT i2=identifier
        { $dottedExpression.AddIdentifier($i2.identifier); }
    )*
    ;

identifier returns [ AstIdentifier identifier ]
    : ID
        { $identifier = new AstIdentifier($ID); }
    ;
1

1 Answers

1
votes

Hi Smalltalk Grammar writer,

Firstly, to get a smalltalk grammar to parse properly (1 -- -2) and to support the optional '.' on the last statement, etc., you should treat whitespace as significant. Don't put it on the hidden channel.

The grammar so far is not breaking down the rules into small enough fragments. This will be a problem like you have seen with K=2 and backtracking.

I suggest you check out a working Smalltalk grammar in ANTLR as defined by the Redline Smalltalk project http://redline.st & https://github.com/redline-smalltalk/redline-smalltalk

Rgs, James.