Added functions and ensured the context was being passed around correctly

This commit is contained in:
Stefan Zermatten
2020-09-18 11:52:44 +02:00
parent 06f17a6d33
commit 6570665c1e
12 changed files with 209 additions and 53 deletions

View File

@@ -0,0 +1,83 @@
export default {
'abs': {
comment: 'Returns the absolute value of a number',
examples: [
{input: 'abs(9)', result: '9'},
{input: 'abs(-3)', result: '3'},
],
argumentType: 'number',
resultType: 'number',
fn: Math.abs,
},
'sqrt': {
comment: 'Returns the square root of a number',
examples: [
{input: 'sqrt(16)', result: '4'},
{input: 'sqrt(10)', result: '3.1622776601683795'},
],
argumentType: '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',
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',
resultType: 'number',
fn: Math.min,
},
'round': {
comment: 'Returns the value of a number rounded to the nearest integer',
examples: [
{input: 'round(5.95)', result: '6'},
{input: 'round(5.5)', result: '6'},
{input: 'round(5.05)', result: '5'},
],
argumentType: 'number',
resultType: 'number',
fn: Math.round,
},
'floor': {
comment: 'Rounds a number down to the next smallest integer',
examples: [
{input: 'floor(5.95)', result: '5'},
{input: 'floor(5.05)', result: '5'},
{input: 'floor(5)', result: '5'},
{input: 'floor(-5.5)', result: '-6'},
],
argumentType: 'number',
resultType: 'number',
fn: Math.floor,
},
'ceil': {
comment: 'Rounds a number up to the next largest integer',
examples: [
{input: 'ceil(5.95)', result: '6'},
{input: 'ceil(5.05)', result: '6'},
{input: 'ceil(5)', result: '5'},
{input: 'ceil(-5.5)', result: '-5'},
],
argumentType: 'number',
resultType: 'number',
fn: Math.ceil,
},
'trunc': {
comment: 'Returns the integer part of a number by removing any fractional digits',
examples: [
{input: 'trunc(5.95)', result: '5'},
{input: 'trunc(5.05)', result: '5'},
{input: 'trunc(5)', result: '5'},
{input: 'trunc(-5.5)', result: '-5'},
],
argumentType: 'number',
resultType: 'number',
fn: Math.trunc,
},
}

View File

