class RubyRunJs::Parser
Constants
- Messages
Error messages should be identical to V8.
- PropertyKind
- Syntax
- Token
- TokenName
Public Instance Methods
The following defs are needed only when the option to preserve the comments is active.
# File lib/ruby_run_js/jsparser.rb, line 2939 def addComment(type, value, start, end1, loc) assert(start.instance_of? Fixnum, 'Comment must have valid position') # Because the way the actual token is scanned, often the comments # (if any) are skipped twice during the lexical analysis. # Thus, we need to skip adding a comment if the comment array already # handled it. if (@extra[:comments].length > 0) if (@extra[:comments][@extra[:comments].length - 1][:range][1] > start) return end end @extra[:comments].push({ type: type, value: value, range: [start, end1], loc: loc }) end
# File lib/ruby_run_js/jsparser.rb, line 934 def advance() skipComment() if (@index >= @length) return { type: Token[:EOF], lineNumber: @lineNumber, lineStart: @lineStart, range: [@index, @index] } end token = scanPunctuator() if token != nil return token end ch = @source[@index] if (ch == "'" || ch == '"') return scanStringLiteral() end if (ch == '.' || isDecimalDigit(ch)) return scanNumericLiteral() end token = scanIdentifier() if token != nil return token end throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end
Ensure the condition is true, otherwise throw an error. This is only to have a better contract semantic, i. another safety net to catch a logic error. The condition shall be fulfilled in normal case. Do NOT use this to enforce a certain condition on any user input.
# File lib/ruby_run_js/jsparser.rb, line 124 def assert(condition, message) if (!condition) raise SyntaxError.new 'ASSERT: ' + message end end
# File lib/ruby_run_js/jsparser.rb, line 3131 def collectRegex() skipComment() pos = @index loc = { start: { line: @lineNumber, column: @index - @lineStart } } regex = @extra.scanRegExp() loc[:end] = { line: @lineNumber, column: @index - @lineStart } # Pop the previous token, which is likely '/' or '/=' if (@extra[:tokens].length > 0) token = @extra[:tokens][@extra[:tokens].length - 1] if (token[:range][0] == pos && token[:type] == 'Punctuator') if (token[:value] == '/' || token[:value] == '/=') @extra[:tokens].pop() end end end @extra[:tokens].push({ type: 'RegularExpression', value: regex[:literal], range: [pos, @index], loc: loc }) return regex end
# File lib/ruby_run_js/jsparser.rb, line 3101 def collectToken() skipComment() start = @index loc = { start: { line: @lineNumber, column: @index - @lineStart } } token = @extra.advance() loc[:end] = { line: @lineNumber, column: @index - @lineStart } if (token[:type] != Token[:EOF]) range = [token[:range][0], token[:range][1]] value = sliceSource(token[:range][0], token[:range][1]) @extra[:tokens].push({ type: TokenName[token[:type]], value: value, range: range, loc: loc }) end return token end
# File lib/ruby_run_js/jsparser.rb, line 1125 def consumeSemicolon() # Catch the very common case first. if (@source[@index] == ';') lex() return end line = @lineNumber skipComment() if (@lineNumber != line) return end if (match(';')) lex() return end token = lookahead() if (token[:type] != Token[:EOF] && !match('}')) throwUnexpected(token) end end
# File lib/ruby_run_js/jsparser.rb, line 3189 def createLiteral(token) unless @extra[:raw] return { type: Syntax[:Literal], value: token[:value] } end return { type: Syntax[:Literal], value: token[:value], raw: sliceSource(token[:range][0], token[:range][1]) } end
# File lib/ruby_run_js/jsparser.rb, line 3204 def createLocationMarker() marker = {} marker[:range] = [@index, @index] marker[:loc] = { start: { line: @lineNumber, column: @index - @lineStart }, end: { line: @lineNumber, column: @index - @lineStart } } marker[:end] = lambda { this.range[1] = @index this[:loc][:end][:line] = @lineNumber this[:loc][:end][:column] = @index - @lineStart } marker[:applyGroup] = lambda do |node| if (@extra[:range]) node[:groupRange] = [this.range[0], this.range[1]] end if (@extra[:loc]) node[:groupLoc] = { start: { line: this[:loc].start[:line], column: this[:loc][:start][:column] }, end: { line: this[:loc].end[:line], column: this[:loc][:end][:column] } } end end marker[:apply] = lambda do |node| if (@extra[:range]) node[:range] = [this.range[0], this.range[1]] end if (@extra[:loc]) node[:loc] = { start: { line: this[:loc].start[:line], column: this[:loc][:start][:column] }, end: { line: this[:loc].end[:line], column: this[:loc][:end][:column] } } end end return marker end
# File lib/ruby_run_js/jsparser.rb, line 234 def curCharAndMoveNext c = @source[@index] @index += 1 c end
Expect the next token to match the specified punctuator. If not, an exception will be thrown.
# File lib/ruby_run_js/jsparser.rb, line 1071 def expect(value) token = lex() if (token[:type] != Token[:Punctuator] || token[:value] != value) throwUnexpected(token) end end
Expect the next token to match the specified keyword. If not, an exception will be thrown.
# File lib/ruby_run_js/jsparser.rb, line 1081 def expectKeyword(keyword) token = lex() if (token[:type] != Token[:Keyword] || token[:value] != keyword) throwUnexpected(token) end end
# File lib/ruby_run_js/jsparser.rb, line 3080 def filterCommentLocation() comments = [] @extra[:comments].length.times do |i| entry = @extra.comments[i] comment = { type: entry[:type], value: entry[:value] } if (@extra[:range]) comment[:range] = entry[:range] end if (@extra[:loc]) comment[:loc] = entry[:loc] end comments.push(comment) end @extra[:comments] = comments end
# File lib/ruby_run_js/jsparser.rb, line 3168 def filterTokenLocation() tokens = [] @extra[:tokens].length.times do |i| entry = @extra[:tokens][i] token = { type: entry[:type], value: entry[:value] } if (@extra[:range]) token[:range] = entry[:range] end if (@extra[:loc]) token[:loc] = entry[:loc] end tokens.push(token) end @extra[:tokens] = tokens end
# File lib/ruby_run_js/jsparser.rb, line 134 def isDecimalDigit(ch) return ch && '0123456789'.include?(ch) end
7.6.1.2 Future Reserved Words
# File lib/ruby_run_js/jsparser.rb, line 180 def isFutureReservedWord(id) %w(class enum export extends import super).include? id end
# File lib/ruby_run_js/jsparser.rb, line 138 def isHexDigit(ch) return ch && '0123456789abcdefABCDEF'.include?(ch) end
# File lib/ruby_run_js/jsparser.rb, line 927 def isIdentifierName(token) return token[:type] == Token[:Identifier] || token[:type] == Token[:Keyword] || token[:type] == Token[:BooleanLiteral] || token[:type] == Token[:NullLiteral] end
# File lib/ruby_run_js/jsparser.rb, line 171 def isIdentifierPart(ch) return (ch == "$") || (ch == "_") || (ch == "\\") || (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ((ch >= "0") && (ch <= "9")) || false# ((ch.ord() >= 0x80) && NonAsciiIdentifierPart.match?(ch)) end
7.6 Identifier Names and Identifiers
# File lib/ruby_run_js/jsparser.rb, line 165 def isIdentifierStart(ch) return (ch == "$") || (ch == "_") || (ch == "\\") || (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || false# ((ch.ord() >= 0x80) && NonAsciiIdentifierStart.match?(ch)) end
7.6.1.1 Keywords
# File lib/ruby_run_js/jsparser.rb, line 194 def isKeyword(id) keyword = false case id.length when 2 keyword = (id == 'if') || (id == 'in') || (id == 'do') when 3 keyword = (id == 'var') || (id == 'for') || (id == 'new') || (id == 'try') when 4 keyword = (id == 'this') || (id == 'else') || (id == 'case') || (id == 'void') || (id == 'with') when 5 keyword = (id == 'while') || (id == 'break') || (id == 'catch') || (id == 'throw') when 6 keyword = (id == 'return') || (id == 'typeof') || (id == 'delete') || (id == 'switch') when 7 keyword = (id == 'default') || (id == 'finally') when 8 keyword = (id == 'function') || (id == 'continue') || (id == 'debugger') when 10 keyword = (id == 'instanceof') end return true if keyword case id # Future reserved words. # 'const' is specialized as Keyword in V8. when 'const' return true # For compatiblity to SpiderMonkey and ES[:next] when 'yield','let' return true end if (@strict && isStrictModeReservedWord(id)) return true end return isFutureReservedWord(id) end
Return true if provided expression is LeftHandSideExpression
# File lib/ruby_run_js/jsparser.rb, line 1151 def isLeftHandSide(expr) return expr[:type] == Syntax[:Identifier] || expr[:type] == Syntax[:MemberExpression] end
7.3 Line Terminators
# File lib/ruby_run_js/jsparser.rb, line 159 def isLineTerminator(ch) ["\n", "\r" ,"\u2028", "\u2029"].include?(ch) end
# File lib/ruby_run_js/jsparser.rb, line 142 def isOctalDigit(ch) return ch && '01234567'.include?(ch) end
# File lib/ruby_run_js/jsparser.rb, line 188 def isRestrictedWord(id) return id == 'eval' || id == 'arguments' end
# File lib/ruby_run_js/jsparser.rb, line 184 def isStrictModeReservedWord(id) %w(implements interface package private protected public static yield let).include? id end
7.2 White Space
# File lib/ruby_run_js/jsparser.rb, line 149 def isWhiteSpace(ch) return false unless ch return (ch == " ") || (ch == "\u0009") || (ch == "\u000B") || (ch == "\u000C") || (ch == "\u00A0") || (ch.ord() >= 0x1680 && "\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF".include?(ch)) end
# File lib/ruby_run_js/jsparser.rb, line 969 def lex() if (@buffer) @index = @buffer[:range][1] @lineNumber = @buffer[:lineNumber] @lineStart = @buffer[:lineStart] token = @buffer @buffer = nil return token end @buffer = nil return advance() end
# File lib/ruby_run_js/jsparser.rb, line 984 def lookahead() if (@buffer != nil) return @buffer end pos = @index line = @lineNumber start = @lineStart @buffer = advance() @index = pos @lineNumber = line @lineStart = start return @buffer end
Return true if the next token matches the specified punctuator.
# File lib/ruby_run_js/jsparser.rb, line 1090 def match(value) token = lookahead() return token[:type] == Token[:Punctuator] && token[:value] == value end
Return true if the next token is an assignment operator
# File lib/ruby_run_js/jsparser.rb, line 1104 def matchAssign() token = lookahead() op = token[:value] if (token[:type] != Token[:Punctuator]) return false end return op == '=' || op == '*=' || op == '/=' || op == '%=' || op == '+=' || op == '-=' || op == '<<=' || op == '>>=' || op == '>>>=' || op == '&=' || op == '^=' || op == '|=' end
Return true if the next token matches the specified keyword
# File lib/ruby_run_js/jsparser.rb, line 1097 def matchKeyword(keyword) token = lookahead() return token[:type] == Token[:Keyword] && token[:value] == keyword end
def filterGroup(node)
n = (node.instance_of?(Array)) ? [] : {} for (i in node) { if (node.hasOwnProperty(i) && i != 'groupRange' && i != 'groupLoc') entry = node[i] if (entry == nil || typeof entry != 'object' || entry instanceof RegExp) n[i] = entry else n[i] = filterGroup(entry) end end end return n
end
# File lib/ruby_run_js/jsparser.rb, line 3368 def parse(code, options = nil) @source = code @index = 0 @lineNumber = (@source.length > 0) ? 1 : 0 @lineStart = 0 @length = @source.length @buffer = nil @state = { allowIn: true, labelSet: {}, indefBody: false, inIteration: false, inSwitch: false } @extra = {} @extra[:range] = false @extra[:loc] = false @extra[:raw] = false if (options) @extra[:range] = options[:range] == true @extra[:loc] = options[:loc] == true @extra[:raw] = options[:raw] = true if (options[:tokens]) @extra[:tokens] = [] end if (options[:comment]) @extra[:comments] = [] end if (options[:tolerant]) @extra[:errors] = [] end end program = parseProgram() if (@extra[:comments]) filterCommentLocation() program[:comments] = @extra[:comments] end if (@extra[:tokens]) filterTokenLocation() program[:tokens] = @extra[:tokens] end if (@extra[:errors]) program[:errors] = @extra[:errors] end if (@extra[:range] || @extra[:loc]) # program[:body] = filterGroup(program[:body]) end return program end
11.6 Additive Operators
# File lib/ruby_run_js/jsparser.rb, line 1647 def parseAdditiveExpression() expr = parseMultiplicativeExpression() while (match('+') || match('-')) expr = { type: Syntax[:BinaryExpression], operator: lex()[:value], left: expr, right: parseMultiplicativeExpression() } end return expr end
11.2 Left-Hand-Side Expressions
# File lib/ruby_run_js/jsparser.rb, line 1419 def parseArguments() args = [] expect('(') if (!match(')')) while (@index < @length) args.push(parseAssignmentExpression()) if (match(')')) break end expect(',') end end expect(')') return args end
11.1.4 Array Initialiser
# File lib/ruby_run_js/jsparser.rb, line 1157 def parseArrayInitialiser() elements = [] expect('[') while (!match(']')) if (match(',')) lex() elements.push(nil) else elements.push(parseAssignmentExpression()) if (!match(']')) expect(',') end end end expect(']') return { type: Syntax[:ArrayExpression], elements: elements } end
11.13 Assignment Operators
# File lib/ruby_run_js/jsparser.rb, line 1827 def parseAssignmentExpression() token = lookahead() expr = parseConditionalExpression() if (matchAssign()) # LeftHandSideExpression if (!isLeftHandSide(expr)) throwErrorTolerant(nil, Messages[:InvalidLHSInAssignment]) end # 11.13.1 if (@strict && expr[:type] == Syntax[:Identifier] && isRestrictedWord(expr[:name])) throwErrorTolerant(token, Messages[:StrictLHSAssignment]) end expr = { type: Syntax[:AssignmentExpression], operator: lex()[:value], left: expr, right: parseAssignmentExpression() } end return expr end
11.10 Binary Bitwise Operators
# File lib/ruby_run_js/jsparser.rb, line 1718 def parseBitwiseANDExpression() expr = parseEqualityExpression() while (match('&')) lex() expr = { type: Syntax[:BinaryExpression], operator: '&', left: expr, right: parseEqualityExpression() } end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1750 def parseBitwiseORExpression() expr = parseBitwiseXORExpression() while (match('|')) lex() expr = { type: Syntax[:BinaryExpression], operator: '|', left: expr, right: parseBitwiseXORExpression() } end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1734 def parseBitwiseXORExpression() expr = parseBitwiseANDExpression() while (match('^')) lex() expr = { type: Syntax[:BinaryExpression], operator: '^', left: expr, right: parseBitwiseANDExpression() } end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1897 def parseBlock() expect('{') block = parseStatementList() expect('}') return { type: Syntax[:BlockStatement], body: block } end
12.8 The break @statement
# File lib/ruby_run_js/jsparser.rb, line 2257 def parseBreakStatement() token = nil label = nil expectKeyword('break') # Optimize the most common form: 'break'. if (@source[@index] == ';') lex() if (!(@state[:inIteration] || @state[:inSwitch])) throwError(nil, Messages[:IllegalBreak]) end return { type: Syntax[:BreakStatement], label: nil } end if (peekLineTerminator()) if (!(@state[:inIteration] || @state[:inSwitch])) throwError(nil, Messages[:IllegalBreak]) end return { type: Syntax[:BreakStatement], label: nil } end token = lookahead() if (token[:type] == Token[:Identifier]) label = parseVariableIdentifier() if (false) throwError(nil, Messages[:UnknownLabel], label[:name]) end end consumeSemicolon() if (label == nil && !(@state[:inIteration] || @state[:inSwitch])) throwError(nil, Messages[:IllegalBreak]) end return { type: Syntax[:BreakStatement], label: label } end
12.14 The try @statement
# File lib/ruby_run_js/jsparser.rb, line 2486 def parseCatchClause() expectKeyword('catch') expect('(') if (match(')')) throwUnexpected(lookahead()) end param = parseVariableIdentifier() # 12.14.1 if (@strict && isRestrictedWord(param[:name])) throwErrorTolerant(nil, Messages[:StrictCatchVariable]) end expect(')') return { type: Syntax[:CatchClause], param: param, body: parseBlock() } end
# File lib/ruby_run_js/jsparser.rb, line 1458 def parseComputedMember() expect('[') expr = parseExpression() expect(']') return expr end
11.12 Conditional Operator
# File lib/ruby_run_js/jsparser.rb, line 1802 def parseConditionalExpression() expr = parseLogicalORExpression() if (match('?')) lex() previousAllowIn = @state[:allowIn] @state[:allowIn] = true consequent = parseAssignmentExpression() @state[:allowIn] = previousAllowIn expect(':') expr = { type: Syntax[:ConditionalExpression], test: expr, consequent: consequent, alternate: parseAssignmentExpression() } end return expr end
kind may be `const` or `let` Both are experimental and not in the specification yet. see #wiki[:ecmascript].org/doku.php?id=harmony:const and #wiki[:ecmascript].org/doku.php?id=harmony:let
# File lib/ruby_run_js/jsparser.rb, line 1982 def parseConstLetDeclaration(kind) expectKeyword(kind) declarations = parseVariableDeclarationList(kind) consumeSemicolon() return { type: Syntax[:VariableDeclaration], declarations: declarations, kind: kind } end
12.7 The continue @statement
# File lib/ruby_run_js/jsparser.rb, line 2203 def parseContinueStatement() token = nil label = nil expectKeyword('continue') # Optimize the most common form: 'continue'. if (@source[@index] == ';') lex() if (!@state[:inIteration]) throwError(nil, Messages[:IllegalContinue]) end return { type: Syntax[:ContinueStatement], label: nil } end if (peekLineTerminator()) if (!@state[:inIteration]) throwError(nil, Messages[:IllegalContinue]) end return { type: Syntax[:ContinueStatement], label: nil } end token = lookahead() if (token[:type] == Token[:Identifier]) label = parseVariableIdentifier() if (false) throwError(nil, Messages[:UnknownLabel], label[:name]) end end consumeSemicolon() if (label == nil && !@state[:inIteration]) throwError(nil, Messages[:IllegalContinue]) end return { type: Syntax[:ContinueStatement], label: label } end
12.15 The debugger @statement
# File lib/ruby_run_js/jsparser.rb, line 2542 def parseDebuggerStatement() expectKeyword('debugger') consumeSemicolon() return { type: Syntax[:DebuggerStatement] } end
12.6 Iteration Statements
# File lib/ruby_run_js/jsparser.rb, line 2051 def parseDoWhileStatement() expectKeyword('do') oldInIteration = @state[:inIteration] @state[:inIteration] = true body = parseStatement() @state[:inIteration] = oldInIteration expectKeyword('while') expect('(') test = parseExpression() expect(')') if (match(';')) lex() end return { type: Syntax[:DoWhileStatement], body: body, test: test } end
12.3 Empty Statement
# File lib/ruby_run_js/jsparser.rb, line 1999 def parseEmptyStatement() expect(';') return { type: Syntax[:EmptyStatement] } end
11.9 Equality Operators
# File lib/ruby_run_js/jsparser.rb, line 1701 def parseEqualityExpression() expr = parseRelationalExpression() while (match('==') || match('!=') || match('===') || match('!==')) expr = { type: Syntax[:BinaryExpression], operator: lex()[:value], left: expr, right: parseRelationalExpression() } end return expr end
11.14 Comma Operator
# File lib/ruby_run_js/jsparser.rb, line 1856 def parseExpression() expr = parseAssignmentExpression() if (match(',')) expr = { type: Syntax[:SequenceExpression], expressions: [ expr ] } while (@index < @length) if (!match(',')) break end lex() expr[:expressions].push(parseAssignmentExpression()) end end return expr end
12.4 Expression Statement
# File lib/ruby_run_js/jsparser.rb, line 2009 def parseExpressionStatement() expr = parseExpression() consumeSemicolon() return { type: Syntax[:ExpressionStatement], expression: expr } end
# File lib/ruby_run_js/jsparser.rb, line 2114 def parseForStatement() init = nil test = nil update = nil left = nil expectKeyword('for') expect('(') if (match(';')) lex() else if (matchKeyword('var') || matchKeyword('let')) @state[:allowIn] = false init = parseForVariableDeclaration() @state[:allowIn] = true if (init[:declarations].length == 1 && matchKeyword('in')) lex() left = init right = parseExpression() init = nil end else @state[:allowIn] = false init = parseExpression() @state[:allowIn] = true if (matchKeyword('in')) # LeftHandSideExpression if (!isLeftHandSide(init)) throwErrorTolerant(nil, Messages[:InvalidLHSInForIn]) end lex() left = init right = parseExpression() init = nil end end if (left == nil) expect(';') end end if (left == nil) if (!match(';')) test = parseExpression() end expect(';') if (!match(')')) update = parseExpression() end end expect(')') oldInIteration = @state[:inIteration] @state[:inIteration] = true body = parseStatement() @state[:inIteration] = oldInIteration if (left == nil) return { type: Syntax[:ForStatement], init: init, test: test, update: update, body: body } end return { type: Syntax[:ForInStatement], left: left, right: right, body: body, each: false } end
# File lib/ruby_run_js/jsparser.rb, line 2104 def parseForVariableDeclaration() token = lex() return { type: Syntax[:VariableDeclaration], declarations: parseVariableDeclarationList(), kind: token[:value] } end
# File lib/ruby_run_js/jsparser.rb, line 2701 def parseFunctionDeclaration() params = [] expectKeyword('function') token = lookahead() id = parseVariableIdentifier() if (@strict) if (isRestrictedWord(token[:value])) throwErrorTolerant(token, Messages[:StrictFunctionName]) end else if (isRestrictedWord(token[:value])) firstRestricted = token message = Messages[:StrictFunctionName] elsif (isStrictModeReservedWord(token[:value])) firstRestricted = token message = Messages[:StrictReservedWord] end end expect('(') if (!match(')')) paramSet = {} while (@index < @length) token = lookahead() param = parseVariableIdentifier() if (@strict) if (isRestrictedWord(token[:value])) stricted = token message = Messages[:StrictParamName] end if (false) stricted = token message = Messages[:StrictParamDupe] end elsif (!firstRestricted) if (isRestrictedWord(token[:value])) firstRestricted = token message = Messages[:StrictParamName] elsif (isStrictModeReservedWord(token[:value])) firstRestricted = token message = Messages[:StrictReservedWord] elsif (false) firstRestricted = token message = Messages[:StrictParamDupe] end end params.push(param) paramSet[param[:name]] = true if (match(')')) break end expect(',') end end expect(')') previousStrict = @strict body = parseFunctionSourceElements() if (@strict && firstRestricted) throwError(firstRestricted, message) end if (@strict && stricted) throwErrorTolerant(stricted, message) end @strict = previousStrict return { type: Syntax[:FunctionDeclaration], id: id, params: params, defaults: [], body: body, rest: nil, generator: false, expression: false } end
# File lib/ruby_run_js/jsparser.rb, line 2782 def parseFunctionExpression() id = nil params = [] expectKeyword('function') if (!match('(')) token = lookahead() id = parseVariableIdentifier() if (@strict) if (isRestrictedWord(token[:value])) throwErrorTolerant(token, Messages[:StrictFunctionName]) end else if (isRestrictedWord(token[:value])) firstRestricted = token message = Messages[:StrictFunctionName] elsif (isStrictModeReservedWord(token[:value])) firstRestricted = token message = Messages[:StrictReservedWord] end end end expect('(') if (!match(')')) paramSet = {} while (@index < @length) token = lookahead() param = parseVariableIdentifier() if (@strict) if (isRestrictedWord(token[:value])) stricted = token message = Messages[:StrictParamName] end if (false) stricted = token message = Messages[:StrictParamDupe] end elsif (!firstRestricted) if (isRestrictedWord(token[:value])) firstRestricted = token message = Messages[:StrictParamName] elsif (isStrictModeReservedWord(token[:value])) firstRestricted = token message = Messages[:StrictReservedWord] elsif (false) firstRestricted = token message = Messages[:StrictParamDupe] end end params.push(param) paramSet[param[:name]] = true if (match(')')) break end expect(',') end end expect(')') previousStrict = @strict body = parseFunctionSourceElements() if (@strict && firstRestricted) throwError(firstRestricted, message) end if (@strict && stricted) throwErrorTolerant(stricted, message) end @strict = previousStrict return { type: Syntax[:FunctionExpression], id: id, params: params, defaults: [], body: body, rest: nil, generator: false, expression: false } end
13 Function Definition
# File lib/ruby_run_js/jsparser.rb, line 2636 def parseFunctionSourceElements() sourceElements = [] firstRestricted = nil expect('{') while (@index < @length) token = lookahead() if (token[:type] != Token[:StringLiteral]) break end sourceElement = parseSourceElement() sourceElements.push(sourceElement) if (sourceElement[:expression][:type] != Syntax[:Literal]) # this is not directive break end directive = sliceSource(token[:range][0] + 1, token[:range][1] - 1) if (directive == 'use strict') @strict = true if (firstRestricted) throwErrorTolerant(firstRestricted, Messages[:StrictOctalLiteral]) end else if (!firstRestricted && token[:octal]) firstRestricted = token end end end oldLabelSet = @state[:labelSet] oldInIteration = @state[:inIteration] oldInSwitch = @state[:inSwitch] oldIndefBody = @state[:indefBody] @state[:labelSet] = {} @state[:inIteration] = false @state[:inSwitch] = false @state[:indefBody] = true while (@index < @length) if (match('}')) break end sourceElement = parseSourceElement() if (sourceElement == nil) break end sourceElements.push(sourceElement) end expect('}') @state[:labelSet] = oldLabelSet @state[:inIteration] = oldInIteration @state[:inSwitch] = oldInSwitch @state[:indefBody] = oldIndefBody return { type: Syntax[:BlockStatement], body: sourceElements } end
11.1.6 The Grouping Operator
# File lib/ruby_run_js/jsparser.rb, line 1342 def parseGroupExpression() expect('(') expr = parseExpression() expect(')') return expr end
12.5 If @statement
# File lib/ruby_run_js/jsparser.rb, line 2022 def parseIfStatement() expectKeyword('if') expect('(') test = parseExpression() expect(')') consequent = parseStatement() if (matchKeyword('else')) lex() alternate = parseStatement() else alternate = nil end return { type: Syntax[:IfStatement], test: test, consequent: consequent, alternate: alternate } end
# File lib/ruby_run_js/jsparser.rb, line 1517 def parseLeftHandSideExpression() expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression() while (match('.') || match('[')) if (match('[')) expr = { type: Syntax[:MemberExpression], computed: true, object: expr, property: parseComputedMember() } else expr = { type: Syntax[:MemberExpression], computed: false, object: expr, property: parseNonComputedMember() } end end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1485 def parseLeftHandSideExpressionAllowCall() expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression() while (match('.') || match('[') || match('(')) if (match('(')) expr = { type: Syntax[:CallExpression], callee: expr, arguments: parseArguments() } elsif (match('[')) expr = { type: Syntax[:MemberExpression], computed: true, object: expr, property: parseComputedMember() } else expr = { type: Syntax[:MemberExpression], computed: false, object: expr, property: parseNonComputedMember() } end end return expr end
11.11 Binary Logical Operators
# File lib/ruby_run_js/jsparser.rb, line 1768 def parseLogicalANDExpression() expr = parseBitwiseORExpression() while (match('&&')) lex() expr = { type: Syntax[:LogicalExpression], operator: '&&', left: expr, right: parseBitwiseORExpression() } end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1784 def parseLogicalORExpression() expr = parseLogicalANDExpression() while (match('||')) lex() expr = { type: Syntax[:LogicalExpression], operator: '||', left: expr, right: parseLogicalANDExpression() } end return expr end
11.5 Multiplicative Operators
# File lib/ruby_run_js/jsparser.rb, line 1630 def parseMultiplicativeExpression() expr = parseUnaryExpression() while (match('*') || match('/') || match('%')) expr = { type: Syntax[:BinaryExpression], operator: lex()[:value], left: expr, right: parseUnaryExpression() } end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1469 def parseNewExpression() expectKeyword('new') expr = { type: Syntax[:NewExpression], callee: parseLeftHandSideExpression(), arguments: [] } if (match('(')) expr[:arguments] = parseArguments() end return expr end
# File lib/ruby_run_js/jsparser.rb, line 1452 def parseNonComputedMember() expect('.') return parseNonComputedProperty() end
# File lib/ruby_run_js/jsparser.rb, line 1439 def parseNonComputedProperty() token = lex() if (!isIdentifierName(token)) throwUnexpected(token) end return { type: Syntax[:Identifier], name: token[:value] } end
# File lib/ruby_run_js/jsparser.rb, line 1291 def parseObjectInitialiser() properties = [] map = {} expect('{') while (!match('}')) property = parseObjectProperty() if (property[:key][:type] == Syntax[:Identifier]) name = property[:key][:name] else name = (property[:key][:value]).to_s end kind = (property[:kind] == 'init') ? PropertyKind[:Data] : (property[:kind] == 'get') ? PropertyKind[:Get] : PropertyKind[:Set] if (false) if (map[name] == PropertyKind[:Data]) if (@strict && kind == PropertyKind[:Data]) throwErrorTolerant(nil, Messages[:StrictDuplicateProperty]) elsif (kind != PropertyKind[:Data]) throwErrorTolerant(nil, Messages[:AccessorDataProperty]) end else if (kind == PropertyKind[:Data]) throwErrorTolerant(nil, Messages[:AccessorDataProperty]) elsif (map[name] & kind) throwErrorTolerant(nil, Messages[:AccessorGetSet]) end end map[name] |= kind else map[name] = kind end properties.push(property) if (!match('}')) expect(',') end end expect('}') return { type: Syntax[:ObjectExpression], properties: properties } end
# File lib/ruby_run_js/jsparser.rb, line 1225 def parseObjectProperty() token = lookahead() if (token[:type] == Token[:Identifier]) id = parseObjectPropertyKey() # Property Assignment: Getter and Setter. if (token[:value] == 'get' && !match(':')) key = parseObjectPropertyKey() expect('(') expect(')') return { type: Syntax[:Property], key: key, value: parsePropertydef([]), kind: 'get' } elsif (token[:value] == 'set' && !match(':')) key = parseObjectPropertyKey() expect('(') token = lookahead() if (token[:type] != Token[:Identifier]) expect(')') throwErrorTolerant(token, Messages[:UnexpectedToken], token[:value]) return { type: Syntax[:Property], key: key, value: parsePropertydef([]), kind: 'set' } else param = [ parseVariableIdentifier() ] expect(')') return { type: Syntax[:Property], key: key, value: parsePropertydef(param, token), kind: 'set' } end else expect(':') return { type: Syntax[:Property], key: id, value: parseAssignmentExpression(), kind: 'init' } end elsif (token[:type] == Token[:EOF] || token[:type] == Token[:Punctuator]) throwUnexpected(token) else key = parseObjectPropertyKey() expect(':') return { type: Syntax[:Property], key: key, value: parseAssignmentExpression(), kind: 'init' } end end
# File lib/ruby_run_js/jsparser.rb, line 1206 def parseObjectPropertyKey() token = lex() # Note: This def is called only from parseObjectProperty(), where # EOF and Punctuator tokens are already filtered out. if (token[:type] == Token[:StringLiteral] || token[:type] == Token[:NumericLiteral]) if (@strict && token[:octal]) throwErrorTolerant(token, Messages[:StrictOctalLiteral]) end return createLiteral(token) end return { type: Syntax[:Identifier], name: token[:value] } end
11.3 Postfix Expressions
# File lib/ruby_run_js/jsparser.rb, line 1544 def parsePostfixExpression() expr = parseLeftHandSideExpressionAllowCall() token = lookahead() if (token[:type] != Token[:Punctuator]) return expr end if ((match('++') || match('--')) && !peekLineTerminator()) # 11.3.1, 11.3.2 if (@strict && expr[:type] == Syntax[:Identifier] && isRestrictedWord(expr[:name])) throwErrorTolerant(nil, Messages[:StrictLHSPostfix]) end if (!isLeftHandSide(expr)) throwErrorTolerant(nil, Messages[:InvalidLHSInAssignment]) end expr = { type: Syntax[:UpdateExpression], operator: lex()[:value], argument: expr, prefix: false } end return expr end
11.1 Primary Expressions
# File lib/ruby_run_js/jsparser.rb, line 1355 def parsePrimaryExpression() token = lookahead() type = token[:type] if (type == Token[:Identifier]) return { type: Syntax[:Identifier], name: lex()[:value] } end if (type == Token[:StringLiteral] || type == Token[:NumericLiteral]) if (@strict && token[:octal]) throwErrorTolerant(token, Messages[:StrictOctalLiteral]) end return createLiteral(lex()) end if (type == Token[:Keyword]) if (matchKeyword('this')) lex() return { type: Syntax[:ThisExpression] } end if (matchKeyword('function')) return parseFunctionExpression() end end if (type == Token[:BooleanLiteral]) lex() token[:value] = (token[:value] == 'true') return createLiteral(token) end if (type == Token[:NullLiteral]) lex() token[:value] = nil return createLiteral(token) end if (match('[')) return parseArrayInitialiser() end if (match('{')) return parseObjectInitialiser() end if (match('(')) return parseGroupExpression() end if (match('/') || match('/=')) return createLiteral(scanRegExp()) end return throwUnexpected(lex()) end
# File lib/ruby_run_js/jsparser.rb, line 2927 def parseProgram() @strict = false program = { type: Syntax[:Program], body: parseSourceElements() } return program end
11.1.5 Object Initialiser
# File lib/ruby_run_js/jsparser.rb, line 1185 def parsePropertydef(param, first = nil) previousStrict = @strict body = parseFunctionSourceElements() if (first && @strict && isRestrictedWord(param[0][:name])) throwErrorTolerant(first, Messages[:StrictParamName]) end @strict = previousStrict return { type: Syntax[:FunctionExpression], id: nil, params: param, defaults: [], body: body, rest: nil, generator: false, expression: false } end
11.8 Relational Operators
# File lib/ruby_run_js/jsparser.rb, line 1680 def parseRelationalExpression() previousAllowIn = @state[:allowIn] @state[:allowIn] = true expr = parseShiftExpression() while (match('<') || match('>') || match('<=') || match('>=') || (previousAllowIn && matchKeyword('in')) || matchKeyword('instanceof')) expr = { type: Syntax[:BinaryExpression], operator: lex()[:value], left: expr, right: parseShiftExpression() } end @state[:allowIn] = previousAllowIn return expr end
12.9 The return @statement
# File lib/ruby_run_js/jsparser.rb, line 2311 def parseReturnStatement() token = nil argument = nil expectKeyword('return') if (!@state[:indefBody]) throwErrorTolerant(nil, Messages[:IllegalReturn]) end # 'return' followed by a space and an identifier is very common. if (@source[@index] == ' ') if (isIdentifierStart(@source[@index + 1])) argument = parseExpression() consumeSemicolon() return { type: Syntax[:ReturnStatement], argument: argument } end end if (peekLineTerminator()) return { type: Syntax[:ReturnStatement], argument: nil } end if (!match(';')) token = lookahead() if (!match('}') && token[:type] != Token[:EOF]) argument = parseExpression() end end consumeSemicolon() return { type: Syntax[:ReturnStatement], argument: argument } end
11.7 Bitwise Shift Operators
# File lib/ruby_run_js/jsparser.rb, line 1664 def parseShiftExpression() expr = parseAdditiveExpression() while (match('<<') || match('>>') || match('>>>')) expr = { type: Syntax[:BinaryExpression], operator: lex()[:value], left: expr, right: parseAdditiveExpression() } end return expr end
14 Program
# File lib/ruby_run_js/jsparser.rb, line 2869 def parseSourceElement() token = lookahead() if (token[:type] == Token[:Keyword]) case (token[:value]) when 'const','let' return parseConstLetDeclaration(token[:value]) when 'function' return parseFunctionDeclaration() else return parseStatement() end end if (token[:type] != Token[:EOF]) return parseStatement() end end
# File lib/ruby_run_js/jsparser.rb, line 2888 def parseSourceElements() sourceElements = [] firstRestricted = nil while (@index < @length) token = lookahead() if (token[:type] != Token[:StringLiteral]) break end sourceElement = parseSourceElement() sourceElements.push(sourceElement) if (sourceElement[:expression][:type] != Syntax[:Literal]) # this is not directive break end directive = sliceSource(token[:range][0] + 1, token[:range][1] - 1) if (directive == 'use strict') @strict = true if (firstRestricted) throwErrorTolerant(firstRestricted, Messages[:StrictOctalLiteral]) end else if (!firstRestricted && token[:octal]) firstRestricted = token end end end while (@index < @length) sourceElement = parseSourceElement() if (sourceElement == nil) break end sourceElements.push(sourceElement) end return sourceElements end
12 Statements
# File lib/ruby_run_js/jsparser.rb, line 2554 def parseStatement() token = lookahead() if (token[:type] == Token[:EOF]) throwUnexpected(token) end if (token[:type] == Token[:Punctuator]) case token[:value] when ';' return parseEmptyStatement() when '{' return parseBlock() when '(' return parseExpressionStatement() end end if (token[:type] == Token[:Keyword]) case token[:value] when 'break' return parseBreakStatement() when 'continue' return parseContinueStatement() when 'debugger' return parseDebuggerStatement() when 'do' return parseDoWhileStatement() when 'for' return parseForStatement() when 'function' return parseFunctionDeclaration() when 'if' return parseIfStatement() when 'return' return parseReturnStatement() when 'switch' return parseSwitchStatement() when 'throw' return parseThrowStatement() when 'try' return parseTryStatement() when 'var' return parseVariableStatement() when 'while' return parseWhileStatement() when 'with' return parseWithStatement() end end expr = parseExpression() # 12.12 Labelled Statements if ((expr[:type] == Syntax[:Identifier]) && match(':')) lex() if (false) throwError(nil, Messages[:Redeclaration], 'Label', expr[:name]) end @state[:labelSet][expr[:name]] = true labeledBody = parseStatement() @state[:labelSet].delete(expr[:name]) return { type: Syntax[:LabeledStatement], label: expr, body: labeledBody } end consumeSemicolon() return { type: Syntax[:ExpressionStatement], expression: expr } end
12.1 Block
# File lib/ruby_run_js/jsparser.rb, line 1879 def parseStatementList() list = [] statement = nil while (@index < @length) if (match('}')) break end statement = parseSourceElement() unless (statement) break end list.push(statement) end return list end
12.10 The swith @statement
# File lib/ruby_run_js/jsparser.rb, line 2381 def parseSwitchCase() consequent = [] statement = nil if (matchKeyword('default')) lex() test = nil else expectKeyword('case') test = parseExpression() end expect(':') while (@index < @length) if (match('}') || matchKeyword('default') || matchKeyword('case')) break end statement = parseStatement() if (statement == nil) break end consequent.push(statement) end return { type: Syntax[:SwitchCase], test: test, consequent: consequent } end
# File lib/ruby_run_js/jsparser.rb, line 2412 def parseSwitchStatement() expectKeyword('switch') expect('(') discriminant = parseExpression() expect(')') expect('{') cases = [] if (match('}')) lex() return { type: Syntax[:SwitchStatement], discriminant: discriminant, cases: cases } end oldInSwitch = @state[:inSwitch] @state[:inSwitch] = true defaultFound = false while (@index < @length) if (match('}')) break end clause = parseSwitchCase() if (clause[:test] == nil) if (defaultFound) throwError(nil, Messages[:MultipleDefaultsInSwitch]) end defaultFound = true end cases.push(clause) end @state[:inSwitch] = oldInSwitch expect('}') return { type: Syntax[:SwitchStatement], discriminant: discriminant, cases: cases } end
12.13 The throw @statement
# File lib/ruby_run_js/jsparser.rb, line 2466 def parseThrowStatement() expectKeyword('throw') if (peekLineTerminator()) throwError(nil, Messages[:NewlineAfterThrow]) end argument = parseExpression() consumeSemicolon() return { type: Syntax[:ThrowStatement], argument: argument } end
# File lib/ruby_run_js/jsparser.rb, line 2510 def parseTryStatement() handlers = [] finalizer = nil expectKeyword('try') block = parseBlock() if (matchKeyword('catch')) handlers.push(parseCatchClause()) end if (matchKeyword('finally')) lex() finalizer = parseBlock() end if (handlers.length == 0 && !finalizer) throwError(nil, Messages[:NoCatchOrFinally]) end return { type: Syntax[:TryStatement], block: block, guardedHandlers: [], handlers: handlers, finalizer: finalizer } end
11.4 Unary Operators
# File lib/ruby_run_js/jsparser.rb, line 1574 def parseUnaryExpression() token = lookahead() if (token[:type] != Token[:Punctuator] && token[:type] != Token[:Keyword]) return parsePostfixExpression() end if (match('++') || match('--')) token = lex() expr = parseUnaryExpression() # 11.4.4, 11.4.5 if (@strict && expr[:type] == Syntax[:Identifier] && isRestrictedWord(expr[:name])) throwErrorTolerant(nil, Messages[:StrictLHSPrefix]) end if (!isLeftHandSide(expr)) throwErrorTolerant(nil, Messages[:InvalidLHSInAssignment]) end expr = { type: Syntax[:UpdateExpression], operator: token[:value], argument: expr, prefix: true } return expr end if (match('+') || match('-') || match('~') || match('!')) expr = { type: Syntax[:UnaryExpression], operator: lex()[:value], argument: parseUnaryExpression(), prefix: true } return expr end if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) expr = { type: Syntax[:UnaryExpression], operator: lex()[:value], argument: parseUnaryExpression(), prefix: true } if (@strict && expr[:operator] == 'delete' && expr[:argument][:type] == Syntax[:Identifier]) throwErrorTolerant(nil, Messages[:StrictDelete]) end return expr end return parsePostfixExpression() end
# File lib/ruby_run_js/jsparser.rb, line 1926 def parseVariableDeclaration(kind=nil) id = parseVariableIdentifier() init = nil # 12.2.1 if (@strict && isRestrictedWord(id[:name])) throwErrorTolerant(nil, Messages[:StrictVarName]) end if (kind == 'const') expect('=') init = parseAssignmentExpression() elsif (match('=')) lex() init = parseAssignmentExpression() end return { type: Syntax[:VariableDeclarator], id: id, init: init } end
# File lib/ruby_run_js/jsparser.rb, line 1950 def parseVariableDeclarationList(kind = nil) list = [] begin list.push(parseVariableDeclaration(kind)) if (!match(',')) break end lex() end while (@index < @length) return list end
12.2 Variable Statement
# File lib/ruby_run_js/jsparser.rb, line 1913 def parseVariableIdentifier() token = lex() if (token[:type] != Token[:Identifier]) throwUnexpected(token) end return { type: Syntax[:Identifier], name: token[:value] } end
# File lib/ruby_run_js/jsparser.rb, line 1964 def parseVariableStatement() expectKeyword('var') declarations = parseVariableDeclarationList() consumeSemicolon() return { type: Syntax[:VariableDeclaration], declarations: declarations, kind: 'var' } end
# File lib/ruby_run_js/jsparser.rb, line 2080 def parseWhileStatement() expectKeyword('while') expect('(') test = parseExpression() expect(')') oldInIteration = @state[:inIteration] @state[:inIteration] = true body = parseStatement() @state[:inIteration] = oldInIteration return { type: Syntax[:WhileStatement], test: test, body: body } end
12.10 The with @statement
# File lib/ruby_run_js/jsparser.rb, line 2357 def parseWithStatement() if (@strict) throwErrorTolerant(nil, Messages[:StrictModeWith]) end expectKeyword('with') expect('(') object = parseExpression() expect(')') body = parseStatement() return { type: Syntax[:WithStatement], object: object, body: body } end
Return true if there is a line terminator before the next token.
# File lib/ruby_run_js/jsparser.rb, line 1003 def peekLineTerminator() pos = @index line = @lineNumber start = @lineStart skipComment() found = @lineNumber != line @index = pos @lineNumber = line @lineStart = start return found end
# File lib/ruby_run_js/jsparser.rb, line 2960 def scanComment() comment = '' blockComment = false lineComment = false while (@index < @length) ch = @source[@index] if (lineComment) ch = curCharAndMoveNext if (isLineTerminator(ch)) loc[:end] = { line: @lineNumber, column: @index - @lineStart - 1 } lineComment = false addComment('Line', comment, start, @index - 1, loc) if (ch == "\r" && @source[@index] == "\n") @index += 1 end @lineNumber += 1 @lineStart = @index comment = '' elsif (@index >= @length) lineComment = false comment += ch loc[:end] = { line: @lineNumber, column: @length - @lineStart } addComment('Line', comment, start, @length, loc) else comment += ch end elsif (blockComment) if (isLineTerminator(ch)) if (ch == "\r" && @source[@index + 1] == "\n") @index += 1 comment += "\r\n" else comment += ch end @lineNumber += 1 @index += 1 @lineStart = @index if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end else ch = curCharAndMoveNext if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end comment += ch if (ch == '*') ch = @source[@index] if (ch == '/') comment = comment.substr(0, comment.length - 1) blockComment = false @index += 1 loc[:end] = { line: @lineNumber, column: @index - @lineStart } addComment('Block', comment, start, @index, loc) comment = '' end end end elsif (ch == '/') ch = @source[@index + 1] if (ch == '/') loc = { start: { line: @lineNumber, column: @index - @lineStart } } start = @index @index += 2 lineComment = true if (@index >= @length) loc[:end] = { line: @lineNumber, column: @index - @lineStart } lineComment = false addComment('Line', comment, start, @index, loc) end elsif (ch == '*') start = @index @index += 2 blockComment = true loc = { start: { line: @lineNumber, column: @index - @lineStart - 2 } } if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end else break end elsif (isWhiteSpace(ch)) @index += 1 elsif (isLineTerminator(ch)) @index += 1 if (ch == "\r" && @source[@index] == "\n") @index += 1 end @lineNumber += 1 @lineStart = @index else break end end end
# File lib/ruby_run_js/jsparser.rb, line 313 def scanHexEscape(prefix) code = 0 len = (prefix == 'u') ? 4 : 2 len.times do |i| if (@index < @length && isHexDigit(@source[@index])) ch = curCharAndMoveNext code = code * 16 + '0123456789abcdef'.index(ch.downcase()) else return nil end end return [code].pack('U*') end
# File lib/ruby_run_js/jsparser.rb, line 328 def scanIdentifier() ch = @source[@index] if (!isIdentifierStart(ch)) return end start = @index if (ch == "\\") @index += 1 if (@source[@index] != 'u') return end @index += 1 restore = @index ch = scanHexEscape('u') if (ch) if (ch == "\\" || !isIdentifierStart(ch)) return end id = ch else @index = restore id = 'u' end else id = curCharAndMoveNext end while (@index < @length) do ch = @source[@index] if (!isIdentifierPart(ch)) break end if (ch == "\\") @index += 1 if (@source[@index] != 'u') return end @index += 1 restore = @index ch = scanHexEscape('u') if (ch) if (ch == "\\" || !isIdentifierPart(ch)) return end id += ch else @index = restore id += 'u' end else id += curCharAndMoveNext end end # There is no keyword or literal with only one character. # Thus, it must be an identifier. if (id.length == 1) return { type: Token[:Identifier], value: id, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (isKeyword(id)) return { type: Token[:Keyword], value: id, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # 7.8.1 Null Literals if (id == 'null') return { type: Token[:NullLiteral], value: id, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # 7.8.2 Boolean Literals if (id == 'true' || id == 'false') return { type: Token[:BooleanLiteral], value: id, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end return { type: Token[:Identifier], value: id, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end
7.8.3 Numeric Literals
# File lib/ruby_run_js/jsparser.rb, line 605 def scanNumericLiteral() ch = @source[@index] assert(isDecimalDigit(ch) || (ch == '.'), 'Numeric literal must start with a decimal digit or a decimal point') start = @index number = '' if (ch != '.') number = curCharAndMoveNext ch = @source[@index] # Hex number starts with '0x'. # Octal number starts with '0'. if (number == '0') if (ch == 'x' || ch == 'X') number += curCharAndMoveNext while (@index < @length) ch = @source[@index] if (!isHexDigit(ch)) break end number += curCharAndMoveNext end if (number.length <= 2) # only 0x throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end if (@index < @length) ch = @source[@index] if (isIdentifierStart(ch)) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end end return { type: Token[:NumericLiteral], value: number.to_i(16), lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } elsif (isOctalDigit(ch)) number += curCharAndMoveNext while (@index < @length) ch = @source[@index] if (!isOctalDigit(ch)) break end number += curCharAndMoveNext end if (@index < @length) ch = @source[@index] if (isIdentifierStart(ch) || isDecimalDigit(ch)) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end end return { type: Token[:NumericLiteral], value: number.to_i(8), octal: true, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # decimal number starts with '0' such as '09' is illegal. if (isDecimalDigit(ch)) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end end while (@index < @length) ch = @source[@index] if (!isDecimalDigit(ch)) break end number += curCharAndMoveNext end end if (ch == '.') number += curCharAndMoveNext while (@index < @length) ch = @source[@index] if (!isDecimalDigit(ch)) break end number += curCharAndMoveNext end end if (ch == 'e' || ch == 'E') number += curCharAndMoveNext ch = @source[@index] if (ch == '+' || ch == '-') number += curCharAndMoveNext end ch = @source[@index] if (isDecimalDigit(ch)) number += curCharAndMoveNext while (@index < @length) ch = @source[@index] if (!isDecimalDigit(ch)) break end number += curCharAndMoveNext end else throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end end if (@index < @length) ch = @source[@index] if (isIdentifierStart(ch)) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end end return { type: Token[:NumericLiteral], value: number.to_f, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end
7.7 Punctuators
# File lib/ruby_run_js/jsparser.rb, line 442 def scanPunctuator() start = @index ch1 = @source[@index] # Check for most common single-character punctuators. if (ch1 == ';' || ch1 == '{' || ch1 == '}') @index += 1 return { type: Token[:Punctuator], value: ch1, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (ch1 == ',' || ch1 == '(' || ch1 == ')') @index += 1 return { type: Token[:Punctuator], value: ch1, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # Dot (.) can also start a floating-point number, hence the need # to check the next character. ch2 = @source[@index + 1] if (ch1 == '.' && !isDecimalDigit(ch2)) return { type: Token[:Punctuator], value: curCharAndMoveNext, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # Peek more characters. ch3 = @source[@index + 2] ch4 = @source[@index + 3] # 4-character punctuator: >>>= if (ch1 == '>' && ch2 == '>' && ch3 == '>') if (ch4 == '=') @index += 4 return { type: Token[:Punctuator], value: '>>>=', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end end # 3-character punctuators: == != >>> <<= >>= if (ch1 == '=' && ch2 == '=' && ch3 == '=') @index += 3 return { type: Token[:Punctuator], value: '===', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (ch1 == '!' && ch2 == '=' && ch3 == '=') @index += 3 return { type: Token[:Punctuator], value: '!==', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (ch1 == '>' && ch2 == '>' && ch3 == '>') @index += 3 return { type: Token[:Punctuator], value: '>>>', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (ch1 == '<' && ch2 == '<' && ch3 == '=') @index += 3 return { type: Token[:Punctuator], value: '<<=', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end if (ch1 == '>' && ch2 == '>' && ch3 == '=') @index += 3 return { type: Token[:Punctuator], value: '>>=', lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end # 2-character punctuators: <= >= == != ++ -- << >> && || # += -= *= %= &= |= ^= /= if (ch2 == '=') if ('<>=!+-*%&|^/'.include?(ch1)) @index += 2 return { type: Token[:Punctuator], value: ch1 + ch2, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end end if (ch1 == ch2 && ('+-<>&|'.include?(ch1))) if ('+-<>&|'.include?(ch2)) @index += 2 return { type: Token[:Punctuator], value: ch1 + ch2, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end end # The remaining 1-character punctuators. if ('[]<>+-*%&|^!~?:=/'.include?(ch1)) return { type: Token[:Punctuator], value: curCharAndMoveNext, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end end
# File lib/ruby_run_js/jsparser.rb, line 836 def scanRegExp() classMarker = false terminated = false @buffer = nil skipComment() start = @index ch = @source[@index] assert(ch == '/', 'Regular expression literal must start with a slash') str = curCharAndMoveNext while (@index < @length) ch = curCharAndMoveNext str += ch if (ch == "\\") ch = curCharAndMoveNext # ECMA-262 7.8.5 if (isLineTerminator(ch)) throwError(nil, Messages[:UnterminatedRegExp]) end str += ch elsif (classMarker) if (ch == ']') classMarker = false end else if (ch == '/') terminated = true break elsif (ch == '[') classMarker = true elsif (isLineTerminator(ch)) throwError(nil, Messages[:UnterminatedRegExp]) end end end if (!terminated) throwError(nil, Messages[:UnterminatedRegExp]) end # Exclude leading and trailing slash. pattern = str[1, str.length - 2] flags = '' while (@index < @length) ch = @source[@index] if (!isIdentifierPart(ch)) break end @index += 1 if (ch == "\\" && @index < @length) ch = @source[@index] if (ch == 'u') @index += 1 restore = @index ch = scanHexEscape('u') if (ch) flags += ch str += "\\u" while (restore < @index) str += @source[restore] restore += 1 end else @index = restore flags += 'u' str += "\\u" end else str += "\\" end else flags += ch str += ch end end return { literal: str, value: sliceSource(start,@index), regexp: { pattern: str, flags: flags }, range: [start, @index] } end
7.8.4 String Literals
# File lib/ruby_run_js/jsparser.rb, line 741 def scanStringLiteral() str = '' octal = false quote = @source[@index] assert((quote == "'" || quote == '"'), 'String literal must starts with a quote') start = @index @index += 1 while (@index < @length) ch = curCharAndMoveNext if (ch == quote) quote = '' break elsif (ch == "\\") ch = curCharAndMoveNext if (!isLineTerminator(ch)) case ch when 'n' str += "\n" when 'r' str += "\r" when 't' str += "\t" when 'u','x' restore = @index unescaped = scanHexEscape(ch) if (unescaped) str += unescaped else @index = restore str += ch end when 'b' str += "\b" when 'f' str += "\f" when 'v' str += "\x0B" else if (isOctalDigit(ch)) code = '01234567'.index(ch) # \0 is not octal escape sequence if (code != 0) octal = true end if (@index < @length && isOctalDigit(@source[@index])) octal = true code = code * 8 + '01234567'.index(curCharAndMoveNext) # 3 digits are only allowed when string starts # with 0, 1, 2, 3 if ('0123'.include?(ch) && @index < @length && isOctalDigit(@source[@index])) then code = code * 8 + '01234567'.index(curCharAndMoveNext) end end str += [code].pack('U*') else str += ch || '' end end else @lineNumber += 1 if (ch == "\r" && @source[@index] == "\n") @index += 1 end end elsif (isLineTerminator(ch)) break else str += ch end end if (quote != '') throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end return { type: Token[:StringLiteral], value: str, octal: octal, lineNumber: @lineNumber, lineStart: @lineStart, range: [start, @index] } end
7.4 Comments
# File lib/ruby_run_js/jsparser.rb, line 242 def skipComment() blockComment = false lineComment = false while (@index < @length) do ch = @source[@index] if (lineComment) ch = curCharAndMoveNext if (isLineTerminator(ch)) lineComment = false if (ch == "\r" && @source[@index] == "\n") @index += 1 end @lineNumber += 1 @lineStart = @index end elsif (blockComment) if (isLineTerminator(ch)) if (ch == "\r" && @source[@index + 1] == "\n") @index += 1 end @lineNumber += 1 @index += 1 @lineStart = @index if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end else ch = curCharAndMoveNext if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end if (ch == '*') ch = @source[@index] if (ch == '/') @index += 1 blockComment = false end end end elsif (ch == '/') ch = @source[@index + 1] if (ch == '/') @index += 2 lineComment = true elsif (ch == '*') @index += 2 blockComment = true if (@index >= @length) throwError(nil, Messages[:UnexpectedToken], 'ILLEGAL') end else break end elsif (isWhiteSpace(ch)) @index += 1 elsif (isLineTerminator(ch)) @index += 1 if (ch == "\r" && @source[@index] == "\n") @index += 1 end @lineNumber += 1 @lineStart = @index else break end end end
# File lib/ruby_run_js/jsparser.rb, line 130 def sliceSource(from, to) return @source[from...to] end
Throw an exception
# File lib/ruby_run_js/jsparser.rb, line 1017 def throwError(token, messageFormat,*args) msg = messageFormat.gsub(/%(\d)/) do |i| args[i.to_i] || '' end if (token != nil) # raise "Line #{token[:lineNumber]} at #{(token[:range][0] - token[:lineNumber] + 1)} : #{msg}" raise SyntaxError.new "Error: Line #{token[:lineNumber]}: #{msg}" else # raise "Line #{@lineNumber} at #{@index - @lineStart + 1} : #{msg}" raise SyntaxError.new "Error: Line #{@lineNumber}: #{msg}" end end
# File lib/ruby_run_js/jsparser.rb, line 1031 def throwErrorTolerant(token, messageFormat,*args) throwError(token,messageFormat,*args) end
Throw an exception because of the token.
# File lib/ruby_run_js/jsparser.rb, line 1037 def throwUnexpected(token) if (token[:type] == Token[:EOF]) throwError(token, Messages[:UnexpectedEOS]) end if (token[:type] == Token[:NumericLiteral]) throwError(token, Messages[:UnexpectedNumber]) end if (token[:type] == Token[:StringLiteral]) throwError(token, Messages[:UnexpectedString]) end if (token[:type] == Token[:Identifier]) throwError(token, Messages[:UnexpectedIdentifier]) end if (token[:type] == Token[:Keyword]) if (isFutureReservedWord(token[:value])) throwError(token, Messages[:UnexpectedReserved]) elsif (@strict && isStrictModeReservedWord(token[:value])) throwErrorTolerant(token, Messages[:StrictReservedWord]) return end throwError(token, Messages[:UnexpectedToken], token[:value]) end # BooleanLiteral, NullLiteral, or Punctuator. throwError(token, Messages[:UnexpectedToken], token[:value]) end
# File lib/ruby_run_js/jsparser.rb, line 3264 def trackGroupExpression() skipComment() marker = createLocationMarker() expect('(') expr = parseExpression() expect(')') marker[:end].call() marker[:applyGroup].call(expr) return expr end
# File lib/ruby_run_js/jsparser.rb, line 3280 def trackLeftHandSideExpression() skipComment() marker = createLocationMarker() expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression() while (match('.') || match('[')) if (match('[')) expr = { type: Syntax[:MemberExpression], computed: true, object: expr, property: parseComputedMember() } marker[:end].call() marker[:apply].call(expr) else expr = { type: Syntax[:MemberExpression], computed: false, object: expr, property: parseNonComputedMember() } marker[:end].call() marker[:apply].call(expr) end end return expr end
# File lib/ruby_run_js/jsparser.rb, line 3312 def trackLeftHandSideExpressionAllowCall() skipComment() marker = createLocationMarker() expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression() while (match('.') || match('[') || match('(')) if (match('(')) expr = { type: Syntax[:CallExpression], callee: expr, arguments: parseArguments() } marker[:end].call() marker[:apply].call(expr) elsif (match('[')) expr = { type: Syntax[:MemberExpression], computed: true, object: expr, property: parseComputedMember() } marker[:end].call() marker[:apply].call(expr) else expr = { type: Syntax[:MemberExpression], computed: false, object: expr, property: parseNonComputedMember() } marker[:end].call() marker[:apply].call(expr) end end return expr end