UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

359 lines (358 loc) 15 kB
import * as result from "../../types/result/module.f.js"; import { fold, first, drop, toArray, length, concat } from "../../types/list/module.f.js"; import { setReplace, at } from "../../types/ordered_map/module.f.js"; import { fromMap } from "../../types/object/module.f.js"; const parseInitialOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'id': { switch (token.value) { case 'import': return { ...state, state: 'import' }; case 'const': return { ...state, state: 'const' }; case 'export': return { ...state, state: 'export' }; } } } return foldOp({ token, metadata })({ ...state, state: 'exportValue', valueState: '', top: null, stack: null }); }; const parseNewLineRequiredOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case '//': case '/*': return state; case 'nl': return { ...state, state: '' }; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseExportOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; case 'id': { if (token.value === 'default') return { ...state, state: 'exportValue', valueState: '', top: null, stack: null }; } } return { state: 'error', error: { message: 'unexpected token', metadata } }; }; const parseResultOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': case 'eof': return state; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseConstOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'id': { if (at(token.value)(state.module.refs) !== null) return { state: 'error', error: { message: 'duplicate id', metadata } }; const cref = ['cref', length(state.module.consts)]; const refs = setReplace(token.value)(cref)(state.module.refs); return { ...state, state: 'const+name', module: { ...state.module, refs: refs } }; } case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseConstNameOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case '=': return { ...state, state: 'constValue', valueState: '', top: null, stack: null }; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseImportOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'id': { if (at(token.value)(state.module.refs) !== null) { return { state: 'error', error: { message: 'duplicate id', metadata } }; } const aref = ['aref', length(state.module.modules)]; const refs = setReplace(token.value)(aref)(state.module.refs); return { ...state, state: 'import+name', module: { ...state.module, refs: refs } }; } case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseImportNameOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; case 'id': { if (token.value === 'from') return { ...state, state: 'import+from' }; } } return { state: 'error', error: { message: 'unexpected token', metadata } }; }; const parseImportFromOp = ({ token, metadata }) => state => { switch (token.kind) { case 'ws': case 'nl': case '//': case '/*': return state; case 'string': { const modules = concat(state.module.modules)([token.value]); return { ...state, state: 'nl', module: { ...state.module, modules: modules } }; } case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const addKeyToObject = obj => key => (['object', obj[1], key]); const addValueToObject = obj => value => (['object', setReplace(obj[2])(value)(obj[1]), '']); const addToArray = array => value => (['array', concat(array[1])([value])]); const pushKey = state => key => metadata => { if (state.top?.[0] === 'object') { return { ...state, valueState: '{k', top: addKeyToObject(state.top)(key), stack: state.stack }; } return { state: 'error', error: { message: 'error', metadata } }; }; const pushValue = state => value => { if (state.top === null) { const consts = concat(state.module.consts)([value]); switch (state.state) { case 'exportValue': return { ...state, state: 'result', module: { ...state.module, consts: consts } }; case 'constValue': return { ...state, state: 'nl', module: { ...state.module, consts: consts } }; } } if (state.top?.[0] === 'array') { return { ...state, valueState: '[v', top: addToArray(state.top)(value), stack: state.stack }; } return { ...state, valueState: '{v', top: addValueToObject(state.top)(value), stack: state.stack }; }; const pushRef = state => name => metadata => { const ref = at(name)(state.module.refs); if (ref === null) return { state: 'error', error: { message: 'const not found', metadata } }; return pushValue(state)(ref); }; const startArray = state => { const newStack = state.top === null ? null : { first: state.top, tail: state.stack }; return { ...state, valueState: '[', top: ['array', null], stack: newStack }; }; const endArray = state => { const top = state.top; const newState = { ...state, valueState: '', top: first(null)(state.stack), stack: drop(1)(state.stack) }; if (top !== null && top[0] === 'array') { const array = ['array', toArray(top[1])]; return pushValue(newState)(array); } return pushValue(newState)(null); }; const startObject = state => { const newStack = state.top === null ? null : { first: state.top, tail: state.stack }; return { ...state, valueState: '{', top: ['object', null, ''], stack: newStack }; }; const endObject = state => { const obj = state?.top !== null && state?.top[0] === 'object' ? fromMap(state.top[1]) : null; const newState = { ...state, valueState: '', top: first(null)(state.stack), stack: drop(1)(state.stack) }; return pushValue(newState)(obj); }; const tokenToValue = token => { switch (token.kind) { case 'null': return null; case 'false': return false; case 'true': return true; case 'number': return parseFloat(token.value); case 'string': return token.value; case 'bigint': return token.value; case 'undefined': return undefined; default: return null; } }; const isValueToken = token => { switch (token.kind) { case 'null': case 'false': case 'true': case 'number': case 'string': case 'bigint': case 'undefined': return true; default: return false; } }; const parseValueOp = ({ token, metadata }) => state => { switch (token.kind) { case ']': if (state.valueState === '[,') { return endArray(state); } return { state: 'error', error: { message: 'unexpected token', metadata } }; case 'id': return pushRef(state)(token.value)(metadata); case '[': return startArray(state); case '{': return startObject(state); case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: if (isValueToken(token)) { return pushValue(state)(tokenToValue(token)); } return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseArrayStartOp = ({ token, metadata }) => state => { if (isValueToken(token)) { return pushValue(state)(tokenToValue(token)); } switch (token.kind) { case 'id': return pushRef(state)(token.value)(metadata); case '[': return startArray(state); case ']': return endArray(state); case '{': return startObject(state); case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseArrayValueOp = ({ token, metadata }) => state => { switch (token.kind) { case ']': return endArray(state); case ',': return { ...state, valueState: '[,', top: state.top, stack: state.stack }; case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; // allow identifier property names (#2410) const parseObjectStartOp = ({ token, metadata }) => state => { switch (token.kind) { case 'string': case 'id': return pushKey(state)(String(token.value))(metadata); case '}': return endObject(state); case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseObjectKeyOp = ({ token, metadata }) => state => { switch (token.kind) { case ':': return { ...state, valueState: '{:', top: state.top, stack: state.stack }; case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseObjectColonOp = ({ token, metadata }) => state => { if (isValueToken(token)) { return pushValue(state)(tokenToValue(token)); } switch (token.kind) { case 'id': return pushRef(state)(token.value)(metadata); case '[': return startArray(state); case '{': return startObject(state); case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseObjectNextOp = ({ token, metadata }) => state => { switch (token.kind) { case '}': return endObject(state); case ',': return { ...state, valueState: '{,', top: state.top, stack: state.stack }; case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const parseObjectCommaOp = ({ token, metadata }) => state => { switch (token.kind) { case '}': return endObject(state); case 'string': case 'id': return pushKey(state)(String(token.value))(metadata); case 'ws': case 'nl': case '//': case '/*': return state; case 'eof': return { state: 'error', error: { message: 'unexpected end', metadata } }; default: return { state: 'error', error: { message: 'unexpected token', metadata } }; } }; const foldOp = token => state => { switch (state.state) { case '': return parseInitialOp(token)(state); case 'nl': return parseNewLineRequiredOp(token)(state); case 'import': return parseImportOp(token)(state); case 'import+name': return parseImportNameOp(token)(state); case 'import+from': return parseImportFromOp(token)(state); case 'const': return parseConstOp(token)(state); case 'const+name': return parseConstNameOp(token)(state); case 'export': return parseExportOp(token)(state); case 'result': return parseResultOp(token)(state); case 'error': return { state: 'error', error: state.error }; case 'constValue': case 'exportValue': { switch (state.valueState) { case '': return parseValueOp(token)(state); case '[': return parseArrayStartOp(token)(state); case '[v': return parseArrayValueOp(token)(state); case '[,': return parseValueOp(token)(state); case '{': return parseObjectStartOp(token)(state); case '{k': return parseObjectKeyOp(token)(state); case '{:': return parseObjectColonOp(token)(state); case '{v': return parseObjectNextOp(token)(state); case '{,': return parseObjectCommaOp(token)(state); } } } }; export const parseFromTokens = (tokenList) => { const state = fold(foldOp)({ state: '', module: { refs: null, modules: null, consts: null } })(tokenList); switch (state.state) { case 'result': return result.ok([toArray(state.module.modules), toArray(state.module.consts)]); case 'error': return result.error(state.error); default: return result.error({ message: 'unexpected end', metadata: null }); } };