php7parser
Version:
1,702 lines (1,379 loc) • 100 kB
text/typescript
/* 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