UNPKG

algebrite

Version:

Computer Algebra System in Coffeescript

1,189 lines (959 loc) 57.1 kB
#jmp_buf stop_return, draw_stop_return # s is a string here stop = (s) -> #if (draw_flag == 2) # longjmp(draw_stop_return, 1) #else errorMessage += "Stop: " errorMessage += s #debugger message = errorMessage errorMessage = '' tos = 0 throw new Error(message) #longjmp(stop_return, 1) # Figuring out dependencies is key to automatically # generating a method signature when generating JS code # from algebrite scripts. # This is important because the user can keep using normal Algebrite # scripting without special notations. # Basically the process consists of figuring out # the "ground variables" that are needed to compute each variable. # Now there are two ways of doing this: # * at parse time # * after running the scripts # Doing it at parse time means that we can't track simplifications # canceling-out some variables for example. But on the other side # it's very quick and the user can somehow see what the signature is # going to look like (assuming tha code is rather simple), or anyways # is going to easily make sense of the generated signature. # Doing it after execution on the other hand would allow us to see # if some variable cancel-out. But if variables cancel out then # they might do so according to some run-time behaviour that the user # might struggle to keep track of. # So the effort for the user to make sense of the signature in the first case # is similar to the effort of leeping tab of types in a typed language. # While in the second case the effort is similar to running the # code and simplifications in her head. test_dependencies = -> do_clearall() testResult = findDependenciesInScript('1') if testResult[0] == "All local dependencies: . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: " and testResult[1] == "1" and testResult[2] == "" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = x+1\n g = f\n h = g\n f = g') if testResult[0] == "All local dependencies: variable f depends on: x, g, ; variable g depends on: f, ; variable h depends on: g, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: x, ; f --> g --> ... then f again, variable g depends on: x, ; g --> f --> ... then g again, variable h depends on: x, ; h --> g --> f --> ... then g again, " and testResult[1] == "" and testResult[2] == "// f is part of a cyclic dependency, no code generated.\n// g is part of a cyclic dependency, no code generated.\n// h is part of a cyclic dependency, no code generated." else console.log "fail dependency test. expected: " + testResult do_clearall() if findDependenciesInScript('f = x+1\n g = f + y\n h = g')[0] == "All local dependencies: variable f depends on: x, ; variable g depends on: f, y, ; variable h depends on: g, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: x, ; variable g depends on: x, y, ; variable h depends on: x, y, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() if findDependenciesInScript('g = h(x,y)')[0] == "All local dependencies: variable g depends on: h, x, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable g depends on: h, x, y, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() if findDependenciesInScript('f(x,y) = k')[0] == "All local dependencies: variable f depends on: 'x, 'y, k, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: 'x, 'y, k, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() if findDependenciesInScript('x = z\n f(x,y) = k')[0] == "All local dependencies: variable x depends on: z, ; variable f depends on: 'x, 'y, k, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: z, ; variable f depends on: 'x, 'y, k, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() if findDependenciesInScript('x = z\n g = f(x,y)')[0] == "All local dependencies: variable x depends on: z, ; variable g depends on: f, x, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: z, ; variable g depends on: f, z, y, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() if findDependenciesInScript('x = 1\n x = y\n x = z')[0] == "All local dependencies: variable x depends on: y, z, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: y, z, ; " console.log "ok dependency test" else console.log "fail dependency test" do_clearall() testResult = findDependenciesInScript('x = y*y') if testResult[0] == "All local dependencies: variable x depends on: y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: y, ; " and testResult[1] == "" and testResult[2] == "x = function (y) { return ( Math.pow(y, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = -sqrt(2)/2') if testResult[0] == "All local dependencies: variable x depends on: ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: ; " and testResult[1] == "" and testResult[2] == "x = -1/2*Math.pow(2, (1/2));" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = 2^(1/2-a)*2^a/10') if testResult[0] == "All local dependencies: variable x depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: a, ; " and testResult[1] == "" and testResult[2] == "x = 1/10*Math.pow(2, (1/2));" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = rationalize(t*y/(t+y)+2*t^2*y*(2*t+y)^(-2))') if testResult[0] == "All local dependencies: variable x depends on: t, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: t, y, ; " and testResult[1] == "" and testResult[2] == "x = function (t, y) { return ( t*y*(6*Math.pow(t, 2) + Math.pow(y, 2) + 6*t*y) / ((t + y)*Math.pow((2*t + y), 2)) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = abs((a+i*b)/(c+i*d))') if testResult[0] == "All local dependencies: variable x depends on: a, b, c, d, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: a, b, c, d, ; " and testResult[1] == "" and testResult[2] == "x = function (a, b, c, d) { return ( Math.pow((Math.pow(a, 2) + Math.pow(b, 2)), (1/2)) / (Math.pow((Math.pow(c, 2) + Math.pow(d, 2)), (1/2))) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = sin(1/10)^2 + cos(1/10)^2 + y') if testResult[0] == "All local dependencies: variable x depends on: y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: y, ; " and testResult[1] == "" and testResult[2] == "x = function (y) { return ( 1 + y ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('x = sin(1/10)^2 + cos(1/10)^2') if testResult[0] == "All local dependencies: variable x depends on: ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable x depends on: ; " and testResult[1] == "" and testResult[2] == "x = 1;" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f(x) = x * x') if testResult[0] == "All local dependencies: variable f depends on: 'x, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: 'x, ; " and testResult[1] == "" and testResult[2] == "f = function (x) { return ( x*x ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f(x) = x * x + g(y)') if testResult[0] == "All local dependencies: variable f depends on: 'x, g, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: 'x, g, y, ; " and testResult[1] == "" and testResult[2] == "f = function (g, y, x) { return ( g(y) + Math.pow(x, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('y = 2\nf(x) = x * x + g(y)') if testResult[0] == "All local dependencies: variable y depends on: ; variable f depends on: 'x, g, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable y depends on: ; variable f depends on: 'x, g, ; " and testResult[1] == "" and testResult[2] == "y = 2;\nf = function (g, x) { return ( g(2) + Math.pow(x, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('g(x) = x + 2\ny = 2\nf(x) = x * x + g(y)') if testResult[0] == "All local dependencies: variable g depends on: 'x, ; variable y depends on: ; variable f depends on: 'x, g, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable g depends on: 'x, ; variable y depends on: ; variable f depends on: 'x, ; " and testResult[1] == "" and testResult[2] == "g = function (x) { return ( 2 + x ); }\ny = 2;\nf = function (x) { return ( 4 + Math.pow(x, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('g(x) = x + 2\nf(x) = x * x + g(y)') if testResult[0] == "All local dependencies: variable g depends on: 'x, ; variable f depends on: 'x, g, y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable g depends on: 'x, ; variable f depends on: 'x, y, ; " and testResult[1] == "" and testResult[2] == "g = function (x) { return ( 2 + x ); }\nf = function (y, x) { return ( 2 + y + Math.pow(x, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() ### testResult = findDependenciesInScript('g(x) = f(x)\nf(x)=g(x)') if testResult[0] == "All local dependencies: variable g depends on: 'x, f, x, ; variable f depends on: 'x, g, x, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable g depends on: 'x, ; g --> f --> ... then g again, variable f depends on: 'x, x, ; f --> g --> ... then f again, " and testResult[1] == "" and testResult[2] == "// g is part of a cyclic dependency, no code generated.\n// f is part of a cyclic dependency, no code generated." console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() ### testResult = findDependenciesInScript('f = roots(a*x^2 + b*x + c, x)') if testResult[0] == "All local dependencies: variable f depends on: a, b, c, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: a, b, c, ; " and testResult[1] == "" and testResult[2] == "f = function (a, b, c) { return ( [-1/2*(Math.pow((Math.pow(b, 2) / (Math.pow(a, 2)) - 4*c / a), (1/2)) + b / a),1/2*(Math.pow((Math.pow(b, 2) / (Math.pow(a, 2)) - 4*c / a), (1/2)) - b / a)] ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = roots(a*x^2 + b*x + c)') if testResult[0] == "All local dependencies: variable f depends on: a, b, c, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: a, b, c, ; " and testResult[1] == "" and testResult[2] == "f = function (a, b, c) { return ( [-1/2*(Math.pow((Math.pow(b, 2) / (Math.pow(a, 2)) - 4*c / a), (1/2)) + b / a),1/2*(Math.pow((Math.pow(b, 2) / (Math.pow(a, 2)) - 4*c / a), (1/2)) - b / a)] ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = roots(integral(a*x + b))') if testResult[0] == "All local dependencies: variable f depends on: a, b, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: a, b, ; " and testResult[1] == "" and testResult[2] == "f = function (a, b) { return ( [0,-2*b / a] ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = roots(defint(a*x + y,y,0,1))') if testResult[0] == "All local dependencies: variable f depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: a, ; " and testResult[1] == "" and testResult[2] == "f = function (a) { return ( -1 / (2*a) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = roots(defint(a*x + y + z,y,0,1, z, 0, 1))') if testResult[0] == "All local dependencies: variable f depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: a, ; " and testResult[1] == "" and testResult[2] == "f = function (a) { return ( -1 / a ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = defint(2*x - 3*y,x,0,2*y)') if testResult[0] == "All local dependencies: variable f depends on: y, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: y, ; " and testResult[1] == "" and testResult[2] == "f = function (y) { return ( -2*Math.pow(y, 2) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('f = defint(12 - x^2 - (y^2)/2,x,0,2,y,0,3)') if testResult[0] == "All local dependencies: variable f depends on: ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable f depends on: ; " and testResult[1] == "" and testResult[2] == "f = 55;" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # this example checks that functions are not meddled with, # in particular that in the function body, the variables # bound by the parameters remain "separate" from previous # variables with the same name. testResult = findDependenciesInScript('a = 2\nf(a) = a+1+b') if testResult[0] == "All local dependencies: variable a depends on: ; variable f depends on: 'a, b, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: ; variable f depends on: 'a, b, ; " and testResult[1] == "" and testResult[2] == "a = 2;\nf = function (a, b) { return ( 1 + a + b ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # similar as test above but this time we are not # defining a function, so things are a bit different. testResult = findDependenciesInScript('a = 2\nf = a+1') if testResult[0] == "All local dependencies: variable a depends on: ; variable f depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: ; variable f depends on: ; " and testResult[1] == "" and testResult[2] == "a = 2;\nf = 3;" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # similar as test above but this time we do a # trick with the quote to see whether we # get confused with the indirection testResult = findDependenciesInScript('a := b\nf = a+1') if testResult[0] == "All local dependencies: variable a depends on: b, ; variable f depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: b, ; variable f depends on: b, ; " and testResult[1] == "" and testResult[2] == "a = function (b) { return ( b ); }\nf = function (b) { return ( 1 + b ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # another tricky case of indirection through quote testResult = findDependenciesInScript('a := b\nf(a) = a+1') if testResult[0] == "All local dependencies: variable a depends on: b, ; variable f depends on: 'a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: b, ; variable f depends on: 'a, ; " and testResult[1] == "" and testResult[2] == "a = function (b) { return ( b ); }\nf = function (a) { return ( 1 + a ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # reassignment testResult = findDependenciesInScript('b = 1\nb=a+b+c') if testResult[0] == "All local dependencies: variable b depends on: a, c, ; . Symbols with reassignments: b, . Symbols in expressions without assignments: . All dependencies recursively: variable b depends on: a, c, ; " and testResult[1] == "" and testResult[2] == "b = function (a, c) { return ( 1 + a + c ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # reassignment testResult = findDependenciesInScript('a = a+1') if testResult[0] == "All local dependencies: variable a depends on: ; . Symbols with reassignments: a, . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: ; " and testResult[1] == "" and testResult[2] == "" and testResult[5] == "Error: Stop: recursive evaluation of symbols: a -> a" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() # reassignment testResult = computeDependenciesFromAlgebra('pattern(a,b)\nc= d\na=a+1') if testResult.affectsVariables.length is 3 and testResult.affectsVariables.indexOf("c") != -1 and testResult.affectsVariables.indexOf("a") != -1 and testResult.affectsVariables.indexOf("PATTERN_DEPENDENCY") != -1 and testResult.affectedBy.length is 3 and testResult.affectedBy.indexOf("d") != -1 and testResult.affectedBy.indexOf("a") != -1 and testResult.affectedBy.indexOf("PATTERN_DEPENDENCY") != -1 console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = computeDependenciesFromAlgebra('PCA(M) = eig(Mᵀ⋅M)') if testResult.affectsVariables.length is 1 and testResult.affectsVariables.indexOf("PCA") != -1 and testResult.affectsVariables.indexOf("PATTERN_DEPENDENCY") == -1 and testResult.affectedBy.length is 1 and testResult.affectedBy.indexOf("PATTERN_DEPENDENCY") != -1 console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = computeDependenciesFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') if testResult.affectsVariables.length is 1 and testResult.affectsVariables.indexOf("PATTERN_DEPENDENCY") != -1 and testResult.affectedBy.length is 1 and testResult.affectedBy.indexOf("PATTERN_DEPENDENCY") != -1 console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('a = b\nf = a+1') if testResult[0] == "All local dependencies: variable a depends on: b, ; variable f depends on: a, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable a depends on: b, ; variable f depends on: b, ; " and testResult[1] == "" and testResult[2] == "a = function (b) { return ( b ); }\nf = function (b) { return ( 1 + b ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() testResult = findDependenciesInScript('PCA(M) = eig(cov(M))') if testResult[0] == "All local dependencies: variable PCA depends on: 'M, ; . Symbols with reassignments: . Symbols in expressions without assignments: . All dependencies recursively: variable PCA depends on: 'M, ; " and testResult[1] == "" and testResult[2] == "PCA = function (M) { return ( eig(cov(M)) ); }" console.log "ok dependency test" else console.log "fail dependency test. expected: " + testResult do_clearall() computeResultsAndJavaScriptFromAlgebra('PCA(M) = eig(Mᵀ⋅M)') testResult = run('symbolsinfo') if testResult.indexOf('AVOID_BINDING_TO_EXTERNAL_SCOPE_VALUE') != -1 console.log "fail dependency tests. found AVOID_BINDING_TO_EXTERNAL_SCOPE_VALUE" else console.log "ok dependency test" do_clearall() # this checks error handling in case of pattern and syntax error # picked up during scanning. computeResultsAndJavaScriptFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') computeResultsAndJavaScriptFromAlgebra('simplify(') testResult = computeResultsAndJavaScriptFromAlgebra('PCA = Mᵀ·M') if testResult.code == "PCA = function (M) { return ( cov(M) ); }" and testResult.latexResult == "$$PCA(M) = cov(M)$$" and testResult.result == "$$PCA(M) = cov(M)$$" and testResult.dependencyInfo.affectedBy[0] == "M" and testResult.dependencyInfo.affectedBy[1] == "PATTERN_DEPENDENCY" and testResult.dependencyInfo.affectsVariables.length == 1 and testResult.dependencyInfo.affectsVariables[0] == "PCA" console.log "ok dependency test" else console.log "fail dependency tests. Error handling 1" console.log testResult return do_clearall() console.log "checking hit/miss patterns =======================" resetCache() original_CACHE_HITSMISS_DEBUGS = CACHE_HITSMISS_DEBUGS CACHE_HITSMISS_DEBUGS = true # first two should miss because caches are completely empty # note that each call produces two misses because one is from # "findDependenciesInScript" itself and one is from "run" # after these two, all the others should hit a cache computeResultsAndJavaScriptFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') computeResultsAndJavaScriptFromAlgebra('PCA = Mᵀ·M') if totalAllCachesHits() != 0 console.log "test hecking hit/miss patterns fail, got: " + totalAllCachesHits() + " instead of 0" clearAlgebraEnvironment() console.log "\nclearAlgebraEnvironment()" currentStateHash = getStateHash() console.log "state hash after nclearAlgebraEnvironment: " + currentStateHash console.log "\n" computeResultsAndJavaScriptFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') computeResultsAndJavaScriptFromAlgebra('PCA = Mᵀ·M') if totalAllCachesHits() != 2 console.log "test hecking hit/miss patterns fail, got: " + totalAllCachesHits() + " instead of 2" clearAlgebraEnvironment() console.log "\nclearAlgebraEnvironment()" currentStateHash = getStateHash() console.log "state hash after nclearAlgebraEnvironment: " + currentStateHash console.log "\n" computeResultsAndJavaScriptFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') computeResultsAndJavaScriptFromAlgebra('PCA = Mᵀ·M') CACHE_HITSMISS_DEBUGS = original_CACHE_HITSMISS_DEBUGS if totalAllCachesHits() != 4 console.log "test hecking hit/miss patterns fail, got: " + totalAllCachesHits() + " instead of 4" clearAlgebraEnvironment() console.log "\nclearAlgebraEnvironment()" currentStateHash = getStateHash() console.log "state hash after nclearAlgebraEnvironment: " + currentStateHash console.log "\n" computeResultsAndJavaScriptFromAlgebra('pattern(a_ᵀ⋅a_, cov(a_))') # note that in case of syntax error, "last" is not updated. # so if new symbols have been scanned yet, then they are not created # and the cache should hit # TODO ideally a scan resulting in an error should produce no new # symbols in the symbol table at all computeResultsAndJavaScriptFromAlgebra('simplify(') currentStateHash = getStateHash() console.log "state hash after syntax error: " + currentStateHash computeResultsAndJavaScriptFromAlgebra('PCA = Mᵀ·M') CACHE_HITSMISS_DEBUGS = original_CACHE_HITSMISS_DEBUGS if totalAllCachesHits() != 6 console.log "test hecking hit/miss patterns fail, got: " + totalAllCachesHits() + " instead of 6" if totalAllCachesMisses() != 5 console.log "test hecking hit/miss patterns fail, got: " + totalAllCachesMisses() + " instead of 5" resetCache() console.log "end of checking hit/miss patterns =======================" do_clearall() testResult = computeResultsAndJavaScriptFromAlgebra('x + x + x') if testResult.code == "" and testResult.latexResult == "$$3x$$" and testResult.result == "$$3x$$" and testResult.dependencyInfo.affectedBy.length == 2 and testResult.dependencyInfo.affectedBy[0] == "x" and testResult.dependencyInfo.affectedBy[1] == "PATTERN_DEPENDENCY" and testResult.dependencyInfo.affectsVariables.length == 0 console.log "ok dependency test" else console.log "fail dependency tests" do_clearall() computeResultsAndJavaScriptFromAlgebra('x = y + 2') testResult = computeResultsAndJavaScriptFromAlgebra('x + x + x') if testResult.code == "" and testResult.latexResult == "$$3y+6$$" and testResult.result == "$$3y+6$$" and testResult.dependencyInfo.affectedBy.length == 2 and testResult.dependencyInfo.affectedBy[0] == "x" and testResult.dependencyInfo.affectedBy[1] == "PATTERN_DEPENDENCY" and testResult.dependencyInfo.affectsVariables.length == 0 console.log "ok dependency test" else console.log "fail dependency tests" do_clearall() console.log "-- done dependency tests" do_clearall() # if we just want to compute the dependencies, we don't need to do # anything costly, we don't "run" the code and we don't simplify # the code. Just finding the plain dependencies # TODO change the name of this function, as it doesn't just find the # dependencies. It also runs it and generates the JS code. findDependenciesInScript = (stringToBeParsed, dontGenerateCode) -> if DEBUG then console.log "stringToBeParsed: " + stringToBeParsed timeStartFromAlgebra = new Date().getTime() if CACHE_DEBUGS or CACHE_HITSMISS_DEBUGS or TIMING_DEBUGS console.log " --------- findDependenciesInScript input: " + stringToBeParsed + " at: " + (new Date()) currentStateHash = getStateHash() console.log "state hash: " + currentStateHash inited = true codeGen = true symbolsDependencies = {} symbolsHavingReassignments = [] symbolsInExpressionsWithoutAssignments = [] patternHasBeenFound = false indexOfPartRemainingToBeParsed = 0 allReturnedPlainStrings = "" allReturnedLatexStrings = "" n = 0 # we are going to store the dependencies _of the block as a whole_ # so all affected variables in the whole block are lumped # together, and same for the variable that affect those, we # lump them all together. dependencyInfo = affectsVariables: [] affectedBy: [] stringToBeRun = stringToBeParsed if ENABLE_CACHING and stringToBeRun != "clearall" currentStateHash = getStateHash() cacheKey = currentStateHash + " stringToBeRun: " + stringToBeRun + " - " + dontGenerateCode if CACHE_DEBUGS then console.log "cached_findDependenciesInScript key: " + cacheKey possiblyCached = cached_findDependenciesInScript.get(cacheKey) if possiblyCached? if CACHE_HITSMISS_DEBUGS then console.log "cached_findDependenciesInScript hit on " + stringToBeRun unfreeze(possiblyCached) # return the output string if TIMING_DEBUGS totalTime = new Date().getTime() - timeStartFromAlgebra console.log "findDependenciesInScript input: " + stringToBeRun + " time: " + totalTime + "ms, saved " + (possiblyCached[possiblyCached.length-5] - totalTime) + "ms due to cache hit" return [ possiblyCached[possiblyCached.length - 7], possiblyCached[possiblyCached.length - 6], possiblyCached[possiblyCached.length - 5], possiblyCached[possiblyCached.length - 4], possiblyCached[possiblyCached.length - 3], possiblyCached[possiblyCached.length - 2], possiblyCached[possiblyCached.length - 1] ] else if CACHE_HITSMISS_DEBUGS then console.log "cached_findDependenciesInScript miss on: " + stringToBeRun if TIMING_DEBUGS cacheMissPenalty = (new Date().getTime() - timeStartFromAlgebra) # parse the input. This collects the # dependency information while (1) try errorMessage = "" check_stack() if DEBUG then console.log "findDependenciesInScript: scanning" n = scan(stringToBeParsed.substring(indexOfPartRemainingToBeParsed)) if DEBUG then console.log "scanned" pop() check_stack() catch error if PRINTOUTRESULT then console.log error errorMessage = error + "" #debugger reset_after_error() break if (n == 0) break indexOfPartRemainingToBeParsed += n testableString = "" # print out all local dependencies as collected by this # parsing pass if DEBUG then console.log "all local dependencies ----------------" testableString += "All local dependencies: " for key, value of symbolsDependencies if DEBUG then console.log "variable " + key + " depends on: " dependencyInfo.affectsVariables.push key testableString += " variable " + key + " depends on: " for i in value if DEBUG then console.log " " + i if i[0] != "'" dependencyInfo.affectedBy.push i testableString += i + ", " testableString += "; " testableString += ". " # print out the symbols with re-assignments: if DEBUG then console.log "Symbols with reassignments ----------------" testableString += "Symbols with reassignments: " for key in symbolsHavingReassignments if dependencyInfo.affectedBy.indexOf(key) == -1 dependencyInfo.affectedBy.push key testableString += key + ", " testableString += ". " # print out the symbols that appear in expressions without assignments if DEBUG then console.log "Symbols in expressions without assignments ----------------" testableString += "Symbols in expressions without assignments: " for key in symbolsInExpressionsWithoutAssignments if dependencyInfo.affectedBy.indexOf(key) == -1 dependencyInfo.affectedBy.push key testableString += key + ", " testableString += ". " # ALL Algebrite code is affected by any pattern changing dependencyInfo.affectedBy.push "PATTERN_DEPENDENCY" if patternHasBeenFound dependencyInfo.affectsVariables.push "PATTERN_DEPENDENCY" testableString += " - PATTERN_DEPENDENCY inserted - " # print out all global dependencies as collected by this # parsing pass if DEBUG then console.log "All dependencies recursively ----------------" testableString += "All dependencies recursively: " scriptEvaluation = ["",""] generatedCode = "" readableSummaryOfGeneratedCode = "" if errorMessage == "" and !dontGenerateCode try allReturnedPlainStrings = "" allReturnedLatexStrings = "" scriptEvaluation = run(stringToBeParsed, true) allReturnedPlainStrings = "" allReturnedLatexStrings = "" catch error if PRINTOUTRESULT then console.log error errorMessage = error + "" #debugger init() if errorMessage == "" for key of symbolsDependencies codeGen = true if DEBUG then console.log " variable " + key + " is: " + get_binding(usr_symbol(key)).toString() codeGen = false if DEBUG then console.log " variable " + key + " depends on: " testableString += " variable " + key + " depends on: " recursedDependencies = [] variablesWithCycles = [] cyclesDescriptions = [] recursiveDependencies key, recursedDependencies, [], variablesWithCycles, [], cyclesDescriptions for i in variablesWithCycles if DEBUG then console.log " --> cycle through " + i for i in recursedDependencies if DEBUG then console.log " " + i testableString += i + ", " testableString += "; " for i in cyclesDescriptions testableString += " " + i + ", " if DEBUG then console.log " code generation:" + key + " is: " + get_binding(usr_symbol(key)).toString() # we really want to make an extra effort # to generate simplified code, so # run a "simplify" on the content of each # variable that we are generating code for. # Note that the variable # will still point to un-simplified structures, # we only simplify the generated code. push get_binding(usr_symbol(key)) # Since we go and simplify all variables we meet, # we have to replace each variable passed as a parameter # with something entirely new, so that there is no chance # that it might evoke previous values in the external scope # as in this case: # a = 2 # f(a) = a+1+b # we don't want 'a' in the body of f to be simplified to 2 # There are two cases: 1) the variable actually was already in # the symble table, in which case there is goong to be this new # one prepended with AVOID_BINDING_TO_EXTERNAL_SCOPE_VALUE, and # we'll have to remove up this variable later. # OR 2) the variable wasn't already in the symbol table, in which # case we directly create this one, which means that we'll have # to rename it later to the correct name without the prepended # part. replacementsFrom = [] replacementsTo = [] for eachDependency in recursedDependencies if eachDependency[0] == "'" deQuotedDep = eachDependency.substring(1) originalUserSymbol = usr_symbol(deQuotedDep) newUserSymbol = usr_symbol("AVOID_BINDING_TO_EXTERNAL_SCOPE_VALUE"+deQuotedDep) replacementsFrom.push originalUserSymbol replacementsTo.push newUserSymbol push(originalUserSymbol) push(newUserSymbol) subst() if DEBUG then console.log "after substitution: " + stack[tos-1] try simplifyForCodeGeneration() catch error if PRINTOUTRESULT then console.log error errorMessage = error + "" #debugger init() for indexOfEachReplacement in [0...replacementsFrom.length] #console.log "replacing back " + replacementsTo[indexOfEachReplacement] + " into: " + replacementsFrom[indexOfEachReplacement] push(replacementsTo[indexOfEachReplacement]) push(replacementsFrom[indexOfEachReplacement]) subst() clearRenamedVariablesToAvoidBindingToExternalScope() if errorMessage == "" toBePrinted = pop() # we have to get all the variables used on the right side # here. I.e. to print the arguments it's better to look at the # actual method body after simplification. userVariablesMentioned = [] collectUserSymbols(toBePrinted, userVariablesMentioned) allReturnedPlainStrings = "" allReturnedLatexStrings = "" codeGen = true generatedBody = toBePrinted.toString() codeGen = false bodyForReadableSummaryOfGeneratedCode = toBePrinted.toString() if variablesWithCycles.indexOf(key) != -1 generatedCode += "// " + key + " is part of a cyclic dependency, no code generated." readableSummaryOfGeneratedCode += "#" + key + " is part of a cyclic dependency, no code generated." else ### # using this paragraph instead of the following one # creates methods signatures that # are slightly less efficient # i.e. variables compare even if they are # simplified away. # In theory these signatures are more stable, but # in practice signatures vary quite a bit anyways # depending on previous assignments for example, # so it's unclear whether going for stability # is sensible at all.. if recursedDependencies.length != 0 parameters = "(" for i in recursedDependencies if i.indexOf("'") != 0 parameters += i + ", " else if recursedDependencies.indexOf(i.substring(1)) == -1 parameters += i.substring(1) + ", " ### # remove all native functions from the # parameters as well. userVariablesMentioned = userVariablesMentioned.filter (x) -> predefinedSymbolsInGlobalScope_doNotTrackInDependencies.indexOf(x + "") == -1 if userVariablesMentioned.length != 0 parameters = "(" for i in userVariablesMentioned if i.printname != key parameters += i.printname + ", " # eliminate the last ", " for printout clarity parameters = parameters.replace /, $/gm , "" parameters += ")" generatedCode += key + " = function " + parameters + " { return ( " + generatedBody + " ); }" readableSummaryOfGeneratedCode += key + parameters + " = " + bodyForReadableSummaryOfGeneratedCode else generatedCode += key + " = " + generatedBody + ";" readableSummaryOfGeneratedCode += key + " = " + bodyForReadableSummaryOfGeneratedCode generatedCode += "\n" readableSummaryOfGeneratedCode += "\n" if DEBUG then console.log " " + generatedCode # eliminate the last new line generatedCode = generatedCode.replace /\n$/gm , "" readableSummaryOfGeneratedCode = readableSummaryOfGeneratedCode.replace /\n$/gm , "" # cleanup symbolsDependencies = {} symbolsHavingReassignments = [] patternHasBeenFound = false symbolsInExpressionsWithoutAssignments = [] if DEBUG then console.log "testable string: " + testableString if TIMING_DEBUGS console.log "findDependenciesInScript time for: " + stringToBeRun + " : "+ ((new Date().getTime()) - timeStartFromAlgebra) + "ms" if ENABLE_CACHING and stringToBeRun != "clearall" and errorMessage == "" frozen = freeze() toBeFrozen = [ frozen[0], frozen[1], frozen[2], frozen[3], frozen[4], frozen[5], (new Date().getTime() - timeStartFromAlgebra), testableString, scriptEvaluation[0], generatedCode, readableSummaryOfGeneratedCode, scriptEvaluation[1], errorMessage, dependencyInfo ] if CACHE_DEBUGS then console.log "setting cached_findDependenciesInScript on key: " + cacheKey cached_findDependenciesInScript.set(cacheKey, toBeFrozen) return [testableString, scriptEvaluation[0], generatedCode, readableSummaryOfGeneratedCode, scriptEvaluation[1], errorMessage, dependencyInfo] recursiveDependencies = (variableToBeChecked, arrayWhereDependenciesWillBeAdded, variablesAlreadyFleshedOut, variablesWithCycles, chainBeingChecked, cyclesDescriptions) -> variablesAlreadyFleshedOut.push variableToBeChecked # recursive dependencies can only be descended if the variable is not bound to a parameter if symbolsDependencies[chainBeingChecked[chainBeingChecked.length-1]]? if symbolsDependencies[chainBeingChecked[chainBeingChecked.length-1]].indexOf("'"+variableToBeChecked) != -1 if DEBUG then console.log "can't keep following the chain of " + variableToBeChecked + " because it's actually a variable bound to a parameter" if arrayWhereDependenciesWillBeAdded.indexOf("'"+variableToBeChecked) == -1 and arrayWhereDependenciesWillBeAdded.indexOf(variableToBeChecked) == -1 arrayWhereDependenciesWillBeAdded.push variableToBeChecked return arrayWhereDependenciesWillBeAdded chainBeingChecked.push variableToBeChecked if !symbolsDependencies[variableToBeChecked]? # end case: the passed variable has no dependencies # so there is nothing else to do if arrayWhereDependenciesWillBeAdded.indexOf(variableToBeChecked) == -1 arrayWhereDependenciesWillBeAdded.push variableToBeChecked return arrayWhereDependenciesWillBeAdded else # recursion case: we have to dig deeper for i in symbolsDependencies[variableToBeChecked] # check that there is no recursion in dependencies # we do that by keeping a list of variables that # have already been "fleshed-out". If we encounter # any of those "fleshed-out" variables while # fleshing out, then there is a cycle if chainBeingChecked.indexOf(i) != -1 if DEBUG then console.log " found cycle:" cyclesDescription = "" for k in chainBeingChecked if variablesWithCycles.indexOf(k) == -1 variablesWithCycles.push k if DEBUG then console.log k + " --> " cyclesDescription += k + " --> " if DEBUG then console.log " ... then " + i + " again" cyclesDescription += " ... then " + i + " again" cyclesDescriptions.push cyclesDescription #if DEBUG then console.log " --> cycle through " + i # we want to flesh-out i but it's already been # fleshed-out, just add it to the variables # with cycles and move on # todo refactor this, there are two copies of these two lines if variablesWithCycles.indexOf(i) == -1 variablesWithCycles.push i else # flesh-out i recursively recursiveDependencies i, arrayWhereDependenciesWillBeAdded, variablesAlreadyFleshedOut, variablesWithCycles, chainBeingChecked, cyclesDescriptions chainBeingChecked.pop() #variablesAlreadyFleshedOut.pop() return arrayWhereDependenciesWillBeAdded # parses and runs one statement/expression at a time inited = false latexErrorSign = "\\rlap{\\large\\color{red}\\bigtriangleup}{\\ \\ \\tiny\\color{red}!}" turnErrorMessageToLatex = (theErrorMessage) -> theErrorMessage = theErrorMessage.replace(/\n/g,"") theErrorMessage = theErrorMessage.replace(/_/g, "} \\\\_ \\text{"); theErrorMessage = theErrorMessage.replace(new RegExp(String.fromCharCode(transpose_unicode), 'g'), "}{}^{T}\\text{"); theErrorMessage = theErrorMessage.replace(new RegExp(String.fromCharCode(dotprod_unicode), 'g'),"}\\cdot \\text{"); theErrorMessage = theErrorMessage.replace("Stop:","} \\quad \\text{Stop:"); theErrorMessage = theErrorMessage.replace("->","} \\rightarrow \\text{"); theErrorMessage = theErrorMessage.replace("?","}\\enspace " + latexErrorSign + " \\enspace \\text{"); theErrorMessage = "$$\\text{" + theErrorMessage.replace(/\n/g,"") + "}$$" #console.log "theErrorMessage: " + theErrorMessage return theErrorMessage # there are around a dozen different unicodes that # represent some sort of middle dot, let's catch the most # common and turn them into what we can process normaliseDots = (stringToNormalise) -> stringToNormalise = stringToNormalise.replace(new RegExp(String.fromCharCode(8901), 'g'), String.fromCharCode(dotprod_unicode)); stringToNormalise = stringToNormalise.replace(new RegExp(String.fromCharCode(8226), 'g'), String.fromCharCode(dotprod_unicode)); stringToNormalise = stringToNormalise.replace(new RegExp(String.fromCharCode(12539), 'g'), String.fromCharCode(dotprod_unicode)); stringToNormalise = stringToNormalise.replace(new RegExp(String.fromCharCode(55296), 'g'), String.fromCharCode(dotprod_unicode)); stringToNormalise = stringToNormalise.replace(new RegExp(String.fromCharCode(65381), 'g'), String.fromCharCode(dotprod_unicode)); return stringToNormalise CACHE_DEBUGS = false CACHE_HITSMISS_DEBUGS = false TIMING_DEBUGS = false cacheMissPenalty = 0 run = (stringToBeRun, generateLatex = false) -> timeStart = new Date().getTime() #stringToBeRun = stringToBeRun + "\n" stringToBeRun = normaliseDots stringToBeRun #console.log "run running: " + stringToBeRun if ENABLE_CACHING and stringToBeRun != "clearall" currentStateHash = getStateHash() cacheKey = currentStateHash + " stringToBeRun: " + stringToBeRun if CACHE_DEBUGS then console.log "cached_runs key: " + cacheKey possiblyCached = cached_runs.get(cacheKey) #possiblyCached = null if possiblyCached? if CACHE_HITSMISS_DEBUGS then console.log "cached_runs hit on: " + stringToBeRun unfreeze(possiblyCached) # return the output string if TIMING_DEBUGS totalTime = new Date().getTime() - timeStart console.log "run time: " + totalTime + "ms, saved " + (possiblyCached[possiblyCached.length-2] - totalTime) + "ms due to cache hit" return possiblyCached[possiblyCached.length - 1] else if CACHE_HITSMISS_DEBUGS then console.log "cached_runs miss on: " + stringToBeRun if TIMING_DEBUGS cacheMissPenalty = (new Date().getTime() - timeStart) if stringToBeRun == "selftest" selftest() return #if (setjmp(stop_return)) # return if !inited inited = true init() i = 0 n = 0 indexOfPartRemainingToBeParsed = 0 allReturnedPlainStrings = "" allReturnedLatexStrings = "" while (1) # while we can keep scanning commands out of the # passed input AND we can execute them... try errorMessage = "" check_stack() n = scan(stringToBeRun.substring(indexOfPartRemainingToBeParsed)) p1 = pop() check_stack() catch error if PRINTOUTRESULT then console.log error #debugger allReturnedPlainStrings += error.message if generateLatex #debugger theErrorMessage = turnErrorMessageToLatex error.message allReturnedLatexStrings += theErrorMessage reset_after_error() break if (n == 0) break # if debug mode then print the source text #if (equaln(get_binding(symbol(TRACE)), 1)) { # for (i = 0 i < n i++) # if (s[i] != '\r') # printchar(s[i]) # if (s[n - 1] != '\n') # n is not zero, see above # printchar('\n') #} indexOfPartRemainingToBeParsed += n push(p1) #debugger errorWhileExecution = false try stringsEmittedByUserPrintouts = "" top_level_eval() #console.log "emitted string after top_level_eval(): >" + stringsEmittedByUserPrintouts + "<" #console.log "allReturnedPlainStrings string after top_level_eval(): >" + allReturnedPlainStrings + "<" p2 = pop() check_stack() if (isstr(p2)) if DEBUG then console.log(p2.str) if DEBUG then console.log("\n") # if the return value is nil there isn't much point # in adding "nil" to the printout if (p2 == symbol(NIL)) #collectedPlainResult = stringsEmittedByUserPrintouts collectedPlainResult = stringsEmittedByUserPrintouts if generateLatex collectedLatexResult = "$$" + stringsEmittedByUserPrintouts + "$$" else #console.log "emitted string before collectPlainStringFromReturnValue: >" + stringsEmittedByUserPrintouts + "<" #console.log "allReturnedPlainStrings string before collectPlainStringFromReturnValue: >" + allReturnedPlainStrings + "<" collectedPlainResult = print_expr(p2) collectedPlainResult += "\n" #console.log "collectedPlainResult: >" + collectedPlainResult + "<" if generateLatex collectedLatexResult = "$$" + collectLatexStringFromReturnValue(p2) + "$$" if DEBUG then console.log "collectedLatexResult: " + collectedLatexResult allReturnedPlainStrings += collectedPlainResult if generateLatex then allReturnedLatexStrings += collectedLatexResult if PRINTOUTRESULT if DEBUG then console.log "printline" if DEBUG then console.log collectedPlainResult #alert collectedPlainResult if PRINTOUTRESULT if DEBUG then console.log "display:" print2dascii(p2) if generateLatex then allReturnedLatexStrings += "\n" catch error errorWhileExecution = true collectedPlainResult = error.message if generateLatex then collectedLatexResult = turnErrorMessageToLatex error.message if PRINTOUTRESULT then console.log collectedPlainResult allReturnedPlainStrings += collectedPlainResult if collectedPlainResult != "" allReturnedPlainStrings += "\n" if generateLatex allReturnedLatexStrings += collectedLatexResult allReturnedLatexStrings += "\n" resetCache() init() if allReturnedPlainStrings[allReturnedPlainStrings.length-1] == "\n" allReturnedPlainStrings = allReturnedPlainStrings.substring(0,allReturnedPlainStrings.length-1) if generateLatex if allReturnedLatexStrings[allReturnedLatexStrings.length-1] == "\n" allReturnedLatexStrings = allReturnedLatexStrings.substring(0,allReturnedLatexStrings.length-1) if generateLatex if DEBUG then console.log "allReturnedLatexStrings: " + allReturnedLatexStrings # TODO handle this case of caching stringToBeReturned = [allReturnedPlainStrings, allReturnedLatexStrings] else stringToBeReturned = allReturnedPlainStrings if ENABLE_CACHING and stringToBeRun != "clearall" and !errorWhileExecution frozen = freeze(