UNPKG

php7parser

Version:
1,702 lines (1,379 loc) 100 kB
/* Copyright (c) Ben Robert Mewburn * Licensed under the ISC Licence. */ 'use strict'; import { Token, Lexer, TokenType, LexerMode } from './lexer'; import { ParseError, Phrase, PhraseType, } from './phrase'; export namespace Parser { interface Predicate { (t: Token): boolean; } const enum Associativity { None, Left, Right } function precedenceAssociativityTuple(t: Token) { switch (t.tokenType) { case TokenType.AsteriskAsterisk: return [48, Associativity.Right]; case TokenType.PlusPlus: return [47, Associativity.Right]; case TokenType.MinusMinus: return [47, Associativity.Right]; case TokenType.Tilde: return [47, Associativity.Right]; case TokenType.IntegerCast: return [47, Associativity.Right]; case TokenType.FloatCast: return [47, Associativity.Right]; case TokenType.StringCast: return [47, Associativity.Right]; case TokenType.ArrayCast: return [47, Associativity.Right]; case TokenType.ObjectCast: return [47, Associativity.Right]; case TokenType.BooleanCast: return [47, Associativity.Right]; case TokenType.UnsetCast: return [47, Associativity.Right]; case TokenType.AtSymbol: return [47, Associativity.Right]; case TokenType.InstanceOf: return [46, Associativity.None]; case TokenType.Exclamation: return [45, Associativity.Right]; case TokenType.Asterisk: return [44, Associativity.Left]; case TokenType.ForwardSlash: return [44, Associativity.Left]; case TokenType.Percent: return [44, Associativity.Left]; case TokenType.Plus: return [43, Associativity.Left]; case TokenType.Minus: return [43, Associativity.Left]; case TokenType.Dot: return [43, Associativity.Left]; case TokenType.LessThanLessThan: return [42, Associativity.Left]; case TokenType.GreaterThanGreaterThan: return [42, Associativity.Left]; case TokenType.LessThan: return [41, Associativity.None]; case TokenType.GreaterThan: return [41, Associativity.None]; case TokenType.LessThanEquals: return [41, Associativity.None]; case TokenType.GreaterThanEquals: return [41, Associativity.None]; case TokenType.EqualsEquals: return [40, Associativity.None]; case TokenType.EqualsEqualsEquals: return [40, Associativity.None]; case TokenType.ExclamationEquals: return [40, Associativity.None]; case TokenType.ExclamationEqualsEquals: return [40, Associativity.None]; case TokenType.Spaceship: return [40, Associativity.None]; case TokenType.Ampersand: return [39, Associativity.Left]; case TokenType.Caret: return [38, Associativity.Left]; case TokenType.Bar: return [37, Associativity.Left]; case TokenType.AmpersandAmpersand: return [36, Associativity.Left]; case TokenType.BarBar: return [35, Associativity.Left]; case TokenType.QuestionQuestion: return [34, Associativity.Right]; case TokenType.Question: return [33, Associativity.Left]; //?: ternary case TokenType.Equals: return [32, Associativity.Right]; case TokenType.DotEquals: return [32, Associativity.Right]; case TokenType.PlusEquals: return [32, Associativity.Right]; case TokenType.MinusEquals: return [32, Associativity.Right]; case TokenType.AsteriskEquals: return [32, Associativity.Right]; case TokenType.ForwardslashEquals: return [32, Associativity.Right]; case TokenType.PercentEquals: return [32, Associativity.Right]; case TokenType.AsteriskAsteriskEquals: return [32, Associativity.Right]; case TokenType.AmpersandEquals: return [32, Associativity.Right]; case TokenType.BarEquals: return [32, Associativity.Right]; case TokenType.CaretEquals: return [32, Associativity.Right]; case TokenType.LessThanLessThanEquals: return [32, Associativity.Right]; case TokenType.GreaterThanGreaterThanEquals: return [32, Associativity.Right]; case TokenType.And: return [31, Associativity.Left]; case TokenType.Xor: return [30, Associativity.Left]; case TokenType.Or: return [29, Associativity.Left]; default: throwUnexpectedTokenError(t); } } const statementListRecoverSet = [ TokenType.Use, TokenType.HaltCompiler, TokenType.Const, TokenType.Function, TokenType.Class, TokenType.Abstract, TokenType.Final, TokenType.Trait, TokenType.Interface, TokenType.OpenBrace, TokenType.If, TokenType.While, TokenType.Do, TokenType.For, TokenType.Switch, TokenType.Break, TokenType.Continue, TokenType.Return, TokenType.Global, TokenType.Static, TokenType.Echo, TokenType.Unset, TokenType.ForEach, TokenType.Declare, TokenType.Try, TokenType.Throw, TokenType.Goto, TokenType.Semicolon, TokenType.CloseTag, TokenType.OpenTagEcho, TokenType.Text, TokenType.OpenTag ]; const classMemberDeclarationListRecoverSet = [ TokenType.Public, TokenType.Protected, TokenType.Private, TokenType.Static, TokenType.Abstract, TokenType.Final, TokenType.Function, TokenType.Var, TokenType.Const, TokenType.Use ]; const encapsulatedVariableListRecoverSet = [ TokenType.EncapsulatedAndWhitespace, TokenType.DollarCurlyOpen, TokenType.CurlyOpen ]; function binaryOpToPhraseType(t: Token) { switch (t.tokenType) { case TokenType.Question: return PhraseType.TernaryExpression; case TokenType.Dot: case TokenType.Plus: case TokenType.Minus: return PhraseType.AdditiveExpression; case TokenType.Bar: case TokenType.Ampersand: case TokenType.Caret: return PhraseType.BitwiseExpression; case TokenType.Asterisk: case TokenType.ForwardSlash: case TokenType.Percent: return PhraseType.MultiplicativeExpression; case TokenType.AsteriskAsterisk: return PhraseType.ExponentiationExpression; case TokenType.LessThanLessThan: case TokenType.GreaterThanGreaterThan: return PhraseType.ShiftExpression; case TokenType.AmpersandAmpersand: case TokenType.BarBar: case TokenType.And: case TokenType.Or: case TokenType.Xor: return PhraseType.LogicalExpression; case TokenType.EqualsEqualsEquals: case TokenType.ExclamationEqualsEquals: case TokenType.EqualsEquals: case TokenType.ExclamationEquals: return PhraseType.EqualityExpression; case TokenType.LessThan: case TokenType.LessThanEquals: case TokenType.GreaterThan: case TokenType.GreaterThanEquals: case TokenType.Spaceship: return PhraseType.RelationalExpression; case TokenType.QuestionQuestion: return PhraseType.CoalesceExpression; case TokenType.Equals: return PhraseType.SimpleAssignmentExpression; case TokenType.PlusEquals: case TokenType.MinusEquals: case TokenType.AsteriskEquals: case TokenType.AsteriskAsteriskEquals: case TokenType.ForwardslashEquals: case TokenType.DotEquals: case TokenType.PercentEquals: case TokenType.AmpersandEquals: case TokenType.BarEquals: case TokenType.CaretEquals: case TokenType.LessThanLessThanEquals: case TokenType.GreaterThanGreaterThanEquals: return PhraseType.CompoundAssignmentExpression; case TokenType.InstanceOf: return PhraseType.InstanceOfExpression; default: return PhraseType.Unknown; } } var tokenBuffer: Token[]; var phraseStack: Phrase[]; var errorPhrase: ParseError; var recoverSetStack: TokenType[][]; export function parse(text: string): Phrase { init(text); let stmtList = statementList([TokenType.EndOfFile]); //append trailing hidden tokens hidden(stmtList); return stmtList; } function init(text: string, lexerModeStack?: LexerMode[]) { Lexer.setInput(text, lexerModeStack); phraseStack = []; tokenBuffer = []; recoverSetStack = []; errorPhrase = null; } function start(phraseType?:PhraseType, dontPushHiddenToParent?: boolean) { //parent node gets hidden tokens between children if (!dontPushHiddenToParent) { hidden(); } let p:Phrase = { phraseType: phraseType ? phraseType : PhraseType.Unknown, children: [] } phraseStack.push(p); return p; } function end() { return phraseStack.pop(); } function hidden(p?:Phrase) { if(!p) { p = phraseStack[phraseStack.length - 1]; } let t: Token; while (true) { t = tokenBuffer.length ? tokenBuffer.shift() : Lexer.lex(); if (t.tokenType < TokenType.Comment) { tokenBuffer.unshift(t); break; } else { p.children.push(t); } } } function optional(tokenType: TokenType) { if (tokenType === peek().tokenType) { errorPhrase = null; return next(); } else { return null; } } function optionalOneOf(tokenTypes: TokenType[]) { if (tokenTypes.indexOf(peek().tokenType) >= 0) { errorPhrase = null; return next(); } else { return null; } } function next(doNotPush?: boolean): Token { let t = tokenBuffer.length ? tokenBuffer.shift() : Lexer.lex(); if (t.tokenType === TokenType.EndOfFile) { return t; } if (t.tokenType >= TokenType.Comment) { //hidden token phraseStack[phraseStack.length - 1].children.push(t); return next(doNotPush); } else if (!doNotPush) { phraseStack[phraseStack.length - 1].children.push(t); } return t; } function expect(tokenType: TokenType) { let t = peek(); if (t.tokenType === tokenType) { errorPhrase = null; return next(); } else if (tokenType === TokenType.Semicolon && t.tokenType === TokenType.CloseTag) { //implicit end statement return t; } else { error(tokenType); //test skipping a single token to sync if (peek(1).tokenType === tokenType) { let predicate = (x: Token) => { return x.tokenType === tokenType; }; skip(predicate); errorPhrase = null; return next(); //tokenType } return null; } } function expectOneOf(tokenTypes: TokenType[]) { let t = peek(); if (tokenTypes.indexOf(t.tokenType) >= 0) { errorPhrase = null; return next(); } else if (tokenTypes.indexOf(TokenType.Semicolon) >= 0 && t.tokenType === TokenType.CloseTag) { //implicit end statement return t; } else { error(); //test skipping single token to sync if (tokenTypes.indexOf(peek(1).tokenType) >= 0) { let predicate = (x: Token) => { return tokenTypes.indexOf(x.tokenType) >= 0; }; skip(predicate); errorPhrase = null; return next(); //tokenType } return null; } } function peek(n?: number) { let k = n ? n + 1 : 1; let bufferPos = -1; let t: Token; while (true) { ++bufferPos; if (bufferPos === tokenBuffer.length) { tokenBuffer.push(Lexer.lex()); } t = tokenBuffer[bufferPos]; if (t.tokenType < TokenType.Comment) { //not a hidden token --k; } if (t.tokenType === TokenType.EndOfFile || k === 0) { break; } } return t; } /** * skipped tokens get pushed to error phrase children */ function skip(predicate: Predicate) { let t: Token; while (true) { t = tokenBuffer.length ? tokenBuffer.shift() : Lexer.lex(); if (predicate(t) || t.tokenType === TokenType.EndOfFile) { tokenBuffer.unshift(t); break; } else { errorPhrase.children.push(t); } } } function error(expected?:TokenType) { //dont report errors if recovering from another if (errorPhrase) { return; } errorPhrase = <ParseError>{ phraseType : PhraseType.Error, children:[], unexpected: peek() }; if(expected) { errorPhrase.expected = expected; } phraseStack[phraseStack.length - 1].children.push(errorPhrase); } function list(phraseType: PhraseType, elementFunction: () => Phrase | Token, elementStartPredicate: Predicate, breakOn?: TokenType[], recoverSet?: TokenType[]) { let p = start(phraseType); let t: Token; let recoveryAttempted = false; let listRecoverSet = recoverSet ? recoverSet.slice(0) : []; if (breakOn) { Array.prototype.push.apply(listRecoverSet, breakOn); } recoverSetStack.push(listRecoverSet); while (true) { t = peek(); if (elementStartPredicate(t)) { recoveryAttempted = false; p.children.push(elementFunction()); } else if (!breakOn || breakOn.indexOf(t.tokenType) >= 0 || recoveryAttempted) { break; } else { error(); //attempt to sync with token stream t = peek(1); if (elementStartPredicate(t) || breakOn.indexOf(t.tokenType) >= 0) { skip((x) => { return x === t }); } else { defaultSyncStrategy(); } recoveryAttempted = true; } } recoverSetStack.pop(); return end(); } function defaultSyncStrategy() { let mergedRecoverTokenTypeArray: TokenType[] = []; for (let n = recoverSetStack.length - 1; n >= 0; --n) { Array.prototype.push.apply(mergedRecoverTokenTypeArray, recoverSetStack[n]); } let mergedRecoverTokenTypeSet = new Set(mergedRecoverTokenTypeArray); let predicate: Predicate = (x) => { return mergedRecoverTokenTypeSet.has(x.tokenType); }; skip(predicate); } /* function isListPhrase(phraseType: PhraseType) { switch (phraseType) { case PhraseType.StatementList: return true; default: false; } } */ function statementList(breakOn: TokenType[]) { return list( PhraseType.StatementList, statement, isStatementStart, breakOn, statementListRecoverSet); } function constDeclaration() { let p = start(PhraseType.ConstDeclaration); next(); //const p.children.push(delimitedList( PhraseType.ConstElementList, constElement, isConstElementStartToken, TokenType.Comma, [TokenType.Semicolon] )); expect(TokenType.Semicolon); return end(); } function isClassConstElementStartToken(t: Token) { return t.tokenType === TokenType.Name || isSemiReservedToken(t); } function isConstElementStartToken(t: Token) { return t.tokenType === TokenType.Name; } function constElement() { let p = start(PhraseType.ConstElement); expect(TokenType.Name); expect(TokenType.Equals); p.children.push(expression(0)); return end(); } function expression(minPrecedence: number) { let precedence: number; let associativity: Associativity; let op: Token; let lhs = expressionAtom(); let p: Phrase; let rhs: Phrase | Token; let binaryPhraseType: PhraseType; while (true) { op = peek(); binaryPhraseType = binaryOpToPhraseType(op); if (binaryPhraseType === PhraseType.Unknown) { break; } [precedence, associativity] = precedenceAssociativityTuple(op); if (precedence < minPrecedence) { break; } if (associativity === Associativity.Left) { ++precedence; } if (binaryPhraseType === PhraseType.TernaryExpression) { lhs = ternaryExpression(lhs); continue; } p = start(binaryPhraseType, true); p.children.push(lhs); next(); if (binaryPhraseType === PhraseType.InstanceOfExpression) { p.children.push(typeDesignator(PhraseType.InstanceofTypeDesignator)); } else { if (binaryPhraseType === PhraseType.SimpleAssignmentExpression && peek().tokenType === TokenType.Ampersand) { next(); //& p.phraseType = PhraseType.ByRefAssignmentExpression; } p.children.push(expression(precedence)); } lhs = end(); } return lhs; } function ternaryExpression(testExpr: Phrase | Token) { let p = start(PhraseType.TernaryExpression,true); p.children.push(testExpr); next(); //? if (optional(TokenType.Colon)) { p.children.push(expression(0)); } else { p.children.push(expression(0)); expect(TokenType.Colon); p.children.push(expression(0)); } return end(); } function variableOrExpression() { let part = variableAtom(); let isVariable = (<Phrase>part).phraseType === PhraseType.SimpleVariable; if (isDereferenceOperator(peek())) { part = variable(part); isVariable = true; } else { switch ((<Phrase>part).phraseType) { case PhraseType.QualifiedName: case PhraseType.FullyQualifiedName: case PhraseType.RelativeQualifiedName: part = constantAccessExpression(part); break; default: break; } } if (!isVariable) { return part; } //check for post increment/decrement let t = peek(); if (t.tokenType === TokenType.PlusPlus) { return postfixExpression(PhraseType.PostfixIncrementExpression, <Phrase>part); } else if (t.tokenType === TokenType.MinusMinus) { return postfixExpression(PhraseType.PostfixDecrementExpression, <Phrase>part); } else { return part; } } function constantAccessExpression(qName: Phrase | Token) { let p = start(PhraseType.ConstantAccessExpression, true); p.children.push(qName); return end(); } function postfixExpression(phraseType: PhraseType, variableNode: Phrase) { let p = start(phraseType,true); p.children.push(variableNode); next(); //operator return end(); } function isDereferenceOperator(t: Token) { switch (t.tokenType) { case TokenType.OpenBracket: case TokenType.OpenBrace: case TokenType.Arrow: case TokenType.OpenParenthesis: case TokenType.ColonColon: return true; default: return false; } } function expressionAtom() { let t = peek(); switch (t.tokenType) { case TokenType.Static: if (peek(1).tokenType === TokenType.Function) { return anonymousFunctionCreationExpression(); } else { return variableOrExpression(); } case TokenType.StringLiteral: if (isDereferenceOperator(peek(1))) { return variableOrExpression(); } else { return next(true); } case TokenType.VariableName: case TokenType.Dollar: case TokenType.Array: case TokenType.OpenBracket: case TokenType.Backslash: case TokenType.Name: case TokenType.Namespace: case TokenType.OpenParenthesis: return variableOrExpression(); case TokenType.PlusPlus: return unaryExpression(PhraseType.PrefixIncrementExpression); case TokenType.MinusMinus: return unaryExpression(PhraseType.PrefixDecrementExpression); case TokenType.Plus: case TokenType.Minus: case TokenType.Exclamation: case TokenType.Tilde: return unaryExpression(PhraseType.UnaryOpExpression); case TokenType.AtSymbol: return unaryExpression(PhraseType.ErrorControlExpression); case TokenType.IntegerCast: case TokenType.FloatCast: case TokenType.StringCast: case TokenType.ArrayCast: case TokenType.ObjectCast: case TokenType.BooleanCast: case TokenType.UnsetCast: return unaryExpression(PhraseType.CastExpression); case TokenType.List: return listIntrinsic(); case TokenType.Clone: return cloneExpression(); case TokenType.New: return objectCreationExpression(); case TokenType.FloatingLiteral: case TokenType.IntegerLiteral: case TokenType.LineConstant: case TokenType.FileConstant: case TokenType.DirectoryConstant: case TokenType.TraitConstant: case TokenType.MethodConstant: case TokenType.FunctionConstant: case TokenType.NamespaceConstant: case TokenType.ClassConstant: return next(true); case TokenType.StartHeredoc: return heredocStringLiteral(); case TokenType.DoubleQuote: return doubleQuotedStringLiteral(); case TokenType.Backtick: return shellCommandExpression(); case TokenType.Print: return printIntrinsic(); case TokenType.Yield: return yieldExpression(); case TokenType.YieldFrom: return yieldFromExpression(); case TokenType.Function: return anonymousFunctionCreationExpression(); case TokenType.Include: return scriptInclusion(PhraseType.IncludeExpression); case TokenType.IncludeOnce: return scriptInclusion(PhraseType.IncludeOnceExpression); case TokenType.Require: return scriptInclusion(PhraseType.RequireExpression); case TokenType.RequireOnce: return scriptInclusion(PhraseType.RequireOnceExpression); case TokenType.Eval: return evalIntrinsic(); case TokenType.Empty: return emptyIntrinsic(); case TokenType.Exit: return exitIntrinsic(); case TokenType.Isset: return issetIntrinsic(); default: //error start(PhraseType.ErrorExpression); error(); return end(); } } function exitIntrinsic() { let p = start(PhraseType.ExitIntrinsic); next(); //exit or die if (optional(TokenType.OpenParenthesis)) { if (isExpressionStart(peek())) { p.children.push(expression(0)); } expect(TokenType.CloseParenthesis); } return end(); } function issetIntrinsic() { let p = start(PhraseType.IssetIntrinsic); next(); //isset expect(TokenType.OpenParenthesis); p.children.push(variableList([TokenType.CloseParenthesis])); expect(TokenType.CloseParenthesis); return end(); } function emptyIntrinsic() { let p = start(PhraseType.EmptyIntrinsic); next(); //keyword expect(TokenType.OpenParenthesis); p.children.push(expression(0)); expect(TokenType.CloseParenthesis); return end(); } function evalIntrinsic() { let p = start(PhraseType.EvalIntrinsic); next(); //keyword expect(TokenType.OpenParenthesis); p.children.push(expression(0)); expect(TokenType.CloseParenthesis); return end(); } function scriptInclusion(phraseType: PhraseType) { let p = start(phraseType); next(); //keyword p.children.push(expression(0)); return end(); } function printIntrinsic() { let p = start(PhraseType.PrintIntrinsic); next(); //keyword p.children.push(expression(0)); return end(); } function yieldFromExpression() { let p = start(PhraseType.YieldFromExpression); next(); //keyword p.children.push(expression(0)); return end(); } function yieldExpression() { let p = start(PhraseType.YieldExpression); next(); //yield if (!isExpressionStart(peek())) { return end(); } let keyOrValue = expression(0); p.children.push(keyOrValue); if (optional(TokenType.FatArrow)) { p.children.push(expression(0)); } return end(); } function shellCommandExpression() { let p = start(PhraseType.ShellCommandExpression); next(); //` p.children.push(encapsulatedVariableList(TokenType.Backtick)); expect(TokenType.Backtick); return end(); } function doubleQuotedStringLiteral() { let p = start(PhraseType.DoubleQuotedStringLiteral); next(); //" p.children.push(encapsulatedVariableList(TokenType.DoubleQuote)); expect(TokenType.DoubleQuote); return end(); } function encapsulatedVariableList(breakOn: TokenType) { return list( PhraseType.EncapsulatedVariableList, encapsulatedVariable, isEncapsulatedVariableStart, [breakOn], encapsulatedVariableListRecoverSet ); } function isEncapsulatedVariableStart(t: Token) { switch (t.tokenType) { case TokenType.EncapsulatedAndWhitespace: case TokenType.VariableName: case TokenType.DollarCurlyOpen: case TokenType.CurlyOpen: return true; default: return false; } } function encapsulatedVariable() { switch (peek().tokenType) { case TokenType.EncapsulatedAndWhitespace: return next(true); case TokenType.VariableName: let t = peek(1); if (t.tokenType === TokenType.OpenBracket) { return encapsulatedDimension(); } else if (t.tokenType === TokenType.Arrow) { return encapsulatedProperty(); } else { return simpleVariable(); } case TokenType.DollarCurlyOpen: return dollarCurlyOpenEncapsulatedVariable(); case TokenType.CurlyOpen: return curlyOpenEncapsulatedVariable(); default: throwUnexpectedTokenError(peek()); } } function curlyOpenEncapsulatedVariable() { let p = start(PhraseType.EncapsulatedVariable); next(); //{ p.children.push(variable(variableAtom())); expect(TokenType.CloseBrace); return end(); } function dollarCurlyOpenEncapsulatedVariable() { let p = start(PhraseType.EncapsulatedVariable); next(); //${ let t = peek(); if (t.tokenType === TokenType.VariableName) { if (peek(1).tokenType === TokenType.OpenBracket) { p.children.push(dollarCurlyEncapsulatedDimension()); } else { let sv = start(PhraseType.SimpleVariable); next(); p.children.push(end()); } } else if (isExpressionStart(t)) { p.children.push(expression(0)); } else { error(); } expect(TokenType.CloseBrace); return end(); } function dollarCurlyEncapsulatedDimension() { let p = start(PhraseType.SubscriptExpression); next(); //VariableName next(); // [ p.children.push(expression(0)); expect(TokenType.CloseBracket); return end(); } function encapsulatedDimension() { let p = start(PhraseType.SubscriptExpression); p.children.push(simpleVariable()); //T_VARIABLE next(); //[ switch (peek().tokenType) { case TokenType.Name: case TokenType.IntegerLiteral: next(); break; case TokenType.VariableName: p.children.push(simpleVariable()); break; case TokenType.Minus: let u = start(PhraseType.UnaryOpExpression); next(); //- expect(TokenType.IntegerLiteral); p.children.push(end()); break; default: //error error(); break; } expect(TokenType.CloseBracket); return end(); } function encapsulatedProperty() { let p = start(PhraseType.PropertyAccessExpression); p.children.push(simpleVariable()); next(); //-> expect(TokenType.Name); return end(); } function heredocStringLiteral() { let p = start(PhraseType.HeredocStringLiteral); next(); //StartHeredoc p.children.push(encapsulatedVariableList(TokenType.EndHeredoc)); expect(TokenType.EndHeredoc); return end(); } function anonymousClassDeclaration() { let p = start(PhraseType.AnonymousClassDeclaration); p.children.push(anonymousClassDeclarationHeader()); p.children.push(typeDeclarationBody( PhraseType.ClassDeclarationBody, isClassMemberStart, classMemberDeclarationList )); return end(); } function anonymousClassDeclarationHeader() { let p = start(PhraseType.AnonymousClassDeclarationHeader); next(); //class if (optional(TokenType.OpenParenthesis)) { if (isArgumentStart(peek())) { p.children.push(argumentList()); } expect(TokenType.CloseParenthesis); } if (peek().tokenType === TokenType.Extends) { p.children.push(classBaseClause()); } if (peek().tokenType === TokenType.Implements) { p.children.push(classInterfaceClause()); } return end(); } function classInterfaceClause() { let p = start(PhraseType.ClassInterfaceClause); next(); //implements p.children.push(qualifiedNameList([TokenType.OpenBrace])); return end(); } function classMemberDeclarationList() { return list( PhraseType.ClassMemberDeclarationList, classMemberDeclaration, isClassMemberStart, [TokenType.CloseBrace], classMemberDeclarationListRecoverSet ); } function isClassMemberStart(t: Token) { switch (t.tokenType) { case TokenType.Public: case TokenType.Protected: case TokenType.Private: case TokenType.Static: case TokenType.Abstract: case TokenType.Final: case TokenType.Function: case TokenType.Var: case TokenType.Const: case TokenType.Use: return true; default: return false; } } function classMemberDeclaration() { let p = start(PhraseType.ErrorClassMemberDeclaration); let t = peek(); switch (t.tokenType) { case TokenType.Public: case TokenType.Protected: case TokenType.Private: case TokenType.Static: case TokenType.Abstract: case TokenType.Final: let modifiers = memberModifierList(); t = peek(); if (t.tokenType === TokenType.VariableName) { p.children.push(modifiers); return propertyDeclaration(p); } else if (t.tokenType === TokenType.Function) { return methodDeclaration(p, modifiers); } else if (t.tokenType === TokenType.Const) { p.children.push(modifiers); return classConstDeclaration(p); } else { //error p.children.push(modifiers); error(); return end(); } case TokenType.Function: return methodDeclaration(p, null); case TokenType.Var: next(); return propertyDeclaration(p); case TokenType.Const: return classConstDeclaration(p); case TokenType.Use: return traitUseClause(p); default: //should not get here throwUnexpectedTokenError(t); } } function throwUnexpectedTokenError(t: Token) { throw new Error(`Unexpected token: ${t.tokenType}`); } function traitUseClause(p: Phrase) { p.phraseType = PhraseType.TraitUseClause; next(); //use p.children.push(qualifiedNameList([TokenType.Semicolon, TokenType.OpenBrace])); p.children.push(traitUseSpecification()); return end(); } function traitUseSpecification() { let p = start(PhraseType.TraitUseSpecification); let t = expectOneOf([TokenType.Semicolon, TokenType.OpenBrace]); if (t && t.tokenType === TokenType.OpenBrace) { if (isTraitAdaptationStart(peek())) { p.children.push(traitAdaptationList()); } expect(TokenType.CloseBrace); } return end(); } function traitAdaptationList() { return list( PhraseType.TraitAdaptationList, traitAdaptation, isTraitAdaptationStart, [TokenType.CloseBrace], ); } function isTraitAdaptationStart(t: Token) { switch (t.tokenType) { case TokenType.Name: case TokenType.Backslash: case TokenType.Namespace: return true; default: return isSemiReservedToken(t); } } function traitAdaptation() { let p = start(PhraseType.ErrorTraitAdaptation); let t = peek(); let t2 = peek(1); if (t.tokenType === TokenType.Namespace || t.tokenType === TokenType.Backslash || (t.tokenType === TokenType.Name && (t2.tokenType === TokenType.ColonColon || t2.tokenType === TokenType.Backslash))) { p.children.push(methodReference()); if (peek().tokenType === TokenType.InsteadOf) { next(); return traitPrecedence(p); } } else if (t.tokenType === TokenType.Name || isSemiReservedToken(t)) { let methodRef = start(PhraseType.MethodReference); methodRef.children.push(identifier()); p.children.push(end()); } else { //error error(); return end(); } return traitAlias(p); } function traitAlias(p: Phrase) { p.phraseType = PhraseType.TraitAlias; expect(TokenType.As); let t = peek(); if (t.tokenType === TokenType.Name || isReservedToken(t)) { p.children.push(identifier()); } else if (isMemberModifier(t)) { next(); t = peek(); if (t.tokenType === TokenType.Name || isSemiReservedToken(t)) { p.children.push(identifier()); } } else { error(); } expect(TokenType.Semicolon); return end(); } function traitPrecedence(p: Phrase) { p.phraseType = PhraseType.TraitPrecedence; p.children.push(qualifiedNameList([TokenType.Semicolon])); expect(TokenType.Semicolon); return end(); } function methodReference() { let p = start(PhraseType.MethodReference); p.children.push(qualifiedName()); expect(TokenType.ColonColon); p.children.push(identifier()); return end(); } function methodDeclarationHeader(memberModifers: Phrase) { let p = start(PhraseType.MethodDeclarationHeader, true); if (memberModifers) { p.children.push(memberModifers); } next(); //function optional(TokenType.Ampersand); p.children.push(identifier()); expect(TokenType.OpenParenthesis); if (isParameterStart(peek())) { p.children.push(delimitedList( PhraseType.ParameterDeclarationList, parameterDeclaration, isParameterStart, TokenType.Comma, [TokenType.CloseParenthesis] )); } expect(TokenType.CloseParenthesis); if (peek().tokenType === TokenType.Colon) { p.children.push(returnType()); } return end(); } function methodDeclaration(p: Phrase, memberModifers: Phrase) { p.phraseType = PhraseType.MethodDeclaration; p.children.push(methodDeclarationHeader(memberModifers)); p.children.push(methodDeclarationBody()); return end(); } function methodDeclarationBody() { let p = start(PhraseType.MethodDeclarationBody); if (peek().tokenType === TokenType.Semicolon) { next(); } else { p.children.push(compoundStatement()); } return end(); } function identifier() { let p = start(PhraseType.Identifier); let t = peek(); if (t.tokenType === TokenType.Name || isSemiReservedToken(t)) { next(); } else { error(); } return end(); } function interfaceDeclaration() { let p = start(PhraseType.InterfaceDeclaration); p.children.push(interfaceDeclarationHeader()); p.children.push(typeDeclarationBody( PhraseType.InterfaceDeclarationBody, isClassMemberStart, interfaceMemberDeclarations )); return end(); } function typeDeclarationBody<T extends Phrase>(phraseType: PhraseType, elementStartPredicate: Predicate, listFunction: () => T) { let p = start(phraseType); expect(TokenType.OpenBrace); if (elementStartPredicate(peek())) { p.children.push(listFunction()); } expect(TokenType.CloseBrace); return end(); } function interfaceMemberDeclarations() { return list( PhraseType.InterfaceMemberDeclarationList, classMemberDeclaration, isClassMemberStart, [TokenType.CloseBrace], classMemberDeclarationListRecoverSet ); } function interfaceDeclarationHeader() { let p = start(PhraseType.InterfaceDeclarationHeader); next(); //interface expect(TokenType.Name); if (peek().tokenType === TokenType.Extends) { p.children.push(interfaceBaseClause()); } return end(); } function interfaceBaseClause() { let p = start(PhraseType.InterfaceBaseClause); next(); //extends p.children.push(qualifiedNameList([TokenType.OpenBrace])); return end(); } function traitDeclaration() { let p = start(PhraseType.TraitDeclaration); p.children.push(traitDeclarationHeader()); p.children.push(typeDeclarationBody( PhraseType.TraitDeclarationBody, isClassMemberStart, traitMemberDeclarations )); return end(); } function traitDeclarationHeader() { let p = start(PhraseType.TraitDeclarationHeader); next(); //trait expect(TokenType.Name); return end(); } function traitMemberDeclarations() { return list( PhraseType.TraitMemberDeclarationList, classMemberDeclaration, isClassMemberStart, [TokenType.CloseBrace], classMemberDeclarationListRecoverSet ); } function functionDeclaration() { let p = start(PhraseType.FunctionDeclaration); p.children.push(functionDeclarationHeader()); p.children.push(functionDeclarationBody()); return end(); } function functionDeclarationBody() { let cs = compoundStatement(); cs.phraseType = PhraseType.FunctionDeclarationBody; return cs; } function functionDeclarationHeader() { let p = start(PhraseType.FunctionDeclarationHeader); next(); //function optional(TokenType.Ampersand); expect(TokenType.Name); expect(TokenType.OpenParenthesis); if (isParameterStart(peek())) { p.children.push(delimitedList( PhraseType.ParameterDeclarationList, parameterDeclaration, isParameterStart, TokenType.Comma, [TokenType.CloseParenthesis] )); } expect(TokenType.CloseParenthesis); if (peek().tokenType === TokenType.Colon) { p.children.push(returnType()); } return end(); } function isParameterStart(t: Token) { switch (t.tokenType) { case TokenType.Ampersand: case TokenType.Ellipsis: case TokenType.VariableName: return true; default: return isTypeDeclarationStart(t); } } function classDeclaration() { let p = start(PhraseType.ClassDeclaration); p.children.push(classDeclarationHeader()); p.children.push(typeDeclarationBody( PhraseType.ClassDeclarationBody, isClassMemberStart, classMemberDeclarationList )); return end(); } function classDeclarationHeader() { let p = start(PhraseType.ClassDeclarationHeader); optionalOneOf([TokenType.Abstract, TokenType.Final]); expect(TokenType.Class); expect(TokenType.Name); if (peek().tokenType === TokenType.Extends) { p.children.push(classBaseClause()); } if (peek().tokenType === TokenType.Implements) { p.children.push(classInterfaceClause()); } return end(); } function classBaseClause() { let p = start(PhraseType.ClassBaseClause); next(); //extends p.children.push(qualifiedName()); return end(); } function compoundStatement() { let p = start(PhraseType.CompoundStatement); expect(TokenType.OpenBrace); if (isStatementStart(peek())) { p.children.push(statementList([TokenType.CloseBrace])); } expect(TokenType.CloseBrace); return end(); } function statement() { let t = peek(); switch (t.tokenType) { case TokenType.Namespace: return namespaceDefinition(); case TokenType.Use: return namespaceUseDeclaration(); case TokenType.HaltCompiler: return haltCompilerStatement(); case TokenType.Const: return constDeclaration(); case TokenType.Function: { let p1 = peek(1); if( p1.tokenType === TokenType.OpenParenthesis || (p1.tokenType === TokenType.Ampersand && peek(2).tokenType === TokenType.OpenParenthesis) ) { //anon fn without assignment return expressionStatement(); } else { return functionDeclaration(); } } case TokenType.Class: case TokenType.Abstract: case TokenType.Final: return classDeclaration(); case TokenType.Trait: return traitDeclaration(); case TokenType.Interface: return interfaceDeclaration(); case TokenType.OpenBrace: return compoundStatement(); case TokenType.If: return ifStatement(); case TokenType.While: return whileStatement(); case TokenType.Do: return doStatement(); case TokenType.For: return forStatement(); case TokenType.Switch: return switchStatement(); case TokenType.Break: return breakStatement(); case TokenType.Continue: return continueStatement(); case TokenType.Return: return returnStatement(); case TokenType.Global: return globalDeclaration(); case TokenType.Static: if (peek(1).tokenType === TokenType.VariableName && [TokenType.Semicolon, TokenType.Comma, TokenType.CloseTag, TokenType.Equals].indexOf(peek(2).tokenType) >= 0) { return functionStaticDeclaration(); } else { return expressionStatement(); } case TokenType.Text: case TokenType.OpenTag: case TokenType.CloseTag: return inlineText(); case TokenType.ForEach: return foreachStatement(); case TokenType.Declare: return declareStatement(); case TokenType.Try: return tryStatement(); case TokenType.Throw: return throwStatement(); case TokenType.Goto: return gotoStatement(); case TokenType.Echo: case TokenType.OpenTagEcho: return echoIntrinsic(); case TokenType.Unset: return unsetIntrinsic(); case TokenType.Semicolon: return nullStatement(); case TokenType.Name: if (peek(1).tokenType === TokenType.Colon) { return namedLabelStatement(); } //fall though default: return expressionStatement(); } } function inlineText() { let p = start(PhraseType.InlineText); optional(TokenType.CloseTag); optional(TokenType.Text); optional(TokenType.OpenTag); return end(); } func