/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ "use strict"; class Lexer { constructor(origin, originKind, lineNumberOffset, text) { if (!isOriginKind(originKind)) throw new Error("Bad origin kind: " + originKind); this._origin = origin; this._originKind = originKind; this._lineNumberOffset = lineNumberOffset; this._text = text; this._index = 0; this._stack = []; } get lineNumber() { return this.lineNumberForIndex(this._index); } get origin() { return this._origin; } get originString() { return this._origin + ":" + (this.lineNumber + 1); } get originKind() { return this._originKind; } lineNumberForIndex(index) { let matches = this._text.substring(0, index).match(/\n/g); return (matches ? matches.length : 0) + this._lineNumberOffset; } get state() { return {index: this._index, stack: this._stack.concat()}; } set state(value) { this._index = value.index; this._stack = value.stack; } static _textIsIdentifierImpl(text) { return /^[^\d\W]\w*/.test(text); } static textIsIdentifier(text) { return Lexer._textIsIdentifierImpl(text) && !RegExp.rightContext.length; } next() { if (this._stack.length) return this._stack.pop(); if (this._index >= this._text.length) return null; const isCCommentBegin = /\/\*/; const isCPlusPlusCommentBegin = /\/\//; let result = (kind) => { let text = RegExp.lastMatch; let token = new LexerToken(this, this._index, kind, text); this._index += text.length; return token; }; let relevantText; for (;;) { relevantText = this._text.substring(this._index); if (/^\s+/.test(relevantText)) { this._index += RegExp.lastMatch.length; continue; } if (/^\/\*/.test(relevantText)) { let endIndex = relevantText.search(/\*\//); if (endIndex < 0) this.fail("Unterminated comment"); this._index += endIndex; continue; } if (/^\/\/.*/.test(relevantText)) { this._index += RegExp.lastMatch.length; continue; } break; } if (this._index >= this._text.length) return null; // FIXME: Make this handle exp-form literals like 1e1. if (/^(([0-9]*\.[0-9]+[fd]?)|([0-9]+\.[0-9]*[fd]?))/.test(relevantText)) return result("floatLiteral"); // FIXME: Make this do Unicode. if (Lexer._textIsIdentifierImpl(relevantText)) { if (/^(struct|protocol|typedef|if|else|enum|continue|break|switch|case|default|for|while|do|return|constant|device|threadgroup|thread|operator|null|true|false)$/.test(RegExp.lastMatch)) return result("keyword"); if (this._originKind == "native" && /^(native|restricted)$/.test(RegExp.lastMatch)) return result("keyword"); return result("identifier"); } if (/^0x[0-9a-fA-F]+u/.test(relevantText)) return result("uintHexLiteral"); if (/^0x[0-9a-fA-F]+/.test(relevantText)) return result("intHexLiteral"); if (/^[0-9]+u/.test(relevantText)) return result("uintLiteral"); if (/^[0-9]+/.test(relevantText)) return result("intLiteral"); if (/^<<|>>|->|>=|<=|==|!=|\+=|-=|\*=|\/=|%=|\^=|\|=|&=|\+\+|--|&&|\|\||([{}()\[\]?:=+*\/,.%!~^&|<>@;-])/.test(relevantText)) return result("punctuation"); let remaining = relevantText.substring(0, 20).split(/\s/)[0]; if (!remaining.length) remaining = relevantText.substring(0, 20); this.fail("Unrecognized token beginning with: " + remaining); } push(token) { this._stack.push(token); } peek() { let result = this.next(); this.push(result); return result; } fail(error) { throw new WSyntaxError(this.originString, error); } backtrackingScope(callback) { let state = this.state; try { return callback(); } catch (e) { if (e instanceof WSyntaxError) { this.state = state; return null; } throw e; } } testScope(callback) { let state = this.state; try { callback(); return true; } catch (e) { if (e instanceof WSyntaxError) return false; throw e; } finally { this.state = state; } } }