/// <summary> /// Reads an identifier token. /// </summary> /// <param name="firstChar"> The first character of the identifier. </param> /// <returns> An identifier token, literal token or a keyword token. </returns> private Token ReadIdentifier(int firstChar) { // Process the first character. var name = new StringBuilder(); if (firstChar == '\\') { // Unicode escape sequence. if (ReadNextChar() != 'u') { throw new JavaScriptException(this.engine, "SyntaxError", "Invalid escape sequence in identifier.", this.lineNumber, this.Source.Path); } firstChar = ReadHexEscapeSequence(4); if (IsIdentifierChar(firstChar) == false) { throw new JavaScriptException(this.engine, "SyntaxError", "Invalid character in identifier.", this.lineNumber, this.Source.Path); } } name.Append((char)firstChar); // Read characters until we hit the first non-identifier character. while (true) { int c = this.reader.Peek(); if (IsIdentifierChar(c) == false || c == -1) { break; } if (c == '\\') { // Unicode escape sequence. ReadNextChar(); if (ReadNextChar() != 'u') { throw new JavaScriptException(this.engine, "SyntaxError", "Invalid escape sequence in identifier.", this.lineNumber, this.Source.Path); } c = ReadHexEscapeSequence(4); if (IsIdentifierChar(c) == false) { throw new JavaScriptException(this.engine, "SyntaxError", "Invalid character in identifier.", this.lineNumber, this.Source.Path); } name.Append((char)c); } else { // Add the character we peeked at to the identifier name. name.Append((char)c); // Advance the input stream. ReadNextChar(); } } // Check if the identifier is actually a keyword, boolean literal, or null literal. return(KeywordToken.FromString(name.ToString(), this.engine.CompatibilityMode, this.StrictMode)); }
/// <summary> /// Declares a variable or function in this scope. This will be initialized with the value /// of the given expression. /// </summary> /// <param name="keyword"> The keyword that was used to declare the variable (var, let or /// const). </param> /// <param name="name"> The name of the variable. </param> /// <param name="hoistedFunction"> The function value to hoist to the top of the scope. /// Should be <c>null</c> for everything except function declarations. </param> /// <returns> A reference to the variable that was declared. </returns> internal void DeclareVariable(KeywordToken keyword, string name, FunctionExpression hoistedFunction = null) { if (name == null) { throw new ArgumentNullException(nameof(name)); } // If variables cannot be declared in the scope, try the parent scope instead. var declarationScope = this; if (keyword == KeywordToken.Var) { while (declarationScope != null && (declarationScope.Type == ScopeType.Block || declarationScope.Type == ScopeType.With)) { declarationScope = declarationScope.ParentScope; } } if (declarationScope != null && !declarationScope.variables.ContainsKey(name)) { // This is a local variable that has not been declared before. var variable = new DeclaredVariable() { Scope = declarationScope, Index = declarationScope.variables.Count, Keyword = keyword, Name = name, }; declarationScope.variables.Add(name, variable); } if (hoistedFunction != null) { var hoistedScope = this; while (hoistedScope != null && hoistedScope.Type == ScopeType.Block) { hoistedScope = hoistedScope.ParentScope; } if (hoistedScope.hoistedFunctions == null) { hoistedScope.hoistedFunctions = new Dictionary <string, FunctionExpression>(); } hoistedScope.hoistedFunctions[name] = hoistedFunction; } }
/// <summary> /// Parses a var, let or const statement. /// </summary> /// <param name="keyword"> Indicates which type of statement is being parsed. Must be var, /// let or const. </param> /// <returns> A variable declaration statement. </returns> private VarStatement ParseVarLetOrConst(KeywordToken keyword) { var result = new VarStatement(this.labelsForCurrentStatement, keyword == KeywordToken.Var ? this.currentVarScope : this.currentLetScope); // Read past the first token (var, let or const). this.Expect(keyword); // Keep track of the start of the statement so that source debugging works correctly. var start = this.PositionAfterWhitespace; // There can be multiple declarations. while (true) { var declaration = new VariableDeclaration(); // The next token must be a variable name. declaration.VariableName = this.ExpectIdentifier(); ValidateVariableName(declaration.VariableName); // Add the variable to the current function's list of local variables. this.currentVarScope.DeclareVariable(declaration.VariableName, this.context == CodeContext.Function ? null : new LiteralExpression(Undefined.Value), writable: true, deletable: this.context == CodeContext.Eval); // The next token is either an equals sign (=), a semi-colon or a comma. if (this.nextToken == PunctuatorToken.Assignment) { // Read past the equals token (=). this.Expect(PunctuatorToken.Assignment); // Read the setter expression. declaration.InitExpression = ParseExpression(PunctuatorToken.Semicolon, PunctuatorToken.Comma); // Record the portion of the source document that will be highlighted when debugging. declaration.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); } // Add the declaration to the result. result.Declarations.Add(declaration); // Check if we are at the end of the statement. if (this.AtValidEndOfStatement() == true && this.nextToken != PunctuatorToken.Comma) break; // Read past the comma token. this.Expect(PunctuatorToken.Comma); // Keep track of the start of the statement so that source debugging works correctly. start = this.PositionAfterWhitespace; } // Consume the end of the statement. this.ExpectEndOfStatement(); return result; }
public VariableDeclaration(KeywordToken keyword, string name) { Keyword = keyword; VariableName = name; }