Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for calling interpreted functions #201

Open
wants to merge 68 commits into
base: master
Choose a base branch
from

Conversation

Webifi
Copy link

@Webifi Webifi commented Nov 11, 2020

Allows calling anonymous interpreted functions from native code.
(Includes changes in #193)

fixes #192, resolves #199, closes #147, closes #189,

Examples:

Synchronous callback from native function:

function nativeFunction (func) {
	return interpreter.callFunction(func, this, 25).then(function(v) {
		console.log('got psudo result', v)
		return v + 5
	})
}

// Called via interpreted code
var test = nativeFunction(function(v){return v + 2})
// Should produce 32

Synchronous callback from native AsyncFunction:

function nativeAsyncFunction(func, callback) {
	callback(interpreter.callFunction(func, this, 25).then(function(v, callback2) {
		console.log('got psudo result', v)
		callback2(v + 5)
	}))
}

// Called via interpreted code
var test = nativeAsyncFunction(function(v){return v + 2})
// Should produce 32

In both cases above, the .then(...) callback value handler is optional. If omitted, the value of the called interpreted function will be returned.

Additional pseudo functions can be called by simply returning another Callback in the .then() handler via return interpreter.callFunction(...)

For example:

function nativeAsyncFunction(func, func2, callback) {
	callback(interpreter.callFunction(func, this).then(function(v, callback) {
		callback(interpreter.callFunction(func2, this).then(function(v2, callback) {
		        callback("I'm done with " + v + " and " +  v2)
	        }))
	}))
}

or:

function nativeFunction(func, func2) {
	return interpreter.callFunction(func, this).then(function(v) {
		return interpreter.callFunction(func2, this).then(function(v2) {
		        return "I'm done with " + v + " and " +  v2
	        })
	})
}

Queued via queueFunction: (func is called last.)

function nativeFunction(func) {
  interpreter.queueFunction(func, this, pseudoarg1, pseudoarg2);
}

Queued Callbacks:

function nativeFunction(func1, func2) {
  interpreter.queueFunction(func1, this).then(val => {
    console.log('func1 returned:', val);
  });
  interpreter.queueFunction(func2, this).then(val => {
    console.log('func2 returned:', val);
  });
}

Throwing exception in native AsyncFunction:

function nativeAsyncFunction(val, callback) {
        if (val < 2) return callback(interpreter.createThrowable(
            interpreter.RANGE_ERROR,
           'Value must be greater than 2'
        ))
        callback(val + 2)
}

Throwing exception in native function: (Alternate to interpreter.throwException(...))

function nativeFunction(val) {
        if (val < 2) return interpreter.createThrowable(
            interpreter.RANGE_ERROR,
           'Value must be greater than 2'
        )
        return val + 2
}

Catching exceptions in pseudo function calls from native:

function nativeFunction(func1, func2) {
   // Will be called later
   interpreter.queueFunction(func2, this).then(val => {
    console.log('func2 returned:', val);
  }).catch(e => {
    console.log('Got an error in func2:', interpreter.getProperty(e, 'message'); e);
  });
  // Will be called on next step
  return interpreter.callFunction(func1, this).then(val => {
    console.log('func1 returned:', val);
    return val + 25; // will return val + 25 to caller
  }).catch(e => {
    console.log('Got an error in func1:', interpreter.getProperty(e, 'message'); e);
    return 42; // will return 42 to caller
  });
}

Catching exceptions in pseudo function calls from native async:

function nativeAsyncFunction(func1, func2, callback) {
   // Will be called later
   interpreter.queueFunction(func2, this).then(val => {
    console.log('func2 returned:', val);
  }).catch(e => {
    console.log('Got an error in func2:', interpreter.getProperty(e, 'message); e);
  });
  // Will be called on next step
  callback(interpreter.callFunction(func1, this).then((val, callback) => {
    console.log('func1 returned:', val);
    callback(val + 25); // will return val + 25 to caller
  }).catch((e, callback) => {
    console.log('Got an error in func1:', interpreter.getProperty(e, 'message); e);
    callback(42); // will return 42 to caller
  }));
}

Chaining thens in functions calls from native:

function nativeFunction(func1, func2) {
  return interpreter.callFunction(func1, this).then(val => {
    console.log('func1 returned:', val);
    return val + 25; // will return val + 25 to caller
  }).then(val => {
    return val + 2;
  }).then(val => {
    return val * 4;
  }).catch(e => {
    return 0;
  });
  // Note:  Returning an interpreter.callFunction(...) in a then will effectively terminate the chain.
  //            All remaining then operations will be ignored and the callback will be executed.
}

Implementing native timers example:

const interpreter = new Interpreter("", (interpreter, globalObject) => {
  const timeouts = {};
  let timeoutCounter = 0;

  const intervals = {};
  let intervalCounter = 0;

  const frames = {};
  let frameCounter = 0;

  interpreter.setProperty(
    globalObject,
    "setTimeout",
    interpreter.createNativeFunction(function (fn, time) {
      const tid = ++timeoutCounter;
      const _this = this;
      timeouts[tid] = setTimeout(function () {
        if (timeouts[tid]) {
          delete timeouts[tid];
          interpreter.queueFunction(fn, _this);
          interpreter.run(); // Keep running
        }
      }, time);
      return tid;
    })
  );

  interpreter.setProperty(
    globalObject,
    "clearTimeout",
    interpreter.createNativeFunction((tid) => {
      clearTimeout(timeouts[tid]);
      delete timeouts[tid];
    })
  );

  interpreter.setProperty(
    globalObject,
    "setInterval",
    interpreter.createNativeFunction(function (fn, time) {
      const tid = ++intervalCounter;
      const _this = this;
      intervals[tid] = setInterval(function () {
        interpreter.queueFunction(fn, _this);
        interpreter.run(); // Keep running
      }, time);
      return tid;
    })
  );

  interpreter.setProperty(
    globalObject,
    "clearInterval",
    interpreter.createNativeFunction((tid) => {
      clearInterval(intervals[tid]);
      delete intervals[tid];
    })
  );

  interpreter.setProperty(
    globalObject,
    "requestAnimationFrame",
    interpreter.createNativeFunction(function (fn, time) {
      const tid = ++frameCounter;
      const _this = this;
      frames[tid] = requestAnimationFrame(function () {
        if (frames[tid]) {
          delete frames[tid];
          interpreter.queueFunction(fn, _this);
          interpreter.run(); // Keep running
        }
      }, time);
      return tid;
    })
  );

  interpreter.setProperty(
    globalObject,
    "cancelAnimationFrame",
    interpreter.createNativeFunction((tid) => {
      cancelAnimationFrame(frames[tid]);
      delete frames[tid];
    })
  );
});

interpreter.appendCode(`
  var interval = setInterval(function() {
    console.log('Yay! Intervals!');
  }, 1000);
  setTimeout(function() {
    console.log('Yay! Timeouts!');
    clearInterval(interval);
  }, 5000);
`);
interpreter.run();

Use Callbacks in AsyncFuntion, full example:

const interpreter = new Interpreter('', (interpreter, globalObject) => {
  interpreter.setProperty(
    globalObject,
    'doThing',
    interpreter.createAsyncFunction(function(
      urlProvider,
      success,
      error,
      callback
    ) {
      var _this = this
      callback(
        interpreter.callFunction(urlProvider, _this).then(url => {
          fetch(url)
            .then(response => response.json())
            .then(data => {
              callback(interpreter.callFunction(success, _this, data))
              interpreter.run()
            })
            .catch(e => {
              callback(interpreter.callFunction(error, _this, e.toString()))
              interpreter.run()
            })
        })
      )
    })
  )
})

interpreter.appendCode(`
function onSuccess(json) {
  console.log(json.fruit);
}
function onError(message) {
  console.log(message);
}
function urlProvider() {
  return 'https://support.oneskyapp.com/hc/en-us/article_attachments/202761627/example_1.json'
}
doThing(urlProvider, onSuccess, onError);
`)
interpreter.run()

Use Callbacks in Native Function, full example:

const interpreter = new Interpreter('', (interpreter, globalObject) => {
  interpreter.setProperty(
    globalObject,
    'doThing',
    interpreter.createNativeFunction(function(urlProvider, success, error) {
      var _this = this
      return interpreter.callFunction(urlProvider, _this).then(url => {
        fetch(url)
          .then(response => response.json())
          .then(data => {
            interpreter.queueFunction(success, _this, data)
            interpreter.run()
          })
          .catch(e => {
            interpreter.queueFunction(error, _this, e.toString())
            interpreter.run()
          })
      })
    })
  )
})

interpreter.appendCode(`
function onSuccess(json) {
  console.log(json.fruit);
}
function onError(message) {
  console.log(message);
}
function urlProvider() {
  return 'https://support.oneskyapp.com/hc/en-us/article_attachments/202761627/example_1.json'
}
doThing(urlProvider, onSuccess, onError);
`)
interpreter.run()

@Webifi Webifi mentioned this pull request Nov 12, 2020
@Webifi
Copy link
Author

Webifi commented Nov 12, 2020

[EDIT] No longer applicable

@Webifi
Copy link
Author

Webifi commented Nov 12, 2020

EDIT: No longer applicable

@Webifi
Copy link
Author

Webifi commented Nov 22, 2020

Points @cpcallen made here should all be addressed now.

Naming of new exported methods, callFunction, queueFunction and createThrowable have yet to be finalized.

@Webifi
Copy link
Author

Webifi commented Nov 23, 2020

I think I ironed out all the bugs now.

@cpcallen
Copy link
Collaborator

@NeilFraser: do you want to consider this PR? The topic of implementing setTimeout in JS Interpreter has come up in the Blockly forum again.

If you're interested in considering it I'm happy to give it a careful review as a second pair of eyes if you like.

@Webifi
Copy link
Author

Webifi commented Jun 30, 2023

I merged it with all the recent changes, but I'm not sure it's working correctly. Don't have time to test right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants