UNPKG

spellu-core

Version:

Spellu is a parser combinator engine.

660 lines 22.5 kB
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