Added functions and ensured the context was being passed around correctly
This commit is contained in:
83
app/imports/parser/functions.js
Normal file
83
app/imports/parser/functions.js
Normal 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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`
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
49
app/imports/ui/documentation/FunctionReference.vue
Normal file
49
app/imports/ui/documentation/FunctionReference.vue
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user