| /* |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| "use strict"; |
| |
| const Basic = {}; |
| |
| Basic.NumberApply = function(state) |
| { |
| // I'd call this arguments but we're in strict mode. |
| let parameters = this.parameters.map(value => value.evaluate(state)); |
| |
| return state.getValue(this.name, parameters.length).apply(state, parameters); |
| }; |
| |
| Basic.Variable = function(state) |
| { |
| let parameters = this.parameters.map(value => value.evaluate(state)); |
| |
| return state.getValue(this.name, parameters.length).leftApply(state, parameters); |
| } |
| |
| Basic.Const = function(state) |
| { |
| return this.value; |
| } |
| |
| Basic.NumberPow = function(state) |
| { |
| return Math.pow(this.left.evaluate(state), this.right.evaluate(state)); |
| } |
| |
| Basic.NumberMul = function(state) |
| { |
| return this.left.evaluate(state) * this.right.evaluate(state); |
| } |
| |
| Basic.NumberDiv = function(state) |
| { |
| return this.left.evaluate(state) / this.right.evaluate(state); |
| } |
| |
| Basic.NumberNeg = function(state) |
| { |
| return -this.term.evaluate(state); |
| } |
| |
| Basic.NumberAdd = function(state) |
| { |
| return this.left.evaluate(state) + this.right.evaluate(state); |
| } |
| |
| Basic.NumberSub = function(state) |
| { |
| return this.left.evaluate(state) - this.right.evaluate(state); |
| } |
| |
| Basic.StringVar = function(state) |
| { |
| let value = state.stringValues.get(this.name); |
| if (value == null) |
| state.abort("Could not find string variable " + this.name); |
| return value; |
| } |
| |
| Basic.Equals = function(state) |
| { |
| return this.left.evaluate(state) == this.right.evaluate(state); |
| } |
| |
| Basic.NotEquals = function(state) |
| { |
| return this.left.evaluate(state) != this.right.evaluate(state); |
| } |
| |
| Basic.LessThan = function(state) |
| { |
| return this.left.evaluate(state) < this.right.evaluate(state); |
| } |
| |
| Basic.GreaterThan = function(state) |
| { |
| return this.left.evaluate(state) > this.right.evaluate(state); |
| } |
| |
| Basic.LessEqual = function(state) |
| { |
| return this.left.evaluate(state) <= this.right.evaluate(state); |
| } |
| |
| Basic.GreaterEqual = function(state) |
| { |
| return this.left.evaluate(state) >= this.right.evaluate(state); |
| } |
| |
| Basic.GoTo = function*(state) |
| { |
| state.nextLineNumber = this.target; |
| } |
| |
| Basic.GoSub = function*(state) |
| { |
| state.subStack.push(state.nextLineNumber); |
| state.nextLineNumber = this.target; |
| } |
| |
| Basic.Def = function*(state) |
| { |
| state.validate(!state.values.has(this.name), "Cannot redefine function"); |
| state.values.set(this.name, new NumberFunction(this.parameters, this.expression)); |
| } |
| |
| Basic.Let = function*(state) |
| { |
| this.variable.evaluate(state).assign(this.expression.evaluate(state)); |
| } |
| |
| Basic.If = function*(state) |
| { |
| if (this.condition.evaluate(state)) |
| state.nextLineNumber = this.target; |
| } |
| |
| Basic.Return = function*(state) |
| { |
| this.validate(state.subStack.length, "Not in a subroutine"); |
| this.nextLineNumber = state.subStack.pop(); |
| } |
| |
| Basic.Stop = function*(state) |
| { |
| state.nextLineNumber = null; |
| } |
| |
| Basic.On = function*(state) |
| { |
| let index = this.expression.evaluate(state); |
| if (!(index >= 1) || !(index <= this.targets.length)) |
| state.abort("Index out of bounds: " + index); |
| this.nextLineNumber = this.targets[Math.floor(index)]; |
| } |
| |
| Basic.For = function*(state) |
| { |
| let sideState = state.getSideState(this); |
| sideState.variable = state.getValue(this.variable, 0).leftApply(state, []); |
| sideState.initialValue = this.initial.evaluate(state); |
| sideState.limitValue = this.limit.evaluate(state); |
| sideState.stepValue = this.step.evaluate(state); |
| sideState.variable.assign(sideState.initialValue); |
| sideState.shouldStop = function() { |
| return (sideState.variable.value - sideState.limitValue) * Math.sign(sideState.stepValue) > 0; |
| }; |
| if (sideState.shouldStop()) |
| this.nextLineNumber = this.target.lineNumber + 1; |
| } |
| |
| Basic.Next = function*(state) |
| { |
| let sideState = state.getSideState(this.target); |
| sideState.variable.assign(sideState.variable.value + sideState.stepValue); |
| if (sideState.shouldStop()) |
| return; |
| state.nextLineNumber = this.target.lineNumber + 1; |
| } |
| |
| Basic.Next.isBlockEnd = true; |
| |
| Basic.Print = function*(state) |
| { |
| let string = ""; |
| for (let item of this.items) { |
| switch (item.kind) { |
| case "comma": |
| while (string.length % 14) |
| string += " "; |
| break; |
| case "tab": { |
| let value = item.value.evaluate(state); |
| value = Math.max(Math.round(value), 1); |
| while (string.length % value) |
| string += " "; |
| break; |
| } |
| case "string": |
| case "number": |
| string += item.value.evaluate(state); |
| break; |
| default: |
| throw new Error("Bad item kind: " + item.kind); |
| } |
| } |
| |
| yield {kind: "output", string}; |
| } |
| |
| Basic.Input = function*(state) |
| { |
| let results = yield {kind: "input", numItems: this.items.length}; |
| state.validate(results != null && results.length == this.items.length, "Input did not get the right number of items"); |
| for (let i = 0; i < results.length; ++i) |
| this.items[i].evaluate(state).assign(results[i]); |
| } |
| |
| Basic.Read = function*(state) |
| { |
| for (let item of this.items) { |
| state.validate(state.dataIndex < state.program.data.length, "Attempting to read past the end of data"); |
| item.assign(state.program.data[state.dataIndex++]); |
| } |
| } |
| |
| Basic.Restore = function*(state) |
| { |
| state.dataIndex = 0; |
| } |
| |
| Basic.Dim = function*(state) |
| { |
| for (let item of this.items) { |
| state.validate(!state.values.has(item.name), "Variable " + item.name + " already exists"); |
| state.validate(item.bounds.length, "Dim statement is for arrays"); |
| state.values.set(item.name, new NumberArray(item.bounds.map(bound => bound + 1))); |
| } |
| } |
| |
| Basic.Randomize = function*(state) |
| { |
| state.rng = createRNGWithRandomSeed(); |
| } |
| |
| Basic.End = function*(state) |
| { |
| state.nextLineNumber = null; |
| } |
| |
| Basic.End.isBlockEnd = true; |
| |
| Basic.Program = function* programGenerator(state) |
| { |
| state.validate(state.program == this, "State must match program"); |
| let maxLineNumber = Math.max(...this.statements.keys()); |
| while (state.nextLineNumber != null) { |
| state.validate(state.nextLineNumber <= maxLineNumber, "Went out of bounds of the program"); |
| let statement = this.statements.get(state.nextLineNumber++); |
| if (statement == null || statement.process == null) |
| continue; |
| state.statement = statement; |
| yield* statement.process(state); |
| } |
| } |
| |