Added tableLookup function

This commit is contained in:
Stefan Zermatten
2021-02-24 11:57:40 +02:00
parent 331fcef9ad
commit e11ab39864
2 changed files with 120 additions and 43 deletions

View File

@@ -1,3 +1,5 @@
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
export default {
'abs': {
comment: 'Returns the absolute value of a number',
@@ -5,7 +7,7 @@ export default {
{input: 'abs(9)', result: '9'},
{input: 'abs(-3)', result: '3'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.abs,
},
@@ -15,21 +17,21 @@ export default {
{input: 'sqrt(16)', result: '4'},
{input: 'sqrt(10)', result: '3.1622776601683795'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.sqrt,
},
'max': {
comment: 'Returns the largest of the given numbers',
examples: [{input: 'min(12, 6, 3, 168)', result: '168'}],
argumentType: 'number',
examples: [{input: 'max(12, 6, 3, 168)', result: '168'}],
arguments: anyNumberOf('number'),
resultType: 'number',
fn: Math.max,
},
'min': {
comment: 'Returns the smallest of the given numbers',
examples: [{input: 'min(12, 6, 3, 168)', result: '3'}],
argumentType: 'number',
arguments: anyNumberOf('number'),
resultType: 'number',
fn: Math.min,
},
@@ -40,7 +42,7 @@ export default {
{input: 'round(5.5)', result: '6'},
{input: 'round(5.05)', result: '5'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.round,
},
@@ -52,7 +54,7 @@ export default {
{input: 'floor(5)', result: '5'},
{input: 'floor(-5.5)', result: '-6'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.floor,
},
@@ -64,7 +66,7 @@ export default {
{input: 'ceil(5)', result: '5'},
{input: 'ceil(-5.5)', result: '-5'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.ceil,
},
@@ -76,7 +78,7 @@ export default {
{input: 'trunc(5)', result: '5'},
{input: 'trunc(-5.5)', result: '-5'},
],
argumentType: 'number',
arguments:[ 'number'],
resultType: 'number',
fn: Math.trunc,
},
@@ -87,8 +89,32 @@ export default {
{input: 'sign(3)', result: '1'},
{input: 'sign(0)', result: '0'},
],
argumentType: 'number',
arguments: ['number'],
resultType: 'number',
fn: Math.sign,
},
'tableLookup': {
comment: 'Returns the index of the last value in the array that is less than the specified amount',
examples: [
{input: 'tableLookup([100, 300, 900], 457)', result: '2'},
{input: 'tableLookup([100, 300, 900], 23)', result: '0'},
{input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'},
{input: 'tableLookup([100, 300], 594)', result: '2'},
],
arguments: [ArrayNode, 'number'],
resultType: 'number',
fn: function tableLookup(arrayNode, number){
for(let i in arrayNode.values){
let node = arrayNode.values[i];
if (node.value > number) return i;
}
return arrayNode.values.length;
}
}
}
function anyNumberOf(type){
let argumentArray = [type];
argumentArray.anyLength = true;
return argumentArray;
}

View File

@@ -11,40 +11,64 @@ export default class CallNode extends ParseNode {
}
resolve(fn, scope, context){
let func = functions[this.functionName];
// Check that the function exists
if (!func) return new ErrorNode({
node: this,
error: `${this.functionName} is not a function`,
error: `${this.functionName} is not a supported function`,
context,
});
let args = castArgsToType({fn, scope, context, args: this.args, type: func.argumentType});
if (args.failed){
if (fn === 'reduce'){
// Resolve the arguments
let resolvedArgs = this.args.map(node => node[fn](scope, context));
// Check that the arguments match what is expected
let checkFailed = this.checkArugments({
fn,
context,
resolvedArgs,
argumentsExpected: func.arguments
});
if (checkFailed){
if (fn !== 'reduce'){
return new ErrorNode({
node: this,
error: 'Could not convert all arguments to the correct type',
context,
error: `Invalid arguments to ${this.functionName} function`,
});
} else {
return new CallNode({
functionName: this.functionName,
args: args,
args: resolvedArgs,
});
}
} else {
try {
let value = func.fn.apply(null, args);
return new ConstantNode({
value,
type: 'number',
previousNodes: [this],
});
} catch (error) {
return new ErrorNode({
node: this,
error,
context,
});
}
// Map contant nodes to constants before attempting to run the function
let mappedArgs = resolvedArgs.map(node => {
if (node instanceof ConstantNode){
return node.value;
} else {
return node;
}
});
try {
// Run the function
let value = func.fn.apply(null, mappedArgs);
let type = typeof value;
if (type === 'number' || type === 'string' || type === 'boolean'){
// Convert constant results into constant nodes
return new ConstantNode({ value, type });
} else {
return value;
}
} catch (error) {
return new ErrorNode({
node: this,
error: error.message || error,
context,
});
}
}
toString(){
@@ -57,20 +81,47 @@ export default class CallNode extends ParseNode {
replaceChildren(fn){
this.args = this.args.map(arg => arg.replaceNodes(fn));
}
}
checkArugments({fn, context, argumentsExpected, resolvedArgs}){
// Check that the number of arguments matches the number expected
if (
!argumentsExpected.anyLength &&
argumentsExpected.length !== resolvedArgs.length
){
context.storeError({
type: 'error',
message: 'Incorrect number of arguments ' +
`to ${this.functionName} function, ` +
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`
});
return true;
}
function castArgsToType({fn, scope, context, args, type}){
let resolvedArgs = args.map(node => node[fn](scope, context))
let result = [];
if (type === 'number'){
resolvedArgs.forEach(node => {
if (node.isNumber){
result.push(node.value);
let failed = false;
// Check that each argument is of the correct type
resolvedArgs.forEach((node, index) => {
let type;
if (argumentsExpected.anyLength){
type = argumentsExpected[0];
} else {
resolvedArgs.failed = true;
type = argumentsExpected[index];
}
})
if (typeof type === 'string'){
// Type being a string means a constant node with matching type
if (node.type !== type) failed = true;
} else {
// Otherwise check that the node is an instance of the given type
if (!(node instanceof type)) failed = true;
}
if (failed && fn === 'reduce'){
let typeName = typeof type === 'string' ? type : type.constructor.name;
let nodeName = node.type || node.constructor.name
context.storeError({
type: 'error',
message: `Incorrect arguments to ${this.functionName} function` +
`expected ${typeName} got ${nodeName}`
});
}
});
return failed;
}
if (resolvedArgs.failed) return resolvedArgs;
return result;
}