waterbearlang / moonshine

A programming language that maps nicely to blocks
2 stars 0 forks source link

Allow escape into host language in block definition #13

Closed dethe closed 2 years ago

dethe commented 2 years ago

At some point blocks can't be defined in terms of other blocks and have to drop into the host language. We can do this in a hidden runtime, but better if we can do it in the blocks themselves for better visibility. The goal is always to keep as much in blocks as possible.

Considering limiting escapes to a single expression or function call for now, to keep complex host code out of blocks. May not need to enforce, but at least keep to this for examples.

dethe commented 2 years ago

There are at least two parts to this:

  1. How to call hosted code from Moonshine

This is just a special case of a block call and can be parsed from that code already.

hosted(JavaScript) return (Number) [
  // js code here
]

And inside the JavaScript we need a way to call the blocks. We'll pass them in as an array, we can extract values from them at run time for interpretation, or we can extract code from the for compilation. This may take some experimenting.

We'll also need to return a value back from the JavaScript into Moonshine, which may be the only place we need a return statement.

dethe commented 2 years ago

Do I need to specify the language for every hosted code block? Is a given library or sprite going to have hosted code for more than one language?

Can we used hosted code anywhere except a library?

Going to say no to both of these until proven otherwise, so code will change to:

library Controls hue: (0) language: (JavaScript)[
  define wait (seconds:Number) seconds[
    hosted returns (Number) [
      // js code here
    ]
  ]
]
dethe commented 2 years ago

In JavaScript there are several things we could be doing:

  1. returning a value (constant or variable)
  2. calling a function and returning a value
  3. calling an asynchronous function and resuming later
  4. processing a list of blocks (which themselves may be values, functions, or async)

And in each case, the value returned must be attached to a name to be handled by the rest of the Moonshine code.

For value, function, and async, I think the best way is to tell it in the host call.

Another factor is that code can be "compiled" for speed or interpreted block-by-block.

I think the easiest, at least for now is that blocks and compiled functions take an environment and save their values (or munged values) to the environment, returning the environment rather than the value. This may get ugly... They can also take a next value to call on completion. Mapping those may be interesting.

If multiple values are returned in an environment with the same name, can they form a stack, and then if something refers to one of them, they have to specify which index in the stack they want? So instead of

let x = foobar();

We would call

env.push('x', foobar());

And the environment would make sure "x" exists, then push to it. That way we don't have to munge names, but can still use intermediate values later. Kind of a weird way to handle immutability, but it could work.

dethe commented 2 years ago

OMG I am working so hard to make it possible to mix sync and async blocks when I have Triggers sitting right there! Use Triggers to handle async code, step/value blocks for everything else. Deal with any exceptions when I have to. Stop making my own life difficult!

dethe commented 2 years ago

Can't use bare square brackets for delimiting code, because square brackets appear in code. Going to try using [@ and @] to delimit hosted code for now.