functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
359 lines (358 loc) • 15 kB
JavaScript
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 });
}
};