{"id":18645510,"url":"https://github.com/cscott/lua-turtle","last_synced_at":"2026-01-07T06:50:16.596Z","repository":{"id":64468857,"uuid":"239147018","full_name":"cscott/lua-turtle","owner":"cscott","description":"TurtleScript interpreter in lua","archived":false,"fork":false,"pushed_at":"2024-03-29T16:22:48.000Z","size":1160,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-03T13:11:52.513Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cscott.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-02-08T14:37:49.000Z","updated_at":"2022-12-02T20:48:06.000Z","dependencies_parsed_at":"2023-12-11T19:28:55.221Z","dependency_job_id":"21419710-06d5-41b8-a9a9-32da1dfde752","html_url":"https://github.com/cscott/lua-turtle","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cscott%2Flua-turtle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cscott%2Flua-turtle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cscott%2Flua-turtle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cscott%2Flua-turtle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cscott","download_url":"https://codeload.github.com/cscott/lua-turtle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246131335,"owners_count":20728303,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T06:16:16.018Z","updated_at":"2026-01-07T06:50:16.566Z","avatar_url":"https://github.com/cscott.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lua-turtle\n\n`lua-turtle` is an implementation of\n[TurtleScript](https://github.com/cscott/turtlescript) in\nLua.  TurtleScript is a syntactic\n(but not semantic) subset of JavaScript, originally created for\nthe One Laptop per Child project.  This implementation especially\ntakes pains to match the official ECMAScript runtime semantics from\nhttps://tc39.es/ecma262 -- probably at the expense of some execution\nspeed.\n\n## Install, and Run\n\nThis installation is standalone.  I developed this code using Lua\n5.3.3 and then ported it to Lua 5.1 to make it compatible with\nScribunto on Wikimedia projects.  You can try it out on Wikipedia at\nhttps://en.wikipedia.org/wiki/User:Cscott/LuaTurtle .\n\nTo run a TurtleScript\n[REPL](http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop):\n```\n$ ./repl.lua\n\u003e\u003e\u003e 2+3\n5\n\u003e\u003e\u003e var fact = function(x) { return (x\u003c2) ? x : (x * fact(x-1)) ; };\nundefined\n\u003e\u003e\u003e fact(42)\n7538058755741581312\n\u003e\u003e\u003e\n```\nUse Control-D (or Control-C) to exit the REPL.  You can also evaluate entire\nTurtleScript scripts by passing the name on the command line:\n```\n$ ./repl.lua foo.js\n```\n\n## Bundling\nThe code running on Wikipedia has been bundled into a single file\nusing the command:\n```\n$ lua make-one-file.lua luaturtle.repl\n```\n\n## Testing\nYou can run the unit tests with `./run_tests.lua` from the top-level\ndirectory. See `tests/test_interp.lua` for a set of script-based tests,\nwhich you could manually reproduce in the REPL (if you were so inclined).\n\n## Design\n`lua-turtle` is an interpreter for the bytecode emitted by\n`bcompile.js` from the TurtleScript project.  It is heavily based on\n`binterp.js` from that project, which is a TurtleScript interpreter written\nin TurtleScript, as well as on\n[`rusty-turtle`](https://github.com/cscott/rusty-turtle) and\n[`php-turtle`](https://github.com/cscott/php-turtle), my previous\nimplementations of TurtleScript runtimes in Rust and PHP, respectively.\nThe `luaturtle/startup.lua` file contains the bytecode for the\nTurtleScript standard library implementation (from `binterp.js`) as\nwell as the tokenizer, parser, and bytecode compiler itself (emitted\nby `write-lua-bytecode.js` in the\n[TurtleScript](https://github.com/cscott/TurtleScript)\nproject).  This allows the `lua-turtle` REPL to parse and compile the\nexpressions you type at it into bytecode modules which it can interpret.\n\nThe JavaScript object model in\n[`luaturtle/jsval.lua`](https://github.com/cscott/lua-turtle/blob/master/luaturtle/jsval.lua)\nhas been implemented in Lua in a way which\ntries to make Lua access to and operations on JavaScript objects feel\nnatural.  Although this is mostly straightforward for (say) arithmetic\noperators on numeric types, some performance compromises were\nrequired.  In particular JS properties are renamed and \"hidden\" in the\nLua object in order to ensure that direct property access in Lua\ndoesn't hit in the table but instead goes through the `__index`\nmethod.  It's possible we could dial this back a bit and use the\nUTF-16 JS field names directly: since this effectively prepends a `\\0`\nin front of most ASCII field names this would still ensure that\n`__index` is used for most natural human accesses.  Arrays would\nrequire special treatment (see below).\n\nWe've implemented fast paths through `[[Get]]` and `[[Set]]`\nfor the most typical cases: read/write of plain properties (writable,\nenumerable, configurable) and \"modern method\" invocation (reads of\nfunction objects from not writable/not enumerable/not configurable\nproperties).  Plain properties are stored directly in the Lua\ntable; descriptors are stored for other properties.\n\nWe do not currently wrap any Lua objects for insertion into the JavaScript\nenvironment, but it would not be too hard to do so given the ECMAScript\nstandard's support for \"Exotic\" and Proxy objects.\n\nGenerally we've tried to use dynamic method dispatch through the\nmetatable as often as possible to replace explicit\ntype-test-and-branch code.  For example, instead of testing both\narguments to the `BI_ADD` (binary addition) bytecode operation to see\nif either is a String (in which case we need to do string\nconcatenation instead of numerical addition), we dispatch through the\n`__add` method in the metatable.  In the common case where the left\nhand operand is already a String, this saves a test and we can do the\nconcatenation directly.  This technique doesn't work quite as well\nwhen the left-hand operation is a Number, since we still have to test\nwhether the right-hand operation is a String in that case, but we try\nto do as many typechecks as possible in this way.\n\n## Future performance improvements\n\nThe representation of arrays at present leaves much to be\ndesired -- they are just objects with keys which are numeric strings.\nThese should be replaced by \"real\" Lua arrays, so we can use (presumably\nfast) integer access to a native table and not have to convert every\nnumber offset into a string.\n\nStrings are representing using a 'cons' like structure, which\npreserves JavaScript's performance expectations related to string\nconcatenation.  Strings are converted to UTF-8 and then prefixed to\nindex into the Lua backing storage for object slots.  As mentioned\nabove, this ensures that `foo.bar` from Lua will invoke `__index` from\nthe metatable and not accidentally hit the backing storage for\nproperty `bar`, but it's possible that we could improve performance by\nusing the UTF-16 strings directly as keys.  We don't use `__index`\ninside the bytecode interpreter, so this only affects Lua\ninteroperability.\n\nWe probably want to introduce a \"integer string\" type, to represent\nproperty accesses using numerical indexes.  In the common case that\nthe receiver was an array, we'd use the integer value directly to\nindex backing storage, instead of (slow) conversion to a string.\nWe'd transparently convert back and forth from \"integer string\" to\n\"real string\" in the corner cases (plain object access using integer\nindex / array access using a string).  Alternatively we could\nbreak from the ECMAScript standard and allow numbers to be\n[Property Keys](https://tc39.es/ecma262/#sec-topropertykey)\n(in the language of the spec) and only convert once we'd passed\nthe possible dispatch to Array's\n[`DefineOwnProperty`](https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc).\n\nCurrently bytecode is interpreted; a logical next step would be to\ncompile directly to Lua code and eliminate the overhead of the\ninterpretation loop.  We probably want to precede this with some\nadditional analysis in the TurtleScript compiler.  A first step\nwould be escape analysis and the introduction of a `PUSH_LOCAL_FRAME`\nopcode to complement `PUSH_FRAME`.  The \"local frame\" would be used\nfor those variables which don't escape the current function, and wouldn't\nbe included in the execution context of functions created in its scope.\nA simple runtime would treat `PUSH_FRAME` and `PUSH_LOCAL_FRAME` as\nidentical, but a more advanced runtime would recognize that properties\nof the local frame can be stored in registers and don't actually need\nto be implemented as `Get`/`Set` on a literal local frame object.\n\nIndicating the borders of the control flow blocks in the bytecode would\nalso be useful to transform `JMP` and `JMP_UNLESS` into balanced\n`if/then/else` blocks.\n\nValues could be represented as a pair of \"metatable\" and \"value\" to\navoid redundant `getmetatable(value)` calls during dispatching.\nInstead of implementing `BI_ADD` as:\n```\nprop = jsval.newString('foo')\nresult = getmetatable(left).__add(left, right, env)\ngetmetatable(object).Set(env, object, prop, result)\n```\nwe could write:\n```\nprop_meta, prop = StringMT, jsval.newStringIntern('foo')\nresult_meta, result = left_meta._add(left_meta, left, right_meta, right, env)\nobject_meta.Set(env, object_meta, object, prop_meta, prop, result_meta, result)\n```\nA follow-on optimization would do basic constant/type propagation to further\noptimize this to:\n```\nresult_meta, result = NumberMT._add(NumberMT, 5, right_meta, right, env)\nObjectMT.Set(ObjectMT, object, StringMT, jsval.newStringIntern('foo'), result_meta, result)\n```\nIf right_meta is also known to be NumberMT the first line can become:\n```\nresult_meta, result = NumberMT, NumberMT:from(5 + right.value)\n```\nFinally, we can 'unbox' primitive types when they are stored in registers\nand re-box on storage (or when the types become unknown at a merge point)\nto get:\n```\nprop_meta, prop = StringMT, '\\0f\\0o\\0o'\nresult_meta, result = NumberMT, (5 + right)\nobject_meta.Set(object_meta, object, StringMT, StringMT:fromUtf16(prop), NumberMT, NumberMT:from(result))\n```\nNote that `prop_meta` and `result_meta` are constants here and thus\nunused (for example, we've just substituted their values in the call\nto `object_meta.Set`); I've written assignments for them above just\nfor clarity.\n\nWe may have to introduce explicit\n[PHI and SIGMA](https://en.wikipedia.org/wiki/Static_single_assignment_form)\nfunctions in the bytecode to facilitate the representation of the analysis\nresults to the code generator.\n\n## Future research\n\nI would like to explore multilingual JavaScript using this platform.\nThere are some thoughts in\n[Wikimedia phabricator](https://phabricator.wikimedia.org/T230665);\n[Babylscript](http://www.babylscript.com/) also appears very interesting.\n\nFor that matter, multilingual Lua might be a better first step, the\nLua language is extremely compact!\n\nNOTES:\nIn lua, any type can be a table key, but there is syntactic sugar for using\nstrings as keys:\n```\npoint = { x = 10, y = 20 }   -- Create new table\nprint(point[\"x\"])            -- Prints 10\nprint(point.x)               -- Has exactly the same meaning as line above. The easier-to-read dot notation is just syntactic sugar.\n```\nIn Multilinugal lua, the 'sugar' would be internationalized: \"symbols\"\nare the default keys, and the `_(\"xxx\")` constructor makes a symbol out\nof a string.\n```\npoint = { [_(\"x\")] = 10, [_(\"y\")] = 20 }\nprint(point[_(\"x\")])            -- Prints 10\nprint(point.x)               -- Has exactly the same meaning as line above.\n```\n\n(Note that `point` is also effectively _(\"point\") as well.)\n\nTwo issues:\n1. How does `_(\"x\")` know what the \"current language\" is?  (That is,\n   `_(\"net\")` and `_(\"red\")` should create the exact same symbol if\n   the current language is English or Spanish, respectively.)\n2. How to disambiguate if `foo.x` and `bar.x` are actually translated\ndifferently?   For example, for `fish.net` the symbol naming the\nproperty is likely different from the symbol naming the property in\n`ether.net`, even though the english translation of both symbols is\nthe same.\n\nIn multilingual JS, we had a special import statement and #foo syntax to\nmark \"symbols\" as a separate type.\n```\npoint = { #x = 10, #y = 20 }\nprint(point[#x])            -- Prints 10\nprint(point.x)               -- Has exactly the same meaning as line above.\n```\nDo we need declare #x and point, etc? Also, `#foo` is already used in lua\nfor the 'length' operator.\n\nLua has the meta table function `__index` which could help:\n```\n__index = function(values, n)\n  return values[_(n)]\nend\n```\n-- or maybe this goes the other way, in the sense that `__index` can be used\nto convert from symbols to strings or integer indexes so you can get\nfastpath behavior from the lua jit.  In other words, code running in\nits English translation would have an `__index` method which\ntranslated English property names to \"symbols\" (wikidata entity ids),\nand code running in Spanish translation would have an `__index` method\nwhich translated Spanish property names to \"symbols\", but the code\nwould be interoperable regardless of what language the code was\n\"natively\" written in.\n\n\n## License\n\nTurtleScript and `lua-turtle` are (c) 2020 C. Scott Ananian and\nlicensed under the terms of the GNU GPL v2.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcscott%2Flua-turtle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcscott%2Flua-turtle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcscott%2Flua-turtle/lists"}