using System; using System.Collections.Immutable; using System.Linq; using Upsilon.Text; namespace Upsilon.Parser { public class Parser { private readonly ImmutableArray _tokens; private readonly Diagnostics _diagnostics; private int _position; private Parser(ImmutableArray tokens, Diagnostics diagnostics) { _tokens = tokens; _diagnostics = diagnostics; } public static BlockStatementSyntax Parse(string text, Diagnostics diagnostics) { var tokens = Lexer.Lex(text, diagnostics); return (BlockStatementSyntax) new Parser(tokens, diagnostics).ParseScriptSyntax(); } private SyntaxToken Current => Get(0); private SyntaxToken Next => Get(1); private SyntaxToken Get(int offset) { if (_position + offset >= _tokens.Length) return new SyntaxToken(SyntaxKind.EndOfFile, _position + offset, "\0", null); return _tokens[_position + offset]; } private SyntaxToken NextToken() { var current = Current; _position++; return current; } private SyntaxToken MatchToken(SyntaxKind kind) { if (Current.Kind == kind) return NextToken(); _diagnostics.LogBadCharacter(Current.Span, kind, Current.Kind); return new SyntaxToken(kind, Current.Span.Start, "", null); } private StatementSyntax ParseScriptSyntax() { var statement = ParseBlockStatement(new []{SyntaxKind.EndOfFile}); MatchToken(SyntaxKind.EndOfFile); return statement; } private StatementSyntax ParseStatement() { if (Current.Kind == SyntaxKind.Identifier && Next.Kind == SyntaxKind.Equals) { return ParseAssignmentExpression(); } if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.Identifier) { return ParseAssignmentExpression(); } if (Current.Kind == SyntaxKind.IfKeyword) { return ParseIfStatement(SyntaxKind.IfKeyword); } if (Current.Kind == SyntaxKind.FunctionKeyword) { return ParseFunctionStatement(); } if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.FunctionKeyword) { return ParseFunctionStatement(); } if (Current.Kind == SyntaxKind.ReturnKeyword) { return ParseReturnStatement(); } return ParseExpressionStatement(); } private StatementSyntax ParseBlockStatement(SyntaxKind[] endTokens) { var statements = ImmutableArray.CreateBuilder(); while (!endTokens.Contains(Current.Kind)) { var next = ParseStatement(); statements.Add(next); } return new BlockStatementSyntax(statements.ToImmutable()); } private StatementSyntax ParseIfStatement(SyntaxKind requiredToken) { var ifToken = MatchToken(requiredToken); var condition = ParseExpressionStatement(); var thenToken = MatchToken(SyntaxKind.ThenKeyword); var block = ParseBlockStatement(new []{SyntaxKind.EndKeyword, SyntaxKind.ElseIfKeyword, SyntaxKind.ElseKeyword}); switch (Current.Kind) { case SyntaxKind.ElseIfKeyword: var nextElseIf = new ElseIfStatementSyntax((IfStatementSyntax) ParseIfStatement(SyntaxKind.ElseIfKeyword)); return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, nextElseIf); case SyntaxKind.ElseKeyword: { var elseToken = MatchToken(SyntaxKind.ElseKeyword); var elseBlock = ParseBlockStatement(new[]{SyntaxKind.EndKeyword}); var endEndToken = MatchToken(SyntaxKind.EndKeyword); var elseStatement = new ElseStatementSyntax(elseToken, (BlockStatementSyntax) elseBlock, endEndToken); return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, elseStatement); } case SyntaxKind.EndKeyword: var endToken = MatchToken(SyntaxKind.EndKeyword); return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, endToken); default: throw new ArgumentOutOfRangeException(); } } private StatementSyntax ParseFunctionStatement() { SyntaxToken localToken = null; if (Current.Kind == SyntaxKind.LocalKeyword) { localToken = NextToken(); } var functionToken = MatchToken(SyntaxKind.FunctionKeyword); var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); var variableBuilder = ImmutableArray.CreateBuilder(); while (Current.Kind != SyntaxKind.CloseParenthesis) { var variableIdentifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); variableBuilder.Add(variableIdentifier); if (Current.Kind == SyntaxKind.Comma) NextToken(); } var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword}); var endToken = MatchToken(SyntaxKind.EndKeyword); return new FunctionStatementSyntax(localToken, functionToken, identifier, openParenthesis, variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken); } private ExpressionStatementSyntax ParseExpressionStatement() { var expression = ParseExpression(); return new ExpressionStatementSyntax(expression); } private StatementSyntax ParseReturnStatement() { var returnToken = MatchToken(SyntaxKind.ReturnKeyword); var expression = ParseExpression(); return new ReturnStatementSyntax(returnToken, expression); } private ExpressionSyntax ParseExpression() { return ParseBinaryExpression(); } private AssignmentExpressionSyntax ParseAssignmentExpression() { SyntaxToken localKeyword = null; if (Current.Kind == SyntaxKind.LocalKeyword) { localKeyword = MatchToken(SyntaxKind.LocalKeyword); } var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); var assignmentToken = MatchToken(SyntaxKind.Equals); var expression = ParseExpression(); return new AssignmentExpressionSyntax(localKeyword, identifier, assignmentToken, expression); } private ExpressionSyntax ParseBinaryExpression(SyntaxKindPrecedence.Precedence parentPrecedence = SyntaxKindPrecedence.Precedence.None) { ExpressionSyntax left; var unaryOperatorPrecedence = Current.Kind.UnaryOperatorPrecedence(); if (unaryOperatorPrecedence != SyntaxKindPrecedence.Precedence.None && unaryOperatorPrecedence >= parentPrecedence) { var operatorToken = NextToken(); var operand = ParseBinaryExpression(unaryOperatorPrecedence); left = new UnaryExpressionSyntax(operatorToken, operand); } else { left = ParsePrimaryExpression(); } while (true) { var precedence = Current.Kind.BinaryOperatorPrecedence(); if (precedence == SyntaxKindPrecedence.Precedence.None || precedence <= parentPrecedence) break; var op = NextToken(); var right = ParseBinaryExpression(precedence); left = new BinaryExpressionSyntax(left, op, right); } return left; } private ExpressionSyntax ParsePrimaryExpression() { switch (Current.Kind) { case SyntaxKind.OpenParenthesis: return ParseParenthesizedExpression(); case SyntaxKind.Number: return ParseNumber(); case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: return ParseBoolean(); case SyntaxKind.Identifier: if (Next.Kind == SyntaxKind.OpenParenthesis) return ParseFunctionCallExpression(); var token = MatchToken(SyntaxKind.Identifier); return new VariableExpressionSyntax((IdentifierToken) token); case SyntaxKind.NilKeyword: var nilToken = MatchToken(SyntaxKind.NilKeyword); return new LiteralExpressionSyntax(nilToken, null); default: _diagnostics.LogBadCharacter(new TextSpan(_position, 1), SyntaxKind.Identifier); NextToken(); return new BadExpressionSyntax(); } } private ExpressionSyntax ParseFunctionCallExpression() { var identifier = MatchToken(SyntaxKind.Identifier); var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); var parameters = ImmutableArray.CreateBuilder(); while (Current.Kind != SyntaxKind.CloseParenthesis) { var exp = ParseExpression(); parameters.Add(exp); if (Current.Kind == SyntaxKind.Comma) NextToken(); } var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); return new FunctionCallExpressionSyntax((IdentifierToken) identifier, openParenthesis, parameters.ToImmutable(), closeParenthesis); } private ExpressionSyntax ParseParenthesizedExpression() { var l = MatchToken(SyntaxKind.OpenParenthesis); var e = ParseExpression(); var r = MatchToken(SyntaxKind.CloseParenthesis); return new ParenthesizedExpressionSyntax(l, e, r); } private ExpressionSyntax ParseNumber() { var numberToken = MatchToken(SyntaxKind.Number); return new LiteralExpressionSyntax(numberToken, numberToken.Value); } private ExpressionSyntax ParseBoolean() { var isTrue = Current.Kind == SyntaxKind.TrueKeyword; var token = MatchToken(isTrue ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); return new LiteralExpressionSyntax(token, isTrue); } } }