/* * 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); } }