Skip to content

Commit 4598c79

Browse files
committed
findvars on recursive custom functions doesn't include that function's name
When finding unbound variables inside the definition of a custom JME function, the scope didn't yet know about the function being defined, so a recursive call of that function would produce the function's own name as a free variable. This changes makeJMEFunction to add the function to the scope that is used when finding unbound variables inside its definition.
1 parent 6a98c8b commit 4598c79

File tree

4 files changed

+21
-3
lines changed

4 files changed

+21
-3
lines changed

runtime/scripts/jme-variables.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ jme.variables = /** @lends Numbas.jme.variables */ {
4949
*/
5050
makeJMEFunction: function(fn,scope) {
5151
fn.tree = jme.compile(fn.definition,scope,true);
52-
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),scope);
52+
const nscope = new jme.Scope([scope]);
53+
nscope.addFunction(fn);
54+
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),nscope);
5355
jme.findvarsOps[fn.name] = function(tree,boundvars,scope) {
5456
var vars = external_vars.slice();
5557
for(var i=0;i<tree.args.length;i++) {

tests/jme-runtime.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19971,7 +19971,9 @@ jme.variables = /** @lends Numbas.jme.variables */ {
1997119971
*/
1997219972
makeJMEFunction: function(fn,scope) {
1997319973
fn.tree = jme.compile(fn.definition,scope,true);
19974-
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),scope);
19974+
const nscope = new jme.Scope([scope]);
19975+
nscope.addFunction(fn);
19976+
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),nscope);
1997519977
jme.findvarsOps[fn.name] = function(tree,boundvars,scope) {
1997619978
var vars = external_vars.slice();
1997719979
for(var i=0;i<tree.args.length;i++) {

tests/jme/jme-tests.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ Numbas.queueScript('jme_tests',['qunit','jme','jme-rules','jme-display','jme-cal
9595
deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('a -> a(x)')),['x'],'findvars on lambda where an argument is another lambda');
9696
deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('[x,[y,z]] -> x+y+z+w')),['w'],'findvars on lambda with destructuring');
9797
deepCloseEqual(assert, Numbas.jme.findvars(Numbas.jme.compile('undefined_function(x)')),['x', 'undefined_function'],'Undefined function names are presumed to be missing variables.');
98+
99+
const fn = Numbas.jme.variables.makeFunction({
100+
parameters: [{type:'number', name:'x'}],
101+
definition: 'if(x>0,0,f(x-1))',
102+
name: 'f',
103+
language: 'jme'
104+
}, Numbas.jme.builtinScope);
105+
106+
const scope = new Numbas.jme.Scope([Numbas.jme.builtinScope]);
107+
scope.addFunction(fn);
108+
const vars = Numbas.jme.findvars(Numbas.jme.compile('f(2)'), scope);
109+
assert.deepEqual(vars, [], "Recursive custom functions don't produce their own names in findvars");
98110
});
99111

100112
QUnit.test('findvars in HTML',function(assert) {

tests/numbas-runtime.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19307,7 +19307,9 @@ jme.variables = /** @lends Numbas.jme.variables */ {
1930719307
*/
1930819308
makeJMEFunction: function(fn,scope) {
1930919309
fn.tree = jme.compile(fn.definition,scope,true);
19310-
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),scope);
19310+
const nscope = new jme.Scope([scope]);
19311+
nscope.addFunction(fn);
19312+
var external_vars = jme.findvars(fn.tree,fn.paramNames.map(function(v) { return jme.normaliseName(v,scope) }),nscope);
1931119313
jme.findvarsOps[fn.name] = function(tree,boundvars,scope) {
1931219314
var vars = external_vars.slice();
1931319315
for(var i=0;i<tree.args.length;i++) {

0 commit comments

Comments
 (0)