UNPKG

@typedefs/parser

Version:
216 lines (202 loc) 5.54 kB
const { Fn, fn, nullable, nonNullable, optional, any, } = require('./tokens'); /** * Splits the string into tokens. * @param {string} s */ const lex = (s) => { const res = s.split(/([!?=*(),:.<>{}|\s+])/g) return res .filter(a => /\S/.test(a)) .map((a) => { switch (a) { case 'function': return fn case 'Function': return Fn case '!': return nonNullable case '?': return nullable case '=': return optional case '*': return any } return a }) } function parser(tokens) { let c = 0 const peek = () => tokens[c] const peekNext = (n = 1) => tokens[c + n] const consume = () => tokens[c++] const parseFunction = () => { /** @type {_typedefsParser.FunctionType} */ const meta = { return: null, args: [] } if (peek() != '(') throw new Error('Expecting opening (') consume() let foundArgs while(peek() != ')') { if (foundArgs && peek() == 'this') throw new Error('this must come first in function arguments') if (foundArgs && peek() == 'new') throw new Error('new must come first in function arguments') if (peek() == 'this') { consume() if (peek() != ':') throw new Error('Expecting :') consume() meta.this = parseType() } else if (peek() == 'new') { consume() if (peek() != ':') throw new Error('Expecting :') consume() meta.new = parseType() } else if (peek() == '.' && peekNext() == '.' && peekNext(2) == '.') { consume() consume() consume() const type = parseType() if (peek() != ')') throw new Error('Variable args must come last') meta.variableArgs = type } else { const arg = parseType() meta.args.push(arg) if (peek() == '=') { arg.optional = true consume() } } foundArgs = true if (peek() == ')') { break } if (peek() == ',') consume() else throw new Error('Expecting , between arguments') } consume() if (peek() == ':') { consume() const ret = parseType() if (ret.name == undefined && ret.nullable) // special case ret.name = '' meta.return = ret } return meta } const parseApplication = () => { const apps = [] while(peek() != '>') { const application = parseType() apps.push(application) if (peek() == '>') { break } if (peek() == ',') consume() else throw new Error('Expecting , between applications') } consume() return apps } const parseRecord = () => { const props = {} while(peek() != '}') { const propName = peek() consume() props[propName] = null if (peek() == ':') { consume() try { const type = parseType() props[propName] = type } catch (err) { err.message += `(when parsing ${propName} property)` throw err } } if(peek() == '}') { consume() break } if (peek() != ',') { throw new Error(`Expecting , for record after ${propName}`) } consume() } return props } const parseType = (specialAllowed = true, union = []) => { /** @type {!_typedefsParser.Type} */ let type = {} let applicationWithDot /** @type {string} */ let token = peek() if ([nullable, nonNullable].includes(token)) { // can repeat for all specials but don't if (!specialAllowed) throw new Error(`${token} not allowed after .`) type.nullable = token === nullable consume() } token = peek() if (token == '(') { // union, consider cases without () consume() type = { ...parseType(true, []), ...type, // preserve nullable } if (peek() != ')') throw new Error('Expecting closing )') consume() if (peek() != '|') { return type } } else if (token == '{') { consume() type.record = parseRecord() return type } if ([nonNullable, nullable].includes(token)) throw new Error('Nullability already defined.') if (/[=),:.<>}|]/.test(token)) throw new Error(`Unexpected token ${token}.`) if (peek() != '|') { type.name = peek() consume() } if (fn == token) { type.function = parseFunction() } else if ( peek() == '<' || (applicationWithDot = (peek() == '.' && peekNext() == '<')) ) { consume() if (applicationWithDot) consume() type.application = parseApplication() } while(peek() == '.') { type.name += '.' consume() const { name } = parseType(false) if (!name) throw new Error('Expected to see the name after .') type.name += name } if (peek() != '|' || !specialAllowed) return type union.push(type) while(peek() == '|') { consume() const nextType = parseType(true, union) // const u = nextType.union ? nextType.union : [nextType] if(nextType.union !== union) union.push(nextType) } /** @type {!_typedefsParser.Type} */ const unionType = { union } return unionType } return parseType() } /** * @suppress {nonStandardJsDocs} * @typedef {import('.').Type} _typedefsParser.Type */ /** * @suppress {nonStandardJsDocs} * @typedef {import('.').FunctionType} _typedefsParser.FunctionType */ module.exports.lex = lex module.exports.parser = parser