diff --git a/tests/jme-runtime.js b/tests/jme-runtime.js index 79eff77f2..7f7bf3c5a 100644 --- a/tests/jme-runtime.js +++ b/tests/jme-runtime.js @@ -11204,7 +11204,12 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { } else { var nlambda = new types.TLambda(); nlambda.names = tok.names; - nlambda.set_expr(jme.substituteTree(tok.expr, scope, true, false)); + nlambda.make_signature(); + var nscope = new Numbas.jme.Scope([scope]); + nlambda.all_names.forEach(function(name) { + nscope.deleteVariable(name); + }); + nlambda.set_expr(jme.substituteTree(tok.expr, nscope, true, false)); return nlambda; } @@ -12128,13 +12133,7 @@ TLambda.prototype = { this.names = names; }, - /** Set the body of this function. The argument names must already have been set. - * - * @param {Numbas.jme.tree} expr - */ - set_expr: function(expr) { - const lambda = this; - this.expr = expr; + make_signature: function(expr) { var all_names = []; /** Make the signature for the given argument. @@ -12159,6 +12158,19 @@ TLambda.prototype = { this.all_names = all_names; + return signature; + }, + + /** Set the body of this function. The argument names must already have been set. + * + * @param {Numbas.jme.tree} expr + */ + set_expr: function(expr) { + const lambda = this; + this.expr = expr; + + const signature = this.make_signature(); + this.fn = new jme.funcObj('', signature, '?', null, { evaluate: function(args, scope) { var nscope = new jme.Scope([scope]); @@ -12805,7 +12817,11 @@ var findvars = jme.findvars = function(tree,boundvars,scope) { return []; } } else { - return jme.findvars_args(tree.args, boundvars, scope); + var argvars = jme.findvars_args(tree.args, boundvars, scope); + if(tree.tok.type == 'function' && scope.getFunction(tree.tok.name).length == 0) { + argvars.push(tree.tok.name); + } + return argvars; } } @@ -17052,6 +17068,13 @@ newBuiltin('numerical_compare',[TExpression,TExpression],TBool,null,{ } }); +newBuiltin('debug_log', ['?','?'], '?', null, { + evaluate: function(args, scope) { + console.log('DEBUG ' + args[1].value + ':', Numbas.jme.unwrapValue(args[0])); + return args[0]; + } +}, {unwrapValues: false}); + newBuiltin('scope_case_sensitive', ['?',TBool], '?', null, { evaluate: function(args,scope) { var caseSensitive = args.length>1 ? scope.evaluate(args[1]).value : true; @@ -20273,6 +20296,7 @@ jme.variables.note_script_constructor = function(construct_scope, process_result */ function Script(source, base, scope) { this.source = source; + scope = construct_scope(scope); try { var notes = source.split(/\n(\s*\n)+/); var ntodo = {}; diff --git a/tests/jme/doc-tests.mjs b/tests/jme/doc-tests.mjs index 4fba8dffd..81f3b9982 100644 --- a/tests/jme/doc-tests.mjs +++ b/tests/jme/doc-tests.mjs @@ -5694,5 +5694,22 @@ export default "examples": [] } ] + }, + { + "name": "Debugging tools", + "fns": [ + { + "name": "debug_log", + "keywords": [ + "debug", + "log" + ], + "noexamples": true, + "calling_patterns": [ + "debug_log(x, label)" + ], + "examples": [] + } + ] } ] diff --git a/tests/jme/jme-tests.mjs b/tests/jme/jme-tests.mjs index a3e6e9e71..82552976c 100644 --- a/tests/jme/jme-tests.mjs +++ b/tests/jme/jme-tests.mjs @@ -93,6 +93,7 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('let([q,w],[2,3],x,z,x+y+q+w)')),['y','z'],'findvars on let with a sequence of names'); deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('x -> x+z')),['z'],'findvars on lambda'); deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('[x,[y,z]] -> x+y+z+w')),['w'],'findvars on lambda with destructuring'); + deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('undefined_function(x)')),['x', 'undefined_function'],'Undefined function names are presumed to be missing variables.'); }); QUnit.test('findvars in HTML',function(assert) { @@ -101,7 +102,7 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal d.innerHTML = '

$\\var{x} + \\simplify{f(y)-{y}}$

{f(c)}
'; var vars = s.findvars(d); vars.sort(); - deepCloseEqual(assert, vars,['c','s','x','y']); + deepCloseEqual(assert, vars,['c','f','s','x','y']); }); QUnit.test('util',function(assert) { @@ -775,6 +776,7 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal ['let(f, x -> 2x, f(3))', 6, 'referring to an anonymous function as a variable'], ['let(z, 2, f, x -> x+z, f(1))', 3, 'using an external variable in an anonymous function'], ['let(z, 2, f, x -> x+z, let(z, 3, f(1)))', 3, 'external variables are bound at the point the anonymous function is defined'], + ['let(z, 2, f, z -> z, f(1) + f(1))', 2, 'Named arguments are removed from the scope when constructing an anonymous function'], ['let(f, n -> 2i for: i of: 1..n, map(f(j),j,1..5))', [[2],[2,4],[2,4,6],[2,4,6,8],[2,4,6,8,10]], 'variables are kept free inside list comprehensions'], ]; tests.forEach(([expr, value, note]) => { @@ -1588,7 +1590,7 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal outtype: 'number', parameters: [{type: 'number', name: 'x'}] }); - deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('issue781(z)')),['z'],'Default findVars behaviour on custom javascript function'); + deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('issue781(z)')),['z', 'issue781'],'Default findVars behaviour on custom javascript function'); }); QUnit.test('Rulesets',function(assert) { diff --git a/tests/numbas-runtime.js b/tests/numbas-runtime.js index 8287cb23a..3bed65f15 100644 --- a/tests/numbas-runtime.js +++ b/tests/numbas-runtime.js @@ -10795,7 +10795,12 @@ Scope.prototype = /** @lends Numbas.jme.Scope.prototype */ { } else { var nlambda = new types.TLambda(); nlambda.names = tok.names; - nlambda.set_expr(jme.substituteTree(tok.expr, scope, true, false)); + nlambda.make_signature(); + var nscope = new Numbas.jme.Scope([scope]); + nlambda.all_names.forEach(function(name) { + nscope.deleteVariable(name); + }); + nlambda.set_expr(jme.substituteTree(tok.expr, nscope, true, false)); return nlambda; } @@ -11719,13 +11724,7 @@ TLambda.prototype = { this.names = names; }, - /** Set the body of this function. The argument names must already have been set. - * - * @param {Numbas.jme.tree} expr - */ - set_expr: function(expr) { - const lambda = this; - this.expr = expr; + make_signature: function(expr) { var all_names = []; /** Make the signature for the given argument. @@ -11750,6 +11749,19 @@ TLambda.prototype = { this.all_names = all_names; + return signature; + }, + + /** Set the body of this function. The argument names must already have been set. + * + * @param {Numbas.jme.tree} expr + */ + set_expr: function(expr) { + const lambda = this; + this.expr = expr; + + const signature = this.make_signature(); + this.fn = new jme.funcObj('', signature, '?', null, { evaluate: function(args, scope) { var nscope = new jme.Scope([scope]); @@ -12396,7 +12408,11 @@ var findvars = jme.findvars = function(tree,boundvars,scope) { return []; } } else { - return jme.findvars_args(tree.args, boundvars, scope); + var argvars = jme.findvars_args(tree.args, boundvars, scope); + if(tree.tok.type == 'function' && scope.getFunction(tree.tok.name).length == 0) { + argvars.push(tree.tok.name); + } + return argvars; } } @@ -16643,6 +16659,13 @@ newBuiltin('numerical_compare',[TExpression,TExpression],TBool,null,{ } }); +newBuiltin('debug_log', ['?','?'], '?', null, { + evaluate: function(args, scope) { + console.log('DEBUG ' + args[1].value + ':', Numbas.jme.unwrapValue(args[0])); + return args[0]; + } +}, {unwrapValues: false}); + newBuiltin('scope_case_sensitive', ['?',TBool], '?', null, { evaluate: function(args,scope) { var caseSensitive = args.length>1 ? scope.evaluate(args[1]).value : true; @@ -19864,6 +19887,7 @@ jme.variables.note_script_constructor = function(construct_scope, process_result */ function Script(source, base, scope) { this.source = source; + scope = construct_scope(scope); try { var notes = source.split(/\n(\s*\n)+/); var ntodo = {};