1# Easy Interoperability with Popular Programming Languages
2I'll focus on C++, Python, Javascript, OpenSCAD.
3
4## JSON Import/Export
5JSON data exchange is a way to interoperate with any programming language.
6Curv makes this easy because the 6 core data types are mostly isomorphic
7to JSON.
8* Import JSON using something like `import("foo.json")`.
9* Export JSON. Provide a CLI option for exporting a specified member
10  (or expression) of a script module as JSON.
11  `curv -i expr -o out.json in.curv`
12
13JSON objects correspond to Curv records.
14JSON object keys are arbitrary string literals (not identifiers, as in Curv).
15In order to fully support JSON import, I'd need a syntax for identifiers
16containing an arbitrary sequence of zero or more printable characters.
17These would be quoted identifiers.
18The syntax I have in mind is `` `foo` `` as a quoted identifier.
19How do you include a backtick character in a quoted identifier?
20I could support generalized unicode escape sequences, but that's overkill.
21The simple option is to represent a backtick by doubling it up.
22(Importing a JSON dictionary with duplicate keys, or keys containing
23control characters, is an error.)
24
25## JCSG Import/Export
26CSG trees are exported using JSON syntax. Call this format "jcsg".
27This means shape values (and modules) know how to serialize themselves as JCSG.
28And there is some way to import this format and recreate a shape/module.
29Details are elsewhere.
30
31## Modules
32Curv modules are the unit of modularity,
33and they are the unit of interoperability with other programming languages.
34
35To call functions written in another language, we will import that language's
36analogue of a module and convert it to a Curv module value. The foreign module
37will need to be specifically designed to be compatible with Curv. Bridge code
38will translate between the Curv data types and the data types of the other
39language. Foreign functions should not have side effects. Curv values that
40are shared with foreign code must be immutable.
41* For C++, the use case would be importing highly efficient geometry code.
42  * It's easy to create new builtins that invoke C++ functions,
43    by hacking curv/builtin.cc. Part of "easiness" is that storage is managed
44    using referencing counting and RAII shared pointers, and you can throw an
45    exception derived from std::exception to report an error.
46  * We could import a `*.so` or `*.dll` file as a Curv module.
47    It would need a Curv-specific entry point that returns a Curv module value.
48    This is easy to implement using libcurv.
49* For Python, the use case would be using the extensive Python libraries
50  to write geometry code callable from Curv.
51  We could import a `*.pyc` Python module, which is wrapped up
52  to behave like a Curv module. Python definitions are restricted to types
53  that can be translated to the Curv types.
54* For Javascript, the use case would be running the Curv UI in a web browser,
55  or using Curv as a geometry library in a web browser.
56  The details will depend on how that is accomplished, TBD.
57* For OpenSCAD, there will be an effort to implement OpenSCAD in the Curv VM
58  with at least 99% compatibility. OpenSCAD scripts can be imported as Curv
59  modules. OpenSCAD functions and modules will be treated as Curv functions.
60  The 3 separate namespaces is an issue: we might need two options for dealing
61  with that, one being to prefix each OpenSCAD top level definition with 'v',
62  'f' or 'm' to ensure that the names are different when referenced from Curv.
63
64When calling Curv functions from another language,
65the use case is writing a geometry application in your favourite language
66while taking advantage of the Curv geometry engine and libraries.
67I expect that the Curv API in your favourite language would include:
68* Evaluating a string or file as Curv source code, or loading a Curv library
69  module from a URL, producing an in-memory Curv module value.
70  This is relatively easy using libcurv.
71* An API for creating a Curv preview widget for incorporation into your
72  program's GUI. This is also provided by libcurv and just needs to be wrapped
73  for your language.
74* With a lot more work, there could be an ahead-of-time compilation process
75  to compile Curv code into something that can be more efficiently loaded into
76  your program.
77
78Libcurv already provides this for C++.
79With a lot more work, we could use LLVM to translate a Curv script
80into an optimized shared object or DLL file, plus a C++ header file.
81
82## OpenSCAD
83### OpenSCAD on the Curv VM
84Mapping OpenSCAD types to Curv types:
85* `undef` and `NaN`: `null`. IMO collapsing the two values should not cause any
86  serious compatibility issues.
87* boolean: `Bool`
88* number: `Num`
89* string: `Str`
90* list and range: `List`
91* function: Function (caveats below)
92* module: double curried Function (caveats below)
93* module shape argument: a Thunk, evaluated by each `children()` call,
94  which returns a Module or Shape
95* group: Module, extended with flags for the debug modifiers
96* shape: Shape
97
98OpenSCAD module calls pass the shape or group by-name, as a thunk that
99is evaluated each time `children()` is invoked. Relativity.scad relies on
100this behaviour; 99.99% of scripts don't care. Curv function values don't
101support this. However, OpenSCAD doesn't have module values, and there's no
102problem compiling calls to staticly specified modules into the needed bytecode.
103The module function doesn't actually need to be double curried, just to make
104OpenSCAD code run, the shape can just be a special argument.
105
106The Curv language may or may not support first class functions with
107OpenSCAD style named arguments and special variables, but that doesn't matter
108for the purpose of just compiling and running OpenSCAD code in the VM.
109We just need to provide the right set of opcodes for the compiler to generate.
110
111We don't run into any real issue unless the two languages try to interoperate.
112
113### Curv imports OpenSCAD
114The use case is reusing the large amount of OpenSCAD code that is available
115on Thingiverse, etc, without having to convert the code to Curv.
116
117A good goal is 99.9% compatibility. Much of the truly weird semantics in
118OpenSCAD are in function and module calls. It's easier for the Curv VM
119to be bug compatible when running an OpenSCAD script directly, than when
120calling a Curv first-class function value that has been converted from an
121OpenSCAD function or module. So there might be less compatibility in that case.
122
123Problem: the triple namespace. Solution:
124* The three OpenSCAD namespaces are merged into one.
125  If you try to reference an ambiguous name, you get an error.
126* Use special notation (qualified identifiers) to disambiguate where necessary.
127  Eg, `v$varname`, `f$funname`, `m$modname`.
128
129Importing variables: No problem, the OpenSCAD value types map cleanly to
130the Curv value types. `undef` and `NaN` are merged into `null`, but that's
131not a big deal. OpenSCAD ranges are mapped onto lists, that's slightly more
132disruptive, but still okay.
133
134Importing functions: The main issue is whether we support named arguments and
135special variables in exactly the same way as OpenSCAD. This would be the best
136solution for OpenSCAD import and the OpenSCAD community, because we could
137keep the syntax of function call exactly the same, and OpenSCAD functions
138map directly to Curv function values. It's less ideal for interoperability
139with Javascript and other languages, so a compromise is looming.
140
141If Curv functions don't work exactly the same, then OpenSCAD functions can
142still be mapped to compile time entities that preserve the same call syntax
143and semantics. When a compile time OpenSCAD entity is converted to a Curv
144runtime value (if that is supported), then the semantics have to change.
145
146To what extent do special variables make sense in the context of first class
147function values? Does a function closure ever capture the dynamic variable
148environment, or does it only capture the lexical environment? (By definition,
149the latter.)
150
151If we want total bug compatibility with OpenSCAD, then we have to consider
152that all unrecognized named arguments are 'special variables' that override
153lexical bindings within the called function. With the Curv VM, that causes
154a performance hit, and that is definitely something that only happens at
155compile time, so OpenSCAD functions can't be promoted to values. Marius
156isn't religious about total bug compatibility. There could be problems in
157the field, where people want to run legacy OpenSCAD code, where we could
158consider supporting selectable levels of bug compatibility.
159
160Modules are weirder than functions. The second curried argument of an OpenSCAD
161module is a thunk that evaluates to a shape or group each time `children()`
162is invoked. 99.99% of OpenSCAD scripts don't rely on the thunk
163`relativity.scad` is the one script I know that relies on this; it's a library
164that a small number of other scripts use.
165If we don't support the thunk, and instead use eager (or even lazy) evaluation,
166then we can promote an OpenSCAD module to a Curv first class function value,
167double curried, to the same extent that this is supported for functions.
168
169### OpenSCAD imports Curv
170Use case:
171* Creating an OpenSCAD implementation that uses the Curv VM, and implementing
172  many OpenSCAD geometric primitives in Curv rather than C++.
173* Being able to use F-Rep geometry primitives within OpenSCAD,
174  even though those primitives are coded in Curv.
175
176Let's assume that certain Curv libraries are designed to be also used from
177within OpenSCAD. That's more tractable than trying to support unrestricted
178use of Curv modules from OpenSCAD. In the general case, this could get quite
179ugly. For the general case, a better approach is to provide a migration tool
180that mostly automates the translation from OpenSCAD to Curv.
181
182OpenSCAD has 3 namespaces, Curv has 1. How does the mapping work?
183* Each member of the Curv module is mapped to all three OpenSCAD namespaces:
184  * A Curv binding can be used as a value (no problem),
185  * it can be called as a function (error if wrong type),
186  * it can be called as a module with 1 or 2 curried arglists (error if wrong
187    type, error if it doesn't return a shape or group.
188
189Note that this may create an issue in OpenSCAD, if the builtin `cube` is
190also visible in the variable and function namespaces. This should mostly not
191matter, but a possible workaround is to use qualified identifiers like `m$cube`
192in definitions. The 'm$' prefix is invisible to Curv users of the same library.
193
194We could introduce some new OpenSCAD syntax for dealing with Curv values
195and scripts. This could make OpenSCAD as powerful as Curv, at the expensive
196of ugly, bolted on syntax.
197* `m$cube` is an expression that looks up a name in the module namespace.
198* `f$sqrt` is an expression that looks up a name in the function namespace.
199* In a function or module call context, v$f(x) calls `f` from the value
200  namespace, `f$g(x)` calls `g` from the function namespace,
201  `m$h(x)` calls `h` from the module namespace.
202* Call a value-returning expression as a function or module:
203  `(expr)(x,y)`, `(expr)(x,y){s;}`.
204* In OpenSCAD, `a.b(c)` treats `a` as a Curv module or record value
205  (that exports a single namespace), selects member `b`,
206  then calls it as a function.
207* `import("foo.curv")` imports a Curv script, returns a Curv module value
208  which can be assigned to a variable.
209* `import("foo.scad")` imports an OpenSCAD script, returns a Curv module value
210  which can be assigned to a variable.
211
212Requirement: OpenSCAD functions must preserve their semantics, WRT named
213arguments and special variables, when they are converted to Curv values,
214and then called as unboxed values from within OpenSCAD.
215* The OpenSCAD boxed function call opcode takes a positional argument list,
216  a named argument list, and a dynamic (special variable) environment,
217  and calls the function, which can be either an OpenSCAD defined function
218  or a Curv defined function.
219
220## Python
221Unlike Javascript, I assume that CPython uses the current bytecode
222implementation of Curv. A Curv module is compiled into Curv bytecodes,
223a Python module is compiled into Python bytecodes, and they can call into
224each other. Curv uses non-atomic reference counting, but so does Python,
225so it's safe. (Non-atomic refcounts are guarded by the Python GIL, the global
226interpreter lock.)
227
228Python has positional and named arguments. Externally defined functions
229(not written in Python) may support only positional arguments, or they
230may support a combination of positional and keyword.
231
232### Python imports Curv
233```
234# import a Curv module, as an object
235import curv
236foomod = curv.Import("file:foo.curv")
237
238# make a Curv module visible to the Python module system
239import sys
240sys.modules['foo'] = foomod
241import foo
242
243# configure the Curv preview window and display a shape
244curv.configure_preview(...)
245curv.display(curv.cube(10))
246```
247`curv` is a Python extension module written in C.
248Python modules are just python objects, with special syntax for naming and
249importing them. Using the Python `curv` module, you can import a Curv or
250OpenSCAD script as a Python object, which can also be used as a Python module
251if desired.
252
253From Python, you can call an OpenSCAD function with positional and keyword
254arguments, it works the same in both languages. Special variable arguments
255are keyword arguments using the prefix `__`, since Python does not permit
256`$` in identifiers. Eg, `curv.sphere(10,__fn=10)`.  Or use `f(x,**ENV)`
257to pass a group of special variables in a dictionary.
258
259In Python, an OpenSCAD module is a double-curried function.
260It doesn't seem worthwhile to implement the thunk semantics for
261the shape argument, which would require Python users to pass
262`lambda():...` instead of `...` as the shape argument.
263I'd like to just drop this aspect of OpenSCAD semantics,
264as it messes up every language binding.
265
266From Python, you can call a Curv function.
267
268### Curv imports Python
269So, `import("file:foo.py")` or `import("file:foo.pyc")`
270or `import("python:foo")`.
271This returns a Curv module (or it could be a record, not sure)
272containing a translated representation of a Python module.
273There's a mapping between Python and Curv data types.
274If the Python module tries to export values (or a Python function tries
275to return values) that can't be translated into Curv, then an error occurs.
276I guess dictionaries with string keys are records and objects are modules,
277the others should be obvious.
278
279How does the Curv VM call a function (which might be a Curv wrapper for a
280Python function)?
281* For compatibility with Python multi-threading, we need to use deep binding,
282  not shallow binding, for dynamic variables. So, there's a pointer to a
283  dynamic environment that is supplied to functions.
284* Current design: a function has a fixed number of arguments, N. The entry
285  point to a function expects N values on the stack. Optional and keyword
286  arguments are processed using function metadata, before the function is
287  entered. This design is based on efficiency: we try to do the optional
288  and labeled argument processing at compile time, where possible.
289* A more "Python compatible" design would give the function a counted list
290  of positional arguments and a set of labeled arguments. This would be
291  important if we were concerned about being able to call any Python function.
292  But we aren't. The use case for calling Python is to extend Curv with
293  functions that are easier to write in Python, *and* the Python functions we
294  are calling are specifically designed for compatibility with Curv.
295
296## Javascript
297The use case for Javascript is running Curv code inside a web browser.
298
299Implementation?
300* Compile Curv language into Javascript.
301* Compile Curv language into WebAssembly. I like this idea. WebAssembly
302  is a low level IR, kind of like LLVM, C and C++ compatible, but platform
303  independent and designed for fast translation to efficient machine code.
304  WebAssembly could be a general purpose JIT solution; maybe I can skip LLVM.
305* Compile the Curv compiler into Javascript or WebAssembly using Emscripten
306  or Binaryen.
307
308### Curv calls into Javascript
309This happens when a web page calls a Curv function and passes a Javascript
310function as a parameter.
311* Javascript doesn't support named parameters in a general way.
312  Instead, there is a convention where the final positional parameter
313  is called the 'options' parameter, and it's an object literal of name/value
314  pairs.
315* I think the common case for calling a function parameter is to pass
316  a small number of positional parameters. We won't have a large, elaborate
317  named parameter interface similar to the `cylinder` function.
318* As long as we are calling a Javascript function with positional parameters
319  only, there's no problem. I'd like to say that in this case, you just pass
320  a native Javascript function with no special encoding.
321* Javascript functions can have metadata (dot attributes). If named parameters
322  need to be interpreted, then the Javascript function should have an attribute
323  specifying how those named arguments are to be interpreted.
324  This is a `params` attribute which is a list of (parameter name,default value)
325  pairs. Let's say that an error occurs if a Javascript function is called
326  with labeled arguments and there is no `params` attribute to interpret them
327  with. And there is some story about how special variables are handled.
328  If a Javascript function is prepared to deal with metadata, then an attribute
329  is used to declare this.
330
331### Javascript calls into Curv
332This is the more normal case. We want the ability to call OpenSCAD and
333Curv functions from Javascript.
334* How is a compiled Curv module represented within a web browser?
335  The current C++ bytecode runtime is not multi-threaded, and uses non-atomic
336  reference counts, so values can't be shared between threads. That won't work
337  in Javascript. What might be better is to compile a Curv module into
338  Javascript. That will have benefits and drawbacks, but it's probably the best
339  approach for web browser integration.
340* Efficiency is an issue. We don't want a heavy and expensive argument
341  encoding. Ideally Curv and OpenSCAD functions just get compiled into
342  conventional and idiomatic Javascript functions that the Javascript
343  compiler knows how to implement efficiently.
344* User defined OpenSCAD and Curv functions all have a fixed number of
345  parameters, some of which are optional. If we say that Javascript must
346  use positional parameters when calling these functions, all is well.
347* Curv additionally supports the ability to support the Javascript
348  convention of passing a record as the final parameter (an options parameter).
349  And I'm planning to use that convention in the shape3d protocol.
350* OpenSCAD functions can require the use of special variables.
351  * In theory, the Curv compiler can use global analysis to determine
352    that a given OpenSCAD function uses or ignores special variables.
353    The Javascript version could be decorated with an attribute for this,
354    if it helps.
355  * How does Javascript code pass special variables to an OpenSCAD function?
356    Maybe there is a postultimate optional parameter which is a dictionary
357    of special variables.
358  * Or maybe there's an attribute with an alternate entry point to the function
359    with an extra environment parameter.
360  * In Javascript, there is a special way to call a function where you
361    specify an extra magic `this` parameter. So maybe use that as the
362    alternate entry point. `fun.call(this,arg1,arg2,...)`
363
364## Function Parameters
365What is the impact of the Curv function calling convention on interoperability?
366* In the case of calling foreign functions, they have to be specially designed
367  to be called from Curv.
368* In the case of other languages calling into Curv (eg using Curv as a
369  geometry library), any Curv function needs to be callable,
370  and there needs to be a reasonable default mapping from Curv functions
371  to the other language.
372* There is also the case of compiling Curv into other languages,
373  specifically machine code or C++ compatible object code via LLVM,
374  and GLSL for execution on the GPU.
375  In these cases, I need a high performance translation.
376
377Curv functions have a fixed number of parameters. There is no 'varargs'
378mechanism, unlike Python and Javascript. This is good for interoperability.
379* It's a design issue. A varargs mechanism adds extra complexity we don't need,
380  given that it's just as easy to pass a list argument. If there are two ways
381  to pass a variable number of arguments (varargs and lists), then both will
382  be used, and users will need to explicitly convert between the two
383  conventions. It's not worth it.
384* It's a performance issue. When compiling to machine code or GPU code,
385  I want to use a fixed-argument calling convention where arguments can be
386  assigned to registers. And/or I want to use a calling convention that is
387  compatible with tail call optimization, and in LLVM, varargs isn't compatible.
388
389Tail parameters may be optional, with a default value.
390This causes no interoperability issues. The 4 target languages all
391support this.
392
393Keyword arguments?
394There's no rush: I'll start without them, and see how things evolve.
395* If we don't support these, then it makes it easier to call Curv code from
396  other languages that lack these features.
397* In Javascript, there isn't keyword argument support in the same way as
398  Python or OpenSCAD. Instead, functions that need keyword arguments
399  use a convention where the last argument is an "options" argument consisting
400  of an object literal that contains keyword arguments. In most cases,
401  the options argument is optional. There is a pattern matching feature in ES 6
402  that makes this convention easy to use.
403  * More abstractly, Javascript function arguments are classified in advance
404    as either positional or keyword, they can't be both. Also, "JavaScript
405    engines should be able to optimize invocations of functions and methods
406    with ES6-style named parameters such that no temporary object is created."
407  * If I use the Javascript convention instead of supporting keyword arguments,
408    then I have more flexibility in wrapping a Curv function for being called
409    from another language. This convention consists of a final 'options'
410    parameter that is a record pattern containing a fixed set of fields.
411    Obviously Javascript would be easier. For C++, I have the option of
412    constructing a curv::Record value and passing that as a parameter,
413    or flattening the options argument into a sequence of positional parameters.
414    (If necessary, Curv functions could have two entry points for these
415    different conventions.)
416    For OpenSCAD and Python, I could convert the options parameter into
417    keyword parameters.
418  * If Curv lacks keyword parameters, then how do you call an OpenSCAD function?
419    As long as OpenSCAD lacks record values, I could interpret a
420    final record literal argument as a set of keyword parameters.
421    But OpenSCAD could be extended to be more Curv-like. So we'll see.
422  * This approach is also more flexible than keyword arguments, since records
423    are first class values that represent sets of model parameters.
424    And then there is the design argument of not supporting two mechanisms
425    for doing the same thing.
426* I could support keyword arguments on the same basis as OpenSCAD.
427  In that case, from Javascript, there could be a special object type that
428  represents a set of keyword arguments, distinct from the Javascript type
429  that represents a Curv record.
430
431Special variables? None of my "popular languages" support this mechanism.
432There's no rush: I'll start without them, and see how things evolve.
433* Don't support special variables directly.
434  Use record arguments instead. This is my current plan for the shape3d
435  protocol.
436* Support special variables. Provide some workaround for specifying them
437  from another language.
438  * Global variables.
439  * A special environment argument.
440