201 lines
6.2 KiB
JavaScript
201 lines
6.2 KiB
JavaScript
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|