@@ -27,9 +27,9 @@ export default class AccessorNode extends ParseNode {
} }
} }
reduce(scope, context){ reduce(scope, context){
let result = this.compile(scope); let result = this.compile(scope, context);
if (result instanceof AccessorNode){ if (result instanceof AccessorNode){
context.storeError({ if (context) context.storeError({
type: 'info', type: 'info',
message: `${result.toString()} not found, set to 0` message: `${result.toString()} not found, set to 0`
}); });

View File

@@ -1,6 +1,7 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js'; import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import functions from '/imports/parser/functions.js';
export default class CallNode extends ParseNode { export default class CallNode extends ParseNode {
constructor({functionName, args}) { constructor({functionName, args}) {
@@ -15,7 +16,7 @@ export default class CallNode extends ParseNode {
error: `${this.functionName} is not a function`, error: `${this.functionName} is not a function`,
context, context,
}); });
let args = castArgsToType({fn, scope, args: this.args, type: func.argumentType}); let args = castArgsToType({fn, scope, context, args: this.args, type: func.argumentType});
if (args.failed){ if (args.failed){
if (fn === 'reduce'){ if (fn === 'reduce'){
return new ErrorNode({ return new ErrorNode({
@@ -51,17 +52,8 @@ export default class CallNode extends ParseNode {
} }
} }
const functions = { function castArgsToType({fn, scope, context, args, type}){
'min': { let resolvedArgs = args.map(node => node[fn](scope, context))
comment: 'Returns the lowest of the given numbers',
argumentType: 'number',
resultType: 'number',
fn: Math.min,
},
}
function castArgsToType({fn, scope, args, type}){
let resolvedArgs = args.map(node => node[fn](scope))
let result = []; let result = [];
if (type === 'number'){ if (type === 'number'){
resolvedArgs.forEach(node => { resolvedArgs.forEach(node => {

View File

@@ -12,15 +12,15 @@ export default class IfNode extends ParseNode {
let {condition, consequent, alternative} = this; let {condition, consequent, alternative} = this;
return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}` return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}`
} }
resolve(fn, scope){ resolve(fn, scope, context){
let condition = this.condition[fn](scope); let condition = this.condition[fn](scope, context);
if (condition instanceof ConstantNode){ if (condition instanceof ConstantNode){
if (condition.value){ if (condition.value){
let consequent = this.consequent[fn](scope); let consequent = this.consequent[fn](scope, context);
consequent.inheritDetails([condition, this]); consequent.inheritDetails([condition, this]);
return this.consequent[fn](scope); return this.consequent[fn](scope);
} else { } else {
let alternative = this.alternative[fn](scope); let alternative = this.alternative[fn](scope, context);
alternative.inheritDetails([condition, this]); alternative.inheritDetails([condition, this]);
return alternative; return alternative;
} }

View File

@@ -6,19 +6,19 @@ export default class IndexNode extends ParseNode {
this.array = array; this.array = array;
this.index = index; this.index = index;
} }
resolve(fn, scope){ resolve(fn, scope, context){
let index = this.index[fn](scope); let index = this.index[fn](scope, context);
if (index.isInteger){ if (index.isInteger){
let selection = this.array.values[index.value - 1]; let selection = this.array.values[index.value - 1];
if (selection){ if (selection){
let result = selection[fn](scope); let result = selection[fn](scope, context);
result.inheritDetails([index, this]); result.inheritDetails([index, this]);
return result; return result;
} }
} }
return new IndexNode({ return new IndexNode({
array: this.array[fn](scope), array: this.array[fn](scope, context),
index: this.index[fn](scope), index: this.index[fn](scope, context),
previousNodes: [this], previousNodes: [this],
}); });
} }

View File

@@ -9,9 +9,9 @@ export default class OperatorNode extends ParseNode {
this.fn = fn; this.fn = fn;
this.operator = operator; this.operator = operator;
} }
resolve(fn, scope){ resolve(fn, scope, context){
let leftNode = this.left[fn](scope); let leftNode = this.left[fn](scope, context);
let rightNode = this.right[fn](scope); let rightNode = this.right[fn](scope, context);
let left, right; let left, right;
if (leftNode.type !== 'number' || rightNode.type !== 'number'){ if (leftNode.type !== 'number' || rightNode.type !== 'number'){
return new OperatorNode({ return new OperatorNode({

View File

@@ -5,8 +5,8 @@ export default class ParenthesisNode extends ParseNode {
super(...arguments); super(...arguments);
this.content = content; this.content = content;
} }
resolve(fn, scope){ resolve(fn, scope, context){
let content = this.content[fn](scope); let content = this.content[fn](scope, context);
if ( if (
content.constructor.name === 'IfNode' || content.constructor.name === 'IfNode' ||
content.constructor.name === 'OperatorNode' content.constructor.name === 'OperatorNode'

View File

@@ -17,7 +17,6 @@ export default class RollArrayNode extends ParseNode {
return new ConstantNode({ return new ConstantNode({
value: total, value: total,
type: 'number', type: 'number',
previousNodes: [this],
}); });
} }
} }

View File

@@ -8,9 +8,9 @@ export default class RollNode extends ParseNode {
this.left = left; this.left = left;
this.right = right; this.right = right;
} }
compile(scope){ compile(scope, context){
let left = this.left.compile(scope); let left = this.left.compile(scope, context);
let right = this.right.compile(scope); let right = this.right.compile(scope, context);
return new RollNode({left, right, previousNodes: [this]}); return new RollNode({left, right, previousNodes: [this]});
} }
toString(){ toString(){
@@ -23,8 +23,8 @@ export default class RollNode extends ParseNode {
} }
} }
roll(scope, context){ roll(scope, context){
let left = this.left.reduce(scope); let left = this.left.reduce(scope, context);
let right = this.right.reduce(scope); let right = this.right.reduce(scope, context);
if (!left.isInteger){ if (!left.isInteger){
return new ErrorNode({ return new ErrorNode({
node: this, node: this,
@@ -56,7 +56,7 @@ export default class RollNode extends ParseNode {
} }
return new RollArrayNode({values}); return new RollArrayNode({values});
} }
reduce(scope){ reduce(scope, context){
return this.roll(scope).reduce(scope); return this.roll(scope, context).reduce(scope, context);
} }
} }

View File

@@ -1,6 +1,5 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default class SymbolNode extends ParseNode { export default class SymbolNode extends ParseNode {
constructor({name}){ constructor({name}){
@@ -19,23 +18,25 @@ export default class SymbolNode extends ParseNode {
type = typeof value; type = typeof value;
} }
if (type === 'string' || type === 'number' || type === 'boolean'){ if (type === 'string' || type === 'number' || type === 'boolean'){
return new ConstantNode({value, type, previousNodes: [this]}); return new ConstantNode({value, type});
} else if (type === 'undefined'){ } else if (type === 'undefined'){
return new SymbolNode({ return new SymbolNode({
name: this.name, name: this.name,
previousNodes: [this],
}); });
} else { } else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`); throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
} }
} }
reduce(scope){ reduce(scope, context){
let result = this.compile(scope); let result = this.compile(scope);
if (result instanceof SymbolNode){ if (result instanceof SymbolNode){
return new ErrorNode({ if (context) context.storeError({
node: result, type: 'info',
error: `${this.toString()} could not be resolved`, message: `${result.toString()} not found, set to 0`
previousNodes: [result], });
return new ConstantNode({
type: 'number',
value: 0,
}); });
} else { } else {
return result; return result;

View File

@@ -0,0 +1,49 @@
<template lang="html">
<div>
<div
v-for="fn in functions"
:key="fn.name"
class="mb-3"
>
<h3>{{ fn.name }}</h3>
<div class="my-2">
{{ fn.comment }}
</div>
<table>
<tr
v-for="example in fn.examples"
:key="example.input"
>
<td>
<code>{{ example.input }}</code>
</td>
<td>
<v-icon>arrow_right_alt</v-icon>
</td>
<td>
<code>{{ example.result }}</code>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import functions from '/imports/parser/functions.js';
export default {
computed:{
functions(){
let fns = [];
for (let name in functions){
let f = functions[name];
fns.push({name, ...f});
}
return fns;
}
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -2,7 +2,16 @@
<div class="layout column align-center justify-center pa-4"> <div class="layout column align-center justify-center pa-4">
<v-card style="width: 100%; max-width: 400px;"> <v-card style="width: 100%; max-width: 400px;">
<v-card-text> <v-card-text>
<v-text-field v-model="input" /> <v-text-field
v-model="input"
label="input"
/>
<v-btn
icon
@click="recompute"
>
<v-icon>refresh</v-icon>
</v-btn>
<v-textarea <v-textarea
v-model="output" v-model="output"
readonly readonly
@@ -33,15 +42,25 @@
readonly readonly
label="reduced" label="reduced"
/> />
<v-textarea
:value="contextJSON"
readonly
label="reduced"
/>
<function-reference />
</v-card-text> </v-card-text>
</v-card> </v-card>
</div> </div>
</template> </template>
<script> <script>
import Parser from '/imports/parser/parser.js'; import { parse, CompilationContext } from '/imports/parser/parser.js';
console.log(Parser); import FunctionReference from '/imports/ui/documentation/FunctionReference.vue';
console.log(parse);
export default { export default {
components: {
FunctionReference,
},
data(){return { data(){return {
input: null, input: null,
output: null, output: null,
@@ -50,22 +69,35 @@ export default {
rolled: null, rolled: null,
reduced: null, reduced: null,
reducedJson: null, reducedJson: null,
context: null,
}}, }},
computed: {
contextJSON(){
return JSON.stringify(this.context, null, 2);
}
},
watch: { watch: {
input(val){ input(){
this.recompute();
}
},
methods: {
recompute(){
let val = this.input;
this.output = this.compiled = this.string = ''; this.output = this.compiled = this.string = '';
let output = new Parser().feed(val).finish()[0]; let output = parse(val);
console.log(output); console.log(output);
this.output = JSON.stringify(output, null, 2); this.output = JSON.stringify(output, null, 2);
if (!output) return; if (!output) return;
this.string = output; this.string = output;
let scope = {strength: {value: 16}, hitpoints: {value: 32, currentValue: 8}, mouse: 3}; let scope = {strength: {value: 16}, hitpoints: {value: 32, currentValue: 8}, mouse: 3};
this.compiled = output.compile(scope); this.context = new CompilationContext();
this.rolled = output.roll(scope); this.compiled = output.compile(scope, this.context);
this.reduced = output.reduce(scope); this.rolled = this.compiled.roll(scope, this.context);
this.reduced = this.rolled.reduce(scope, this.context);
this.reducedJson = JSON.stringify(this.reduced, null, 2) this.reducedJson = JSON.stringify(this.reduced, null, 2)
} }
}, }
} }
</script> </script>