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