spellu-core
Version:
Spellu is a parser combinator engine.
660 lines • 22.5 kB
JavaScript
var spellu;
(function (spellu) {
class SourceStream {
constructor(input, option = {}) {
this._input = option.normalize ? this.normalizeSource(input) : input;
this.inputLength = option.inputLength || this.input.length;
this.inputOffset = option.inputOffset || 0;
this.lineNo = option.startingLineNo || 1;
this.columnNo = option.startingColumnNo || 1;
}
get input() {
return this._input;
}
normalizeSource(string) {
string = this.stripUtf8Bom(string);
string = this.normalizeNewLine(string);
string = this.addTrailNewLine(string);
return string;
}
stripUtf8Bom(string) {
return string.replace(/^\uFEFF/, '');
}
normalizeNewLine(string) {
return string.replace(/\r\n|\r/g, '\n');
}
addTrailNewLine(string) {
if (string[string.length - 1] != '\n')
string += '\n';
return string;
}
location() {
return { lineNo: this.lineNo, columnNo: this.columnNo };
}
charCodeAt(offset = 0) {
return this.input.charCodeAt(this.inputOffset + offset);
}
substring(length) {
return this.input.substring(this.inputOffset, this.inputOffset + length);
}
// TODO 行末まで取得メソッド追加
nowEol() {
return this.input[this.inputOffset] == '\n';
}
nowEos() {
return this.inputLength <= this.inputOffset;
}
startsWith(pattern) {
return this.input.substring(this.inputOffset, this.inputOffset + pattern.length) === pattern;
}
test(pattern) {
return this.regexp(pattern).test(this.input);
}
skip(pattern) {
this.consume(pattern);
}
consume(pattern) {
return this.read(pattern, result => true, false);
}
scan(type, pattern, matchIndex = -1) {
return this.read(pattern, result => {
return this.token(type, this.location(), result[0], matchIndex >= 0 ? result[matchIndex] : undefined);
}, null);
}
scanBlock(type, openString, closeString, option = {}) {
const allowRecursive = option.allowRecursive || false;
const allowMultiline = option.allowMultiline || false;
const innerPattern = option.innerPattern || (allowMultiline ? '[^]*?' /* == '[\\s\\S]*' */ : '[^\\n]*?');
const blockLength = allowRecursive
? this.matchBlockRecursive(openString, closeString, innerPattern)
: this.matchBlock(openString, closeString, innerPattern);
if (blockLength === 0)
return null;
const block = this.substring(blockLength);
const token = this.token(type, this.location(), block, block.substring(openString.length, block.length - closeString.length));
this.updateLocation(block);
return token;
}
read(pattern, converter, defaultValue) {
return this.match(pattern, converter, defaultValue, { locationUpdate: true });
}
// matchString<T>(pattern : string, converter : (result : string) => T, defaultValue : T, option : {locationUpdate? : boolean} = {}) : T {
// }
match(pattern, converter, defaultValue, option = {}) {
const regexp = this.regexp(pattern);
const result = regexp.exec(this.input);
if (result === null)
return defaultValue;
let value = converter(result);
if (option.locationUpdate)
this.updateLocation(result[0]);
//this.updateLocation(result[0], regexp.lastIndex)
return value;
}
// protected regexp(string : string) : RegExp
// protected regexp(pattern : RegExp) : RegExp
regexp(stringOrPattern) {
let pattern;
let flags = '';
if (stringOrPattern instanceof RegExp) {
if (stringOrPattern.global)
flags += 'g';
if (stringOrPattern.ignoreCase)
flags += 'i';
if (stringOrPattern.multiline)
flags += 'm';
pattern = stringOrPattern.source;
}
else {
pattern = escape(stringOrPattern);
}
flags += 'y';
const regexp = new RegExp(pattern, flags);
regexp.lastIndex = this.inputOffset;
return regexp;
function escape(string) {
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
;
}
matchBlock(openString, closeString, innerPattern) {
let length = 0;
const inputOffset = this.inputOffset;
{
if (!this.startsWith(openString))
return 0;
length += openString.length;
// read inner text
this.inputOffset = inputOffset + length;
const innerLength = this.match(new RegExp(`${innerPattern}(?=${this.escapeForReadAhead(closeString)})`), result => result[0].length, 0);
if (innerLength === 0)
throw new Error('Cannot find close');
else
length += innerLength;
// close pattern
length += closeString.length;
}
this.inputOffset = inputOffset;
return length;
}
matchBlockRecursive(openString, closeString, innerPattern) {
let length = 0;
const inputOffset = this.inputOffset;
{
if (!this.startsWith(openString))
return 0;
length += openString.length;
while (true) {
// read inner text
this.inputOffset = inputOffset + length;
const innerLength = this.match(new RegExp(`${innerPattern}(?=${this.escapeForReadAhead(openString)}|${this.escapeForReadAhead(closeString)})`), result => result[0].length, 0);
if (innerLength === 0)
throw new Error('Cannot find close');
else
length += innerLength;
// match by close pattern
this.inputOffset = inputOffset + length;
if (this.startsWith(closeString))
break;
// match by open pattern
length += this.matchBlockRecursive(openString, closeString, innerPattern);
}
length += closeString.length;
}
this.inputOffset = inputOffset;
return length;
}
escapeForReadAhead(string) {
// FIXME 不十分
return string.replace(/\(|\)|\|/, match => '\\' + match);
}
// protected search(string : string) : number {
// return this.input.indexOf(string, this.inputOffset)
// }
token(type, location, string = '', value) {
location = location || this.location();
const token = { type, lineNo: location.lineNo, columnNo: location.columnNo, string };
if (value !== undefined)
token.value = value;
return token;
}
updateLocation(string) {
for (let index = 0; index < string.length; ++index) {
this.inputOffset += 1;
if (string[index] == '\n') {
this.lineNo += 1;
this.columnNo = 1;
}
else {
this.columnNo += 1;
}
}
}
}
spellu.SourceStream = SourceStream;
})(spellu || (spellu = {}));
var spellu;
(function (spellu) {
class Tokenizer {
constructor(source) {
if (source instanceof spellu.SourceStream) {
this.source = source;
}
else {
this.source = new spellu.SourceStream(source.string, {
startingLineNo: source.lineNo,
startingColumnNo: source.columnNo
});
}
}
available() {
return !this.source.nowEos();
}
setToken(token) {
this.token = token;
}
chain() {
return new Chain(this);
}
skip(pattern) {
return this.chain().skip(pattern);
}
consume(pattern) {
return this.chain().consume(pattern);
}
scan(type, pattern, matchIndex = -1, innerTokenizer) {
return this.chain().scan(type, pattern, matchIndex, innerTokenizer);
}
scanBlock(type, openPattern, closePattern, option = {}, innerTokenizer) {
return this.chain().scanBlock(type, openPattern, closePattern, option, innerTokenizer);
}
expect(message) {
const location = this.source.location();
this.error('EXPECTED', message + '\n' + `(${location.lineNo}:${location.columnNo}) ` + this.source.input);
}
error(code, message) {
//var err = error(code, message, {line: this.lineno, column: this.colno, filename: this.filename, src: this.originalInput});
const error = new Error(message);
throw error;
}
fail() {
const location = this.source.location();
const source = this.source.substring(10);
this.error('UNEXPECTED_TEXT', `(${location.lineNo}:${location.columnNo}) unexpected text ... '${source}'`);
}
fatal(message) {
this.error('FATAL', message);
}
result() {
return this.token;
}
}
spellu.Tokenizer = Tokenizer;
class Chain {
constructor(tokenizer) {
this.tokenizer = tokenizer;
this.source = tokenizer.source;
this.result = undefined;
}
call(closure) {
closure(this);
return this;
}
skip(pattern) {
this.source.skip(pattern);
return this;
}
consume(pattern) {
if (this.ok())
return this;
this.result = this.source.consume(pattern);
return this;
}
scan(type, pattern, matchIndex = -1, innerTokenizer) {
if (this.ok())
return this;
const token = this.source.scan(type, pattern, matchIndex);
if (token === null) {
this.result = false;
return this;
}
if (innerTokenizer !== undefined && typeof token.value === 'string') {
token.value = innerTokenizer(new spellu.SourceStream(token.value, {
startingLineNo: token.lineNo,
startingColumnNo: token.columnNo
}));
}
this.tokenizer.setToken(token);
this.result = true;
return this;
}
scanBlock(type, openPattern, closePattern, option = {}, innerTokenizer) {
if (this.ok())
return this;
const token = this.source.scanBlock(type, openPattern, closePattern, option);
if (token === null) {
this.result = false;
return this;
}
if (innerTokenizer !== undefined && typeof token.value === 'string') {
token.value = innerTokenizer(new spellu.SourceStream(token.value, {
startingLineNo: token.lineNo,
startingColumnNo: token.columnNo
}));
}
this.tokenizer.setToken(token);
this.result = true;
return this;
}
expect(message) {
if (this.ng())
this.tokenizer.expect(message);
return this;
}
error() {
}
fail(message) {
if (!this.ok())
this.tokenizer.error('Fail', message || 'bug???');
}
fatal(message) {
if (!this.ok())
this.tokenizer.error('Fatal', message || 'bug');
}
ok() {
return this.result === true;
}
ng() {
return this.result === false;
}
}
spellu.Chain = Chain;
})(spellu || (spellu = {}));
var spellu;
(function (spellu) {
class Success {
constructor(_, value) {
this._ = _;
this.value = value;
}
isSuccess() {
return true;
}
isFailure() {
return false;
}
success(callback) {
callback(this.extract());
return this;
}
failure(callback) {
return this;
}
extract() {
return this.value;
}
resolve(left, right) {
return right ? right(this.value) : null;
}
}
spellu.Success = Success;
class Failure {
constructor(_, diagnostics = []) {
this._ = _;
this.diagnostics = diagnostics;
}
isSuccess() {
return false;
}
isFailure() {
return true;
}
success(callback) {
return this;
}
failure(callback) {
callback(this.extract());
return this;
}
extract() {
return this.diagnostics;
}
resolve(left, right) {
return left ? left(this.diagnostics) : null;
}
}
spellu.Failure = Failure;
function createParser(evaluator) {
const parser = evaluator;
parser.many0 = () => {
return many(parser, 0);
};
parser.many1 = () => {
return many(parser, 1);
};
parser.optional = () => {
return option(parser);
};
parser.or = (_parser) => {
return or(parser, _parser);
};
parser.times = (count) => {
return many(parser, count, count);
};
parser.timesMany = (min) => {
return many(parser, min);
};
parser.timesBetween = (min, max) => {
return many(parser, min, max);
};
parser.expect = expect;
parser.reduce = reduce;
parser.inspect = inspect;
return parser;
function expect(callback, message) {
return createParser(_ => {
const result = parser(_);
return result instanceof Success && callback(result.value)
? result
: failure(_, { message });
});
}
function reduce(callback) {
return createParser(_ => {
const result = parser(_);
return result instanceof Success
? success(_, callback(result.value))
: result;
});
}
function inspect(callback) {
return createParser(_ => {
const result = parser(_);
callback(result);
return result;
});
}
}
spellu.createParser = createParser;
function success(_, value) {
return new Success(_, value);
}
spellu.success = success;
function failure(_, diagnostic) {
const diagnostics = Array.isArray(diagnostic) ? diagnostic : [diagnostic];
for (const diagnostic of diagnostics) {
const { lineNo, columnNo } = _.location();
diagnostic.lineNo = lineNo;
diagnostic.columnNo = columnNo;
}
return new Failure(_, diagnostics);
}
spellu.failure = failure;
function and(...parsers) {
return createParser(_ => {
let values = [];
for (const parser of parsers) {
const result = parser(_);
if (result instanceof Failure)
return result;
values.push(result.value);
}
return success(_, values);
});
}
spellu.and = and;
function or(...parsers) {
return createParser(_ => {
let result;
for (const parser of parsers) {
result = parser(_);
if (result instanceof Success)
return result;
}
return result;
});
}
spellu.or = or;
function option(parser) {
return createParser(_ => {
const result = parser(_);
return (result instanceof Success) ? result : success(_, null);
});
}
spellu.option = option;
function many(parser, min, max = Number.MAX_VALUE) {
return createParser(_ => {
let values = [];
for (let i = 0; i <= max; ++i) {
const result = parser(_);
if (result instanceof Failure)
break;
values.push(result.value);
}
// check repeat times
if (values.length < min || values.length > max) {
return failure(_, { message: `many(${min}, ${max}) failed.` });
}
return success(_, values);
});
}
spellu.many = many;
function $$(...parsers) {
const parser = $expr();
// expect(parsers.length === 0)
return parser;
function $expr() {
return $or();
}
function $or() {
let args = [];
args.push($and());
while (parsers.length > 0 && parsers[0] === '|') {
parsers.shift();
args.push($and());
}
return or(...args);
}
function $and() {
let args = [];
while (parsers.length > 0 && parsers[0] instanceof Function) {
args.push(parsers[0]);
parsers.shift();
}
return and(...args);
}
}
spellu.$$ = $$;
function $0(parser, ...parsers) {
if (parsers.length === 0) {
return many(parser, 0);
}
else {
return many($$(parser, ...parsers), 0);
}
}
spellu.$0 = $0;
function $1(parser, ...parsers) {
if (parsers.length === 0) {
return many(parser, 1);
}
else {
return many($$(parser, ...parsers), 1);
}
}
spellu.$1 = $1;
function $_(parser, ...parsers) {
if (parsers.length === 0) {
return option(parser);
}
else {
return option($$(parser, ...parsers));
}
}
spellu.$_ = $_;
function delay(combinator, ...args) {
let parser;
return createParser(_ => {
if (parser === undefined)
parser = combinator(...args);
return parser(_);
});
}
spellu.delay = delay;
function always(value) {
return createParser(_ => {
return success(_, value);
});
}
spellu.always = always;
function never(message) {
message = message || 'Failed.';
return createParser(_ => {
return failure(_, { message });
});
}
spellu.never = never;
function parse(parser, input) {
return parser(new spellu.SourceStream(input));
}
spellu.parse = parse;
})(spellu || (spellu = {}));
var spellu;
(function (spellu) {
function p(def) {
return spellu.createParser(source => {
try {
const result = def(source);
return spellu.success(source, result);
}
catch (e) {
return spellu.failure(source, e);
}
});
}
spellu.p = p;
spellu.string = {
eos, string: _string, regex
};
function eos() {
return spellu.createParser(source => {
return source.nowEos() ? spellu.success(source, 'EOS') : spellu.failure(source, { message: 'EOS expected.' });
});
}
function _string(string) {
return spellu.createParser(source => {
if (source.consume(new RegExp(string))) {
return spellu.success(source, string);
}
else {
return spellu.failure(source, { message: 'string not match' });
}
});
}
function regex(pattern) {
return p(source => {
const token = source.scan('dummy', pattern);
if (token === null)
throw [{ message: 'regex not match.' }];
return token.string;
});
}
})(spellu || (spellu = {}));
var spellu;
(function (spellu) {
spellu.token = {
eos, string, regex
};
function string(type, string) {
string = string || type;
return spellu.createParser(input => {
const token = input.scan(type, string, 0);
return token ? spellu.success(input, token) : spellu.failure(input, { message: 'string not match' });
});
}
function regex(type, pattern, matchIndex) {
return spellu.createParser(input => {
const token = input.scan(type, pattern, matchIndex);
return token ? spellu.success(input, token) : spellu.failure(input, { message: 'regex not match' });
});
}
// function _token(type : string, message? : string) : spellu.Parser<spellu.Token> {
// return spellu.createParser(input => {
// const token = input.nextTokenIf(type)
// if (token) {
// return success(input, token)
// }
// else {
// message = message || `[${type}] expected.`
// return failure(input, {message})
// }
// })
// }
function eos(type) {
return spellu.createParser((input) => {
return input.nowEos() ? spellu.success(input, input.token(type)) : spellu.failure(input, { message: 'EOS expected.' });
});
}
})(spellu || (spellu = {}));
module.exports = spellu;
//# sourceMappingURL=spellu-core.js.map