grammar Graph; options { language = Java; output = AST; ASTLabelType=CommonTree; } tokens { // lexer INDENT; DEDENT; // graph FILE; RESOURCE; PROPERTY; VARIABLE; EMBEDDED_VALUE; EMBEDDED_TYPE; TEMPLATE_INSTANCE; TEMPLATE_DEFINITION; BLANK; REF; EQUALS; INSTANCE_OF; INHERITS; SUBRELATION_OF; HAS_DOMAIN; HAS_RANGE; DOMAIN_OF; REQUIRES_VALUE_TYPE; // data TYPE_DEFINITIONS; TYPE_DEFINITION; UNION_TYPE; RECORD_TYPE; TUPLE_TYPE; ARRAY_TYPE; TYPE_REFERENCE; TYPE_ANNOTATION; TYPE_COMPONENT; VALUE_DEFINITIONS; VALUE_DEFINITION; NO_VALUE; VARIANT; ARRAY; TUPLE; TAGGED_VALUE; RECORD; MAP; ASSIGNMENT; TRUE; FALSE; } @parser::header { package org.simantics.graph.compiler.internal.parsing; } @lexer::header { package org.simantics.graph.compiler.internal.parsing; import gnu.trove.list.array.*; } @lexer::members { int inParen = 0; TIntArrayList iStack = new TIntArrayList(); { iStack.add(0); } List tokens = new ArrayList(); public void emit(Token token) { state.token = token; tokens.add(token); } public Token nextToken() { if(tokens.isEmpty()) { super.nextToken(); if ( tokens.isEmpty() ) { /* When end-of-file is encountered, we emit balancing number of DEDENT tokens. */ if(iStack.size() <= 1) return getEOFToken(); else { while(iStack.size() > 1) { iStack.removeAt(iStack.size()-1); state.type = DEDENT; emit(); } iStack.clear(); } } } return (Token)tokens.remove(0); } } // ------------------------------------------------------------------ // LEXER // ------------------------------------------------------------------ ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ; COMMENT : '//' ~('\n')* {$channel=HIDDEN;} | '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;} ; WS : ( ' ' | '\t' | '\r' ) {$channel=HIDDEN;} ; LPAREN : '(' { ++ inParen; } ; RPAREN : ')' { -- inParen; } ; LBRACKET : '[' { ++ inParen; } ; RBRACKET : ']' { -- inParen; } ; LCURLY : '{' { ++ inParen; } ; RCURLY : '}' { -- inParen; } ; INT_RANGE : INT '..' INT? | '..' INT ; RANGE : FLOAT '..' (FLOAT | INT)? | '..' FLOAT | INT '..' FLOAT ; NEWLINE @init { int spaces = 0; } : '\n' ( ' ' { ++spaces; } | '//' ~('\n')* '\n' { spaces = 0; } | '/*' ( options {greedy=false;} : . )* '*/' | '\r' | '\n' { spaces = 0; } )* { int c = input.LA(1); if(inParen > 0) { $channel = HIDDEN; } else if(c == EOF) { while(iStack.size() > 1) { iStack.removeAt(iStack.size()-1); state.type = DEDENT; emit(); } $channel = HIDDEN; iStack.clear(); } else { int stackTop = iStack.get(iStack.size()-1); if(spaces > stackTop) { iStack.add(spaces); $type = INDENT; } else if(spaces < stackTop) { while(spaces < iStack.get(iStack.size()-1)) { iStack.removeAt(iStack.size()-1); state.type = DEDENT; emit(); } state.type = NEWLINE; emit(); // TODO check that spaces == iStack.get(iStack.size()-1) } } } ; INDENT: { false }?=> 'INDENT' ; DEDENT: { false }?=> 'DEDENT' ; INT : '-'? '0'..'9'+ ; FLOAT : '-'? ( ('0'..'9')+ '.' ('0'..'9')* EXPONENT? | ('0'..'9')+ EXPONENT ) ; STRING : '"' ( ESC_SEQ | ~('\\'|'"') )* '"' | '"""' ( ~('"') | '"' ~('"') | '""' ~('"') )* '"""' ; URI : '') )* '>' ; 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 ; fragment UNICODE_ESC : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT ; // ------------------------------------------------------------------ // STATEMENTS // ------------------------------------------------------------------ file : NEWLINE? resourceDefinitions? EOF -> ^(FILE resourceDefinitions?) ; resourceDefinitions : resourceDefinition (NEWLINE! resourceDefinition)* ; resourceDefinition : resource localProperty* (INDENT property (NEWLINE property)* DEDENT)? -> ^(RESOURCE resource localProperty* property*) | template -> ^(RESOURCE ^(BLANK template) template) ; localProperty : relation resource -> ^(PROPERTY relation ^(RESOURCE resource)) ; property : relation ( resourceDefinition -> ^(PROPERTY relation resourceDefinition) | INDENT resourceDefinitions DEDENT -> ^(PROPERTY relation resourceDefinitions) ) | template ; template : '@' ( {input.LT(1).getText().equals("template")}?=> ID resource+ INDENT resourceDefinitions DEDENT -> ^(TEMPLATE_DEFINITION resource+ resourceDefinitions) | resource+ (INDENT resourceDefinitions DEDENT)? -> ^(TEMPLATE_INSTANCE resource+ resourceDefinitions?) ) ; // ------------------------------------------------------------------ // RESOURCES // ------------------------------------------------------------------ relation : ( ID -> ID) ('.' ID -> ^(REF $relation ID))* | URI | ' INHERITS | ' SUBRELATION_OF | '<--' -> HAS_DOMAIN | '-->' -> HAS_RANGE | '==>' -> REQUIRES_VALUE_TYPE | '>--' -> DOMAIN_OF | ':' -> INSTANCE_OF | '=' -> EQUALS | '%' ID -> ^(VARIABLE ID) ; resource : ( {input.LT(1).getText().equals("_")}?=> ID -> ^(BLANK ID) | ID -> ID) ('.' (ID -> ^(REF $resource ID) |STRING -> ^(REF $resource STRING) ) )* | URI | simpleValue -> ^(EMBEDDED_VALUE simpleValue) | '$' basicType -> ^(EMBEDDED_TYPE basicType) | '%' ID -> ^(VARIABLE ID) ; // ------------------------------------------------------------------ // TYPE DEFINITIONS // ------------------------------------------------------------------ /*typeDefinitions : typeDefinition* -> ^(TYPE_DEFINITIONS typeDefinition*); typeDefinition : 'type' ID '=' type -> ^(TYPE_DEFINITION ID type) ; */ type : arrayType | unionType ; unionType : ('|' unionComponent)+ -> ^(UNION_TYPE unionComponent+) ; unionComponent : ID ((arrayType) => arrayType)? -> ^(TYPE_COMPONENT ID arrayType?) ; arrayType : (basicType -> basicType) (LBRACKET arrayLength? RBRACKET -> ^(ARRAY_TYPE $arrayType arrayLength?))* ; arrayLength : INT | INT_RANGE ; basicType : tupleType | recordType | typeReference ; tupleType : LPAREN (type (',' type)*)? RPAREN -> ^(TUPLE_TYPE type*) ; recordType : LCURLY (component (',' component)*)? RCURLY -> ^(RECORD_TYPE component*) ; component : ID ':' type -> ^(TYPE_COMPONENT ID type) ; typeReference : ID ((LPAREN)=> LPAREN parameter (',' parameter)* RPAREN)? -> ^(TYPE_REFERENCE ID parameter*) ; parameter : ID '=' parameterValue -> ^(TYPE_ANNOTATION ID parameterValue) | type ; parameterValue : string | boolean_ | number | rangePar -> ^(RANGE rangePar) ; rangePar : (LBRACKET | LPAREN) range (RBRACKET | RPAREN) ; range : number | RANGE | INT_RANGE ; number : INT | FLOAT ; string : STRING ; boolean_ : 'true' -> TRUE | 'false' -> FALSE ; // ------------------------------------------------------------------ // VALUE DEFINITIONS // ------------------------------------------------------------------ valueDefinitions : valueDefinition* -> ^(VALUE_DEFINITIONS valueDefinition*); valueDefinition : ID ':' type '=' value -> ^(VALUE_DEFINITION ID type value) ; value : (basicValue -> basicValue) (':' type -> ^(VARIANT type $value))* ; basicValue : simpleValue | map | {input.LT(1).getText().equals("null")}? ID -> NO_VALUE | taggedValue ; simpleValue : string | number | boolean_ | array | tuple | record ; array : LBRACKET (value (',' value)*)? RBRACKET -> ^(ARRAY value*) ; tuple : LPAREN (value (',' value)*)? RPAREN -> ^(TUPLE value*) ; taggedValue : ID simpleValue? -> ^(TAGGED_VALUE ID simpleValue?) ; record : LCURLY (recordAssignment (',' recordAssignment)*)? RCURLY -> ^(RECORD recordAssignment*) ; recordAssignment : ID '=' value -> ^(ASSIGNMENT ID value) ; map : {input.LT(1).getText().equals("map")}?=> ID LCURLY (mapAssignment (',' mapAssignment)*)? RCURLY -> ^(MAP mapAssignment*) ; mapAssignment : value '=' value -> ^(ASSIGNMENT value*) ;