Typescript all the parser things

This commit is contained in:
Thaum Rystra
2024-02-20 23:21:12 +02:00
parent 3ea492ee78
commit ac15512bc5
86 changed files with 926 additions and 718 deletions

View File

@@ -1,28 +0,0 @@
import { ParseNode } from '/imports/parser/parser';
import { ResolvedResult, Context } from '/imports/parser/resolve';
export type ResolveLevel = 'compile' | 'roll' | 'reduce';
export default interface NodeFactory {
create(node: Partial<ParseNode>): ParseNode;
compile?(
node: ParseNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll?(
node: ParseNode, scope: Record<string, any>, context: Context
): ResolvedResult;
reduce?(
node: ParseNode, scope: Record<string, any>, context: Context
): ResolvedResult;
resolve?(
fn: ResolveLevel, node: ParseNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: ParseNode): string;
traverse?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
}

View File

@@ -1,34 +0,0 @@
import accessor from './accessor';
import array from './array';
import call from './call';
import constant from './constant';
import error from './error';
import ifNode from './if';
import index from './indexNode';
import not from './not';
import operator from './operator';
import parenthesis from './parenthesis';
import roll from './roll';
import rollArray from './rollArray';
import unaryOperator from './unaryOperator';
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
const factories: Record<string, NodeFactory> = {
accessor,
array,
call,
constant,
error,
if: ifNode,
index,
not,
operator,
parenthesis,
roll,
rollArray,
// What used to be symbols are now just treated as accessors without a path
symbol: accessor,
unaryOperator,
};
export default factories;

View File

@@ -1,28 +1,20 @@
import constant from './constant';
import array from './array';
import resolve, { Context, ResolvedResult } from '/imports/parser/resolve';
import constant from '/imports/parser/parseTree/constant';
import array from '/imports/parser/parseTree/array';
import ResolvedResult from '/imports/parser/types/ResolvedResult';
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
export type AccessorNode = {
parseType: 'accessor';
parseType: 'accessor' | 'symbol';
path?: string[];
name: string;
}
interface AccessorFactory extends NodeFactory {
type AccessorFactory = {
create(node: Partial<AccessorNode>): AccessorNode;
compile(
node: AccessorNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll?: undefined;
resolve?: undefined;
reduce(
node: AccessorNode, scope: Record<string, any>, context: Context
): ResolvedResult;
compile: ResolveLevelFunction<AccessorNode>;
reduce: ResolveLevelFunction<AccessorNode>;
toString(node: AccessorNode): string;
traverse?: undefined;
map?: undefined;
}
const accessor: AccessorFactory = {
@@ -33,9 +25,7 @@ const accessor: AccessorFactory = {
name,
};
},
compile(
node: AccessorNode, scope: Record<string, any>, context: Context
): ResolvedResult {
async compile(node, scope, context) {
let value = getFromScope(node.name, scope);
// Get the value from the given path
node.path?.forEach(name => {
@@ -102,9 +92,9 @@ const accessor: AccessorFactory = {
context,
};
},
reduce(node, scope, context): ResolvedResult {
let { result } = accessor.compile(node, scope, context);
({ result } = resolve('reduce', result, scope, context));
async reduce(node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
let { result } = await accessor.compile(node, scope, context, inputProvider, resolveOthers);
({ result } = await resolveOthers('reduce', result, scope, context, inputProvider));
if (result.parseType === 'accessor') {
return {
result: constant.create({

View File

@@ -1,28 +1,26 @@
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
import constant from './constant';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import { serialMap } from '/imports/api/utility/asyncMap';
import constant from '/imports/parser/parseTree/constant';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type ArrayNode = {
parseType: 'array';
values: ParseNode[];
}
interface ArrayFactory extends NodeFactory {
type ArrayFactory = {
create(node: Partial<ArrayNode>): ArrayNode;
fromConstantArray(array: (string | number | boolean | undefined)[]): ArrayNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: ArrayNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: ArrayNode): string;
traverse(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<ArrayNode>;
toString: ToStringFunction<ArrayNode>;
traverse: TraverseFunction<ArrayNode>;
map: MapFunction<ArrayNode>;
}
const array: ArrayFactory = {
const arrayFactory: ArrayFactory = {
create({ values }: { values: ParseNode[] }) {
return {
parseType: 'array',
@@ -44,32 +42,33 @@ const array: ArrayFactory = {
return constant.create({ value: undefined });
}
});
return array.create({ values });
return arrayFactory.create({ values });
},
resolve(fn, node, scope, context): ResolvedResult {
const values = node.values.map(node => {
const { result } = resolve(fn, node, scope, context);
return result;
});
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const values: ParseNode[] = [];
for (const val of node.values) {
const { result } = await resolveOthers(fn, val, scope, context, inputProvider);
values.push(result);
}
return {
result: array.create({ values }),
result: arrayFactory.create({ values }),
context,
};
},
toString(node) {
return `[${node.values.map(value => toString(value)).join(', ')}]`;
toString(node, toStringOthers) {
return `[${node.values.map(value => toStringOthers(value)).join(', ')}]`;
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
node.values.forEach(value => traverse(value, fn));
node.values.forEach(value => traverseOthers(value, fn));
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.values = node.values.map(value => map(value, fn));
node.values = await serialMap(node.values, async value => await mapOthers(value, fn));
}
return resultingNode;
},
}
export default array;
export default arrayFactory;

View File

@@ -1,9 +1,16 @@
import error from './error';
import constant from './constant';
console.log('call.ts imports')
import error from '/imports/parser/parseTree/error';
import constant from '/imports/parser/parseTree/constant';
import functions, { ParserFunction } from '/imports/parser/functions';
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
import Context from '../types/Context';
import ResolvedResult from '../types/ResolvedResult';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import { serialMap } from '/imports/api/utility/asyncMap';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import ResolveLevel from '/imports/parser/types/ResolveLevel';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type CallNode = {
parseType: 'call';
@@ -11,14 +18,12 @@ export type CallNode = {
args: ParseNode[];
}
interface CallFactory extends NodeFactory {
type CallFactory = {
create(node: Partial<CallNode>): CallNode;
resolve(
fn: ResolveLevel, node: CallNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: CallNode): string;
traverse(node: CallNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: CallNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<CallNode>;
toString: ToStringFunction<CallNode>;
traverse: TraverseFunction<CallNode>;
map: MapFunction<CallNode>;
checkArguments(node: CallNode, fn: ResolveLevel, func: ParserFunction,
resolvedArgs: ParseNode[], context: Context): boolean;
}
@@ -31,7 +36,7 @@ const call: CallFactory = {
args,
}
},
resolve(fn, node, scope, context): ResolvedResult {
async resolve(fn, node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
const func = functions[node.functionName];
// Check that the function exists
if (!func) {
@@ -46,7 +51,7 @@ const call: CallFactory = {
}
// Resolve a given node to a maximum depth of resolution
const resolveToLevel = (node, maxResolveFn = 'reduce'): ResolvedResult => {
const resolveToLevel = (node, maxResolveFn = 'reduce'): Promise<ResolvedResult> => {
// Determine the actual depth to resolve to
let resolveFn: ResolveLevel = 'reduce';
if (fn === 'compile' || maxResolveFn === 'compile') {
@@ -55,14 +60,15 @@ const call: CallFactory = {
resolveFn = 'roll';
}
// Resolve
return resolve(resolveFn, node, scope, context);
return resolveOthers(resolveFn, node, scope, context, inputProvider);
}
// Resolve the arguments
const resolvedArgs = node.args.map((arg, i) => {
const { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]);
return result;
});
const resolvedArgs: ParseNode[] = [];
for (const [i, arg] of node.args.entries()) {
const { result } = await resolveToLevel(arg, func.maxResolveLevels?.[i]);
resolvedArgs.push(result);
}
// Check that the arguments match what is expected
const checkFailed = call.checkArguments(node, fn, func, resolvedArgs, context);
@@ -116,7 +122,7 @@ const call: CallFactory = {
};
} else {
// Resolve the return value
return resolve(fn, value, scope, context);
return resolveOthers(fn, value, scope, context, inputProvider);
}
} catch (err) {
context.error(`Internal error: ${err.message || err}`);
@@ -129,22 +135,22 @@ const call: CallFactory = {
}
}
},
toString(node) {
return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`;
toString(node, toStringOthers) {
return `${node.functionName}(${node.args.map(arg => toStringOthers(arg)).join(', ')})`;
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
node.args.forEach(arg => traverse(arg, fn));
node.args.forEach(arg => traverseOthers(arg, fn));
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.args = node.args.map(arg => map(arg, fn));
node.args = await serialMap(node.args, async arg => mapOthers(arg, fn));
}
return resultingNode;
},
checkArguments(node, fn, func, resolvedArgs, context) {
const argumentsExpected = func.arguments;
checkArguments(callNode, fn, func, resolvedArgs, context) {
const argumentsExpected = func.arguments as any;
// Check that the number of arguments matches the number expected
if (
!argumentsExpected.anyLength &&
@@ -152,7 +158,7 @@ const call: CallFactory = {
resolvedArgs.length < (func.minArguments ?? argumentsExpected.length)
) {
context.error('Incorrect number of arguments ' +
`to ${node.functionName} function, ` +
`to ${callNode.functionName} function, ` +
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`);
return true;
}
@@ -160,18 +166,22 @@ const call: CallFactory = {
let failed = false;
// Check that each argument is of the correct type
resolvedArgs.forEach((node, index) => {
let type: string;
let type;
if (argumentsExpected.anyLength) {
type = argumentsExpected[0];
} else {
type = argumentsExpected[index];
}
if (type === 'parseNode') return;
if (node.parseType !== type && node.valueType !== type) failed = true;
if (
node.parseType !== type
&& node.parseType === 'constant'
&& node.valueType !== type
) failed = true;
if (failed && fn === 'reduce') {
const typeName = typeof type === 'string' ? type : type.constructor.name;
const nodeName = node.parseType;
context.error(`Incorrect arguments to ${node.functionName} function` +
context.error(`Incorrect arguments to ${callNode.functionName} function` +
`expected ${typeName} got ${nodeName}`);
}
});

View File

@@ -1,5 +1,5 @@
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
import { Context, ResolvedResult } from '/imports/parser/resolve';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
export type ConstantValueType = number | string | boolean | undefined
@@ -10,17 +10,17 @@ export type ConstantNode = {
valueType: 'number' | 'string' | 'boolean' | 'undefined';
}
interface ConstantFactory extends NodeFactory {
export type FiniteNumberConstantNode = {
parseType: 'constant';
value: number;
// TODO replace all `constantNode.valueType` with `typeof constantNode.value`
valueType: 'number';
}
type ConstantFactory = {
create({ value }: { value: ConstantValueType }): ConstantNode;
compile(
node: ConstantNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll?: undefined;
reduce?: undefined;
resolve?: undefined;
compile: ResolveLevelFunction<ConstantNode>;
toString(node: ConstantNode): string;
traverse?: undefined;
map?: undefined;
}
const constant: ConstantFactory = {
@@ -31,7 +31,7 @@ const constant: ConstantFactory = {
value,
}
},
compile(node, scope, context) {
async compile(node, scope, context) {
return { result: node, context };
},
toString(node) {
@@ -39,4 +39,11 @@ const constant: ConstantFactory = {
},
}
export function isFiniteNode(node: ParseNode): node is FiniteNumberConstantNode {
return node.parseType === 'constant'
&& node.valueType === 'number'
&& typeof node.value === 'number'
&& isFinite(node.value);
}
export default constant;

View File

@@ -1,6 +1,6 @@
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import { Context, ResolvedResult } from '/imports/parser/resolve';
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type ErrorNode = {
parseType: 'error';
@@ -8,17 +8,10 @@ export type ErrorNode = {
error: string;
}
interface ErrorFactory extends NodeFactory {
interface ErrorFactory {
create(node: Partial<ErrorNode>): ErrorNode;
compile(
node: ErrorNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll?: undefined;
reduce?: undefined;
resolve?: undefined;
toString(node: ErrorNode): string;
traverse?: undefined;
map?: undefined;
compile: ResolveLevelFunction<ErrorNode>;
toString: ToStringFunction<ErrorNode>;
}
const error: ErrorFactory = {
@@ -29,7 +22,7 @@ const error: ErrorFactory = {
error,
}
},
compile(node, scope, context) {
async compile(node, scope, context) {
return { result: node, context };
},
toString(node) {

View File

@@ -1,7 +1,9 @@
import resolve, { traverse, toString, map } from '../resolve';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import { Context, ResolvedResult } from '/imports/parser/resolve';
import ResolvedResult from '../types/ResolvedResult';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type IfNode = {
parseType: 'if';
@@ -10,17 +12,12 @@ export type IfNode = {
alternative: ParseNode;
}
interface IfFactory extends NodeFactory {
type IfFactory = {
create(node: Partial<IfNode>): IfNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: IfNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: IfNode): string;
traverse(node: IfNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: IfNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<IfNode>;
toString: ToStringFunction<IfNode>;
traverse: TraverseFunction<IfNode>;
map: MapFunction<IfNode>;
}
const ifNode: IfFactory = {
@@ -35,18 +32,18 @@ const ifNode: IfFactory = {
alternative,
};
},
toString(node) {
toString(node, stringOthers) {
const { condition, consequent, alternative } = node;
condition.parseType
return `${toString(condition)} ? ${toString(consequent)} : ${toString(alternative)}`
return `${stringOthers(condition)} ? ${stringOthers(consequent)} : ${stringOthers(alternative)}`
},
resolve(fn, node, scope, context): ResolvedResult {
const { result: condition } = resolve(fn, node.condition, scope, context);
async resolve(fn, node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
const { result: condition } = await resolveOthers(fn, node.condition, scope, context, inputProvider);
if (condition.parseType === 'constant') {
if (condition.value) {
return resolve(fn, node.consequent, scope, context);
return resolveOthers(fn, node.consequent, scope, context, inputProvider);
} else {
return resolve(fn, node.alternative, scope, context);
return resolveOthers(fn, node.alternative, scope, context, inputProvider);
}
} else {
return {
@@ -59,18 +56,18 @@ const ifNode: IfFactory = {
};
}
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.condition, fn);
traverse(node.consequent, fn);
traverse(node.alternative, fn);
traverseOthers(node.condition, fn);
traverseOthers(node.consequent, fn);
traverseOthers(node.alternative, fn);
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.condition = map(node.condition, fn);
node.consequent = map(node.consequent, fn);
node.alternative = map(node.alternative, fn);
node.condition = await mapOthers(node.condition, fn);
node.consequent = await mapOthers(node.consequent, fn);
node.alternative = await mapOthers(node.alternative, fn);
}
return resultingNode;
},

View File

@@ -0,0 +1,45 @@
console.log('index.ts imports')
import accessor from '/imports/parser/parseTree/accessor';
import array from '/imports/parser/parseTree/array';
import call from '/imports/parser/parseTree/call';
import constant from '/imports/parser/parseTree/constant';
import error from '/imports/parser/parseTree/error';
import ifNode from '/imports/parser/parseTree/if';
import index from '/imports/parser/parseTree/indexNode';
import not from '/imports/parser/parseTree/not';
import operator from '/imports/parser/parseTree/operator';
import parenthesis from '/imports/parser/parseTree/parenthesis';
import roll from '/imports/parser/parseTree/roll';
import rollArray from '/imports/parser/parseTree/rollArray';
import unaryOperator from '/imports/parser/parseTree/unaryOperator';
console.log('index.ts')
const factories = {
accessor,
array,
call,
constant,
error,
if: ifNode,
index,
not,
operator,
parenthesis,
roll,
rollArray,
// What used to be symbols are now just treated as accessors without a path
symbol: accessor,
unaryOperator,
};
console.log('---------------------');
console.log('---------------------');
console.log('---------------------');
console.log(factories.array);
console.log('---------------------');
console.log('---------------------');
console.log('---------------------');
export default factories;

View File

@@ -1,7 +1,11 @@
import resolve, { traverse, toString, map, ResolvedResult, Context } from '/imports/parser/resolve';
import error from './error';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import error from '/imports/parser/parseTree/error';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import toString from '/imports/parser/toString';
import { isFiniteNode } from '/imports/parser/parseTree/constant';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type IndexNode = {
parseType: 'index';
@@ -9,17 +13,12 @@ export type IndexNode = {
index: ParseNode;
}
interface IndexFactory extends NodeFactory {
type IndexFactory = {
create(node: Partial<IndexNode>): IndexNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: IndexNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: IndexNode): string;
traverse(node: IndexNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: IndexNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<IndexNode>;
toString: ToStringFunction<IndexNode>;
traverse: TraverseFunction<IndexNode>;
map: MapFunction<IndexNode>;
}
const indexNode: IndexFactory = {
@@ -30,25 +29,22 @@ const indexNode: IndexFactory = {
index,
}
},
resolve(fn, node, scope, context) {
const { result: index } = resolve(fn, node.index, scope, context);
const { result: array } = resolve(fn, node.array, scope, context);
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const { result: index } = await resolveOthers(fn, node.index, scope, context, inputProvider);
const { result: array } = await resolveOthers(fn, node.array, scope, context, inputProvider);
if (
index.valueType === 'number' &&
isFiniteNode(index) &&
Number.isInteger(index.value) &&
array.parseType === 'array'
) {
if (index.value < 1 || index.value > array.values.length) {
context.error({
type: 'warning',
message: `Index of ${index.value} is out of range for an array` +
` of length ${array.values.length}`,
});
context.error(`Index of ${index.value} is out of range for an array` +
` of length ${array.values.length}`);
}
const selection = array.values[index.value - 1];
if (selection) {
return resolve(fn, selection, scope, context);
return resolveOthers(fn, selection, scope, context, inputProvider);
}
} else if (fn === 'reduce') {
if (array.parseType !== 'array') {
@@ -61,7 +57,7 @@ const indexNode: IndexFactory = {
}),
context,
};
} else if (!index.isInteger) {
} else if (!isFiniteNode(index) || !Number.isInteger(index.value)) {
const message = `${toString(array)} is not an integer index of the array`
context.error(message);
return {
@@ -81,19 +77,19 @@ const indexNode: IndexFactory = {
context,
};
},
toString(node) {
return `${toString(node.array)}[${toString(node.index)}]`;
toString(node, stringOthers) {
return `${stringOthers(node.array)}[${stringOthers(node.index)}]`;
},
traverse(node, fn: (node: ParseNode) => any) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.array, fn);
traverse(node.index, fn);
traverseOthers(node.array, fn);
traverseOthers(node.index, fn);
},
map(node, fn: (node: ParseNode) => any) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.array = map(node.array, fn);
node.index = map(node.index, fn);
node.array = await mapOthers(node.array, fn);
node.index = await mapOthers(node.index, fn);
}
return resultingNode;
},

View File

@@ -1,24 +1,21 @@
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
import constant from './constant';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import constant from '/imports/parser/parseTree/constant';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type NotNode = {
parseType: 'not';
right: ParseNode;
}
interface NotFactory extends NodeFactory {
type NotFactory = {
create(node: Partial<NotNode>): NotNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: NotNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: NotNode): string;
traverse(node: NotNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: NotNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<NotNode>;
toString: ToStringFunction<NotNode>;
traverse: TraverseFunction<NotNode>;
map: MapFunction<NotNode>;
}
const not: NotFactory = {
@@ -28,8 +25,8 @@ const not: NotFactory = {
right,
}
},
resolve(fn, node, scope, context) {
const { result: right } = resolve(fn, node.right, scope, context);
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const { result: right } = await resolveOthers(fn, node.right, scope, context, inputProvider);
if (right.parseType !== 'constant') {
return {
result: not.create({
@@ -45,17 +42,17 @@ const not: NotFactory = {
context,
};
},
toString(node) {
return `!${toString(node.right)}`;
toString(node, stringOthers) {
return `!${stringOthers(node.right)}`;
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.right, fn);
traverseOthers(node.right, fn);
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.right = map(node.right, fn);
node.right = await mapOthers(node.right, fn);
}
return resultingNode;
},

View File

@@ -1,7 +1,9 @@
import resolve, { toString, traverse, map, ResolvedResult, Context } from '/imports/parser/resolve';
import constant from './constant';
import constant, { ConstantValueType, isFiniteNode } from '/imports/parser/parseTree/constant';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
type OperatorSymbol = '*' | '/' | '^' | '+' | '-' | '%' | '&' | '&&' | '|' | '||' | '=' |
'==' | '===' | '!=' | '!==' | '>' | '<' | '>=' | '<=';
@@ -13,17 +15,12 @@ export type OperatorNode = {
operator: OperatorSymbol;
}
interface OperatorFactory extends NodeFactory {
type OperatorFactory = {
create(node: Partial<OperatorNode>): OperatorNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: OperatorNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: OperatorNode): string;
traverse(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<OperatorNode>;
toString: ToStringFunction<OperatorNode>;
traverse: TraverseFunction<OperatorNode>;
map: MapFunction<OperatorNode>;
}
// Which operators can be considered commutative by the parser
@@ -43,10 +40,10 @@ const operator: OperatorFactory = {
operator,
};
},
resolve(fn, node, scope, context) {
const { result: leftNode } = resolve(fn, node.left, scope, context);
const { result: rightNode } = resolve(fn, node.right, scope, context);
let left, right;
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const { result: leftNode } = await resolveOthers(fn, node.left, scope, context, inputProvider);
const { result: rightNode } = await resolveOthers(fn, node.right, scope, context, inputProvider);
let left: ConstantValueType, right: ConstantValueType;
// If commutation is possible, do it and return that result
const commutatedResult = reorderCommutativeOperations(node, leftNode, rightNode);
@@ -73,32 +70,39 @@ const operator: OperatorFactory = {
context,
};
},
toString(node) {
toString(node, stringOthers) {
const { left, right, operator } = node;
// special case of adding a negative number
if (operator === '+' && right.valueType === 'number' && right.value < 0) {
return `${toString(left)} - ${-right.value}`
if (operator === '+' && isFiniteNode(right) && right.value < 0) {
return `${stringOthers(left)} - ${-right.value}`
}
return `${toString(left)} ${operator} ${toString(right)}`;
return `${stringOthers(left)} ${operator} ${stringOthers(right)}`;
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.left, fn);
traverse(node.right, fn);
traverseOthers(node.left, fn);
traverseOthers(node.right, fn);
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.left = map(node.left, fn);
node.right = map(node.right, fn);
node.left = await mapOthers(node.left, fn);
node.right = await mapOthers(node.right, fn);
}
return resultingNode;
},
}
function applyOperator(operator: OperatorSymbol, left: ParseNode, right: ParseNode) {
function applyOperator(operator: OperatorSymbol, left: ConstantValueType, right: ConstantValueType) {
let result;
if (left === undefined) {
left = 0;
}
if (right === undefined) {
right = 0;
}
switch (operator) {
// Typescript might complain about these, but they return NaN as expected
case '+': result = left + right; break;
case '-': result = left - right; break;
case '*': result = left * right; break;

View File

@@ -1,23 +1,20 @@
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
export type ParenthesisNode = {
parseType: 'parenthesis';
content: ParseNode;
}
interface ParenthesisFactory extends NodeFactory {
type ParenthesisFactory = {
create(node: Partial<ParenthesisNode>): ParenthesisNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: ParenthesisNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: ParenthesisNode): string;
traverse(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<ParenthesisNode>;
toString: ToStringFunction<ParenthesisNode>;
traverse: TraverseFunction<ParenthesisNode>;
map: MapFunction<ParenthesisNode>;
}
const parenthesis: ParenthesisFactory = {
@@ -27,8 +24,8 @@ const parenthesis: ParenthesisFactory = {
content,
};
},
resolve(fn, node, scope, context) {
const { result: content } = resolve(fn, node.content, scope, context);
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const { result: content } = await resolveOthers(fn, node.content, scope, context, inputProvider);
if (
fn === 'reduce' ||
content.parseType === 'constant' ||
@@ -42,17 +39,17 @@ const parenthesis: ParenthesisFactory = {
};
}
},
toString(node) {
return `(${toString(node.content)})`;
toString(node, stringOthers) {
return `(${stringOthers(node.content)})`;
},
traverse(node, fn: (node: ParseNode) => any) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.content, fn);
traverseOthers(node.content, fn);
},
map(node, fn: (node: ParseNode) => any) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.content = map(node.content, fn);
node.content = await mapOthers(node.content, fn);
}
return resultingNode;
},

View File

@@ -1,10 +1,12 @@
import resolve, { toString, traverse, map, ResolvedResult, Context } from '../resolve';
import error from './error';
import rollArray from './rollArray';
import rollDice from '/imports/parser/rollDice';
import error from '/imports/parser/parseTree/error';
import rollArray from '/imports/parser/parseTree/rollArray';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
import Context from '/imports/parser/types/Context';
export type RollNode = {
parseType: 'roll';
@@ -12,21 +14,14 @@ export type RollNode = {
right: ParseNode;
}
interface RollNodeFactory extends NodeFactory {
type RollNodeFactory = {
create(node: Partial<RollNode>): RollNode;
compile(
node: RollNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll(
node: RollNode, scope: Record<string, any>, context: Context
): ResolvedResult;
reduce(
node: RollNode, scope: Record<string, any>, context: Context
): ResolvedResult;
resolve?: undefined;
toString(node: RollNode): string;
traverse(node: RollNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: RollNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
compile: ResolveLevelFunction<RollNode>;
roll: ResolveLevelFunction<RollNode>;
reduce: ResolveLevelFunction<RollNode>;
toString: ToStringFunction<RollNode>;
traverse: TraverseFunction<RollNode>;
map: MapFunction<RollNode>;
}
const rollNode: RollNodeFactory = {
@@ -37,28 +32,28 @@ const rollNode: RollNodeFactory = {
right,
};
},
compile(node, scope, context) {
const { result: left } = resolve('compile', node.left, scope, context);
const { result: right } = resolve('compile', node.right, scope, context);
async compile(node, scope, context, inputProvider, resolveOthers) {
const { result: left } = await resolveOthers('compile', node.left, scope, context, inputProvider);
const { result: right } = await resolveOthers('compile', node.right, scope, context, inputProvider);
return {
result: rollNode.create({ left, right }),
context,
};
},
toString(node) {
toString(node, stringOthers) {
if (
node.left.parseType === 'constant'
&& typeof node.left.value === 'number'
&& node.left.value === 1
) {
return `d${toString(node.right)}`;
return `d${stringOthers(node.right)}`;
} else {
return `${toString(node.left)}d${toString(node.right)}`;
return `${stringOthers(node.left)}d${stringOthers(node.right)}`;
}
},
roll(node, scope, context) {
const { result: left } = resolve('reduce', node.left, scope, context);
const { result: right } = resolve('reduce', node.right, scope, context);
async roll(node, scope, context, inputProvider, resolveOthers) {
const { result: left } = await resolveOthers('reduce', node.left, scope, context, inputProvider);
const { result: right } = await resolveOthers('reduce', node.right, scope, context, inputProvider);
if (
left.parseType !== 'constant'
|| typeof left.value !== 'number'
@@ -82,7 +77,7 @@ const rollNode: RollNodeFactory = {
return errorResult(message, node, context);
}
const diceSize = right.value;
const values = rollDice(number, diceSize);
const [values] = await inputProvider.rollDice([{ number, diceSize }]);
if (context) {
context.rolls.push({ number, diceSize, values });
}
@@ -95,20 +90,20 @@ const rollNode: RollNodeFactory = {
context
};
},
reduce(node, scope, context) {
const { result } = rollNode.roll(node, scope, context);
return resolve('reduce', result, scope, context);
async reduce(node, scope, context, inputProvider, resolveOthers) {
const { result } = await rollNode.roll(node, scope, context, inputProvider, resolveOthers);
return resolveOthers('reduce', result, scope, context, inputProvider);
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.left, fn);
traverse(node.right, fn);
traverseOthers(node.left, fn);
traverseOthers(node.right, fn);
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.left = map(node.left, fn);
node.right = map(node.right, fn);
node.left = await mapOthers(node.left, fn);
node.right = await mapOthers(node.right, fn);
}
return resultingNode;
},

View File

@@ -1,6 +1,6 @@
import constant from './constant';
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
import { Context, ResolvedResult } from '/imports/parser/resolve';
import constant from '/imports/parser/parseTree/constant';
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
type RollValue = {
value: number,
@@ -17,19 +17,11 @@ export type RollArrayNode = {
diceNum: number,
}
interface RollArrayFactory extends NodeFactory {
type RollArrayFactory = {
create(input: { values: number[], diceSize: number, diceNum: number }): RollArrayNode;
compile(
node: RollArrayNode, scope: Record<string, any>, context: Context
): ResolvedResult;
roll?: undefined;
reduce(
node: RollArrayNode, scope: Record<string, any>, context: Context
): ResolvedResult;
resolve?: undefined;
toString(node: RollArrayNode): string;
traverse?: undefined;
map?: undefined;
compile: ResolveLevelFunction<RollArrayNode>;
reduce: ResolveLevelFunction<RollArrayNode>;
toString: ToStringFunction<RollArrayNode>;
}
const rollArray: RollArrayFactory = {
@@ -43,7 +35,7 @@ const rollArray: RollArrayFactory = {
diceNum,
};
},
compile(node, scope, context) {
async compile(node, scope, context) {
return {
result: node,
context
@@ -52,7 +44,7 @@ const rollArray: RollArrayFactory = {
toString(node) {
return `${node.diceNum || ''}d${node.diceSize} [${valuesToString(node.values)}]`;
},
reduce(node, scope, context) {
async reduce(node, scope, context) {
const total = node.values.reduce((a, b) => {
if (b.disabled) return a;
return a + b.value;

View File

@@ -1,7 +1,9 @@
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
import constant from './constant';
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
import constant from '/imports/parser/parseTree/constant';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import TraverseFunction from '/imports/parser/types/TraverseFunction';
import MapFunction from '/imports/parser/types/MapFunction';
import ToStringFunction from '/imports/parser/types/ToStringFunction';
type UnaryOperatorSymbol = '+' | '-';
@@ -11,17 +13,12 @@ export type UnaryOperatorNode = {
right: ParseNode;
}
interface UnaryOperatorFactory extends NodeFactory {
type UnaryOperatorFactory = {
create(node: Partial<UnaryOperatorNode>): UnaryOperatorNode;
compile?: undefined;
roll?: undefined;
reduce?: undefined;
resolve(
fn: ResolveLevel, node: UnaryOperatorNode, scope: Record<string, any>, context: Context
): ResolvedResult;
toString(node: UnaryOperatorNode): string;
traverse(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
map(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
resolve: ResolveFunction<UnaryOperatorNode>;
toString: ToStringFunction<UnaryOperatorNode>;
traverse: TraverseFunction<UnaryOperatorNode>;
map: MapFunction<UnaryOperatorNode>;
}
const unaryOperator: UnaryOperatorFactory = {
@@ -32,8 +29,8 @@ const unaryOperator: UnaryOperatorFactory = {
right,
};
},
resolve(fn, node, scope, context) {
const { result: rightNode } = resolve(fn, node.right, scope, context);
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
const { result: rightNode } = await resolveOthers(fn, node.right, scope, context, inputProvider);
if (
rightNode.parseType !== 'constant'
|| typeof rightNode.value !== 'number'
@@ -59,17 +56,17 @@ const unaryOperator: UnaryOperatorFactory = {
context,
};
},
toString(node) {
return `${node.operator}${toString(node.right)}`;
toString(node, stringOthers) {
return `${node.operator}${stringOthers(node.right)}`;
},
traverse(node, fn) {
traverse(node, fn, traverseOthers) {
fn(node);
traverse(node.right, fn);
traverseOthers(node.right, fn);
},
map(node, fn) {
const resultingNode = fn(node);
async map(node, fn, mapOthers) {
const resultingNode = await fn(node);
if (resultingNode === node) {
node.right = map(node.right, fn);
node.right = await mapOthers(node.right, fn);
}
return resultingNode;
},