I'm creating an Antlr grammar for "The Elderscrolls 3" aka. Morrowind's scripting engine.(This is my first grammar btw, so it's probably not that good.)
Basically, it's not letting me define legal if blocks. (In my TES3 scripts.)
Begin test
short DoOnce
if ( DoOnce == 0 )
MessageBox "Done"
set DoOnce to -1
return
endif
End
Here is my Antlr grammar. (Any general tips would be welcome. I basically looked at other grammars, and tried to figure out how to make it work with TES3's scripting language. So, this could have lot's of issues.)
grammar TES3;
options
{
language=CSharp2;
}
tokens
{
BEGIN = 'Begin';
END = 'End';
SET = 'set';
TO = 'to';
FIX = '->';
QUOTE = '"';
}
@members
{
public string ScriptName = String.Empty;
public Dictionary<string,> Variables = new Dictionary<string,>(StringComparer.CurrentCultureIgnoreCase);
ArrayList exceptions = new ArrayList();
public void Begin(ListBox lb)
{
lb.Items.Clear();
program();
PrintMessages(lb);
}
void PrintMessages(ListBox lb)
{
if (HasError)
{
string error = ErrorMessage;
if (error.Contains("<eof>"))
error = error.Replace("<eof>", "End-of-File");
lb.ReportError(error, ErrorLevel.Error, ErrorPosition);
}
}
public override void ReportError(RecognitionException e)
{
exceptions.Add(e);
}
public bool HasError
{
get { return exceptions.Count > 0; }
}
public string ErrorMessage
{
get
{
if (exceptions.Count != 0)
return this.GetErrorMessage(exceptions[0] as RecognitionException, this.TokenNames);
else
return "No Errors Detected!";
}
}
public int ErrorPosition
{
get
{
if (exceptions.Count != 0)
{
RecognitionException EX = (RecognitionException)exceptions[0];
return EX.Line;
}
else
return 0;
}
}
public void QuickReport(string s, int line)
{
RecognitionException re = new RecognitionException(s);
re.Line = line;
ReportError(re);
}
public void FormatReport(string s, int line, params object[] param)
{
QuickReport(String.Format(s, param), line);
}
}
@lexer::namespace {
Parser
}
@lexer::members
{
}
@parser::namespace {
Parser
}
@header
{
using System.Text;
using System.Windows.Forms;
using AvalonEditor;
}
variables : tes3short | tes3long | tes3float;
tes3short: 'short' id=IDENTIFIER
{
if ($id != null)
if (!Variables.ContainsKey($id.Text))
Variables.Add($id.Text, "short");
else
FormatReport("A variable of type '{0}' already exist with the name '{1}'!", $id.Line, Variables[$id.Text], $id.Text);
};
tes3long: 'long' id=IDENTIFIER
{
if ($id != null)
if (!Variables.ContainsKey($id.Text))
Variables.Add($id.Text, "long");
else
FormatReport("A variable of type '{0}' already exist with the name '{1}'!", $id.Line, Variables[$id.Text], $id.Text);
};
tes3float: 'float' id=IDENTIFIER
{
if ($id != null)
if (!Variables.ContainsKey($id.Text))
Variables.Add($id.Text, "float");
else
FormatReport("A variable of type '{0}' already exist with the name '{1}'!", $id.Line, Variables[$id.Text], $id.Text);
};
begin : BEGIN id=IDENTIFIER
{
if ($id != null)
{
if ($id.Text.StartsWith("_"))
QuickReport("Script names cannot begin with an underscore!", $id.Line);
ScriptName = $id.Text;
}
};
end : END id=IDENTIFIER?
{
if ($id != null)
if ($id.Text != ScriptName)
FormatReport("Script name '{0}' at 'End' doesn't match script name '{1}' at 'Begin'!", $id.Line, $id.Text, ScriptName);
};
expression : compare;
compare :
IDENTIFIER |
IDENTIFIER OPERATOR IDENTIFIER |
IDENTIFIER OPERATOR VALUE |
VALUE OPERATOR IDENTIFIER |
VALUE OPERATOR VALUE
IDENTIFIER FIX IDENTIFIER |
IDENTIFIER FIX IDENTIFIER OPERATOR VALUE |
IDENTIFIER FIX IDENTIFIER OPERATOR IDENTIFIER;
statement:
if_statement |
while_statement |
return_statement |
set_statement |
functions;
if_statement :
'if' '(' expression ')'
statement*
('elseif') => elseif_statement*
statement*
('else') => else_statement?
statement*
('endif') => endif_statement;
elseif_statement : 'elseif' '(' expression ')';
else_statement : 'else';
endif_statement : 'endif';
while_statement :
'while' '(' expression ')'
(statement)*
('endwhile') => endwhile_statement;
endwhile_statement: 'endwhile';
return_statement: 'return';
operation: set_operation;
set_operation:
VALUE |
'(' VALUE ')' |
'(' IDENTIFIER OPERATOR VALUE ')' |
'(' IDENTIFIER FIX IDENTIFIER ')';
set_statement : SET IDENTIFIER TO operation;
functions: t3_message;
t3_message : 'MessageBox' '"' (IDENTIFIER|VALUE)* '"';
program:
begin
(variables)*
(statement)*
end
EOF;
fragment PLUS : '+';
fragment MINUS : '-';
fragment DIV : '/';
fragment MUL : '*';
fragment GT : '>';
fragment EQ : '==';
fragment LT : '<';
fragment LTE : '<=';
fragment GTE : '>=';
fragment NE : '!=';
fragment DIGIT : ('0'..'9');
fragment LOWER : ('a'..'z');
fragment UPPER : ('A'..'Z');
fragment INTEGER : DIGIT*;
fragment FLOAT : DIGIT* '.' DIGIT*;
IDENTIFIER : ('_' | LOWER | UPPER)('_' | LOWER | UPPER | DIGIT)*;
VALUE: (MINUS?)(INTEGER|FLOAT);
OPERATOR: (PLUS|MINUS|DIV|MUL|GT|EQ|LT|LTE|GTE|NE);
NEWLINE
: '\r'? '\n' {$channel=Antlr.Runtime.TokenChannels.Hidden;}
;
SPACE
: (' ')+ {$channel=Antlr.Runtime.TokenChannels.Hidden;}
;
TAB
: ('\t')+ {$channel=Antlr.Runtime.TokenChannels.Hidden;}
;
COMMENT
: ';' ( ~('\n'|'\r') )*( '\n'|'\r'('\n')? )? {$channel=Antlr.Runtime.TokenChannels.Hidden;}
;
</eof></eof>