From c1c32688a4afc43f6addb99b8b5fa878944a70e3 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 14 Jan 2019 15:42:48 -0800 Subject: Overhaul in progress, mostly working. Moved all the nomsu packages into lib/, including core/*. Changes to how nomsu environments and importing work. --- .gitignore | 3 +- Makefile | 41 ++-- README.md | 15 +- bootstrap.lua | 6 + bootstrap.moon | 2 + compatibility/compatibility.nom | 42 ++-- compatibility/init.nom | 19 ++ consolecolors.lua | 41 ---- core/collections.nom | 145 ------------ core/control_flow.nom | 489 ---------------------------------------- core/coroutines.nom | 40 ---- core/errors.nom | 130 ----------- core/id.nom | 66 ------ core/init.nom | 11 - core/io.nom | 29 --- core/math.nom | 212 ----------------- core/metaprogramming.nom | 454 ------------------------------------- core/operators.nom | 263 --------------------- core/text.nom | 78 ------- error_handling.lua | 23 +- error_handling.moon | 15 +- examples/how_do_i.nom | 6 +- files.lua | 8 - files.moon | 7 - lib/base64.nom | 50 ---- lib/base64/init.nom | 50 ++++ lib/commandline/init.nom | 11 + lib/consolecolor.nom | 26 --- lib/consolecolor/init.nom | 26 +++ lib/core/collections.nom | 145 ++++++++++++ lib/core/control_flow.nom | 489 ++++++++++++++++++++++++++++++++++++++++ lib/core/coroutines.nom | 40 ++++ lib/core/errors.nom | 131 +++++++++++ lib/core/id.nom | 66 ++++++ lib/core/init.nom | 11 + lib/core/io.nom | 29 +++ lib/core/math.nom | 212 +++++++++++++++++ lib/core/metaprogramming.nom | 459 +++++++++++++++++++++++++++++++++++++ lib/core/operators.nom | 263 +++++++++++++++++++++ lib/core/text.nom | 78 +++++++ lib/file_hash.nom | 60 ----- lib/file_hash/init.nom | 60 +++++ lib/filesystem/init.nom | 36 +++ lib/os.nom | 49 ---- lib/progressbar/init.nom | 16 ++ lib/shell/init.nom | 12 + lib/things.nom | 2 +- lib/tools/find.nom | 95 ++++++++ lib/tools/format.nom | 45 ++++ lib/tools/parse.nom | 47 ++++ lib/tools/repl.nom | 86 +++++++ lib/tools/replace.nom | 147 ++++++++++++ lib/tools/test.nom | 65 ++++++ lib/tools/upgrade.nom | 43 ++++ nomsu.lua | 87 ++++--- nomsu.moon | 69 +++--- nomsu_compiler.lua | 2 + nomsu_compiler.moon | 2 + nomsu_environment.lua | 68 ++++-- nomsu_environment.moon | 47 ++-- string2.lua | 6 + string2.moon | 3 + tools/find.nom | 93 -------- tools/format.nom | 43 ---- tools/parse.nom | 45 ---- tools/repl.nom | 84 ------- tools/replace.nom | 145 ------------ tools/test.nom | 44 ---- tools/upgrade.nom | 41 ---- 69 files changed, 2950 insertions(+), 2823 deletions(-) create mode 100644 compatibility/init.nom delete mode 100644 consolecolors.lua delete mode 100644 core/collections.nom delete mode 100644 core/control_flow.nom delete mode 100644 core/coroutines.nom delete mode 100644 core/errors.nom delete mode 100644 core/id.nom delete mode 100644 core/init.nom delete mode 100644 core/io.nom delete mode 100644 core/math.nom delete mode 100644 core/metaprogramming.nom delete mode 100644 core/operators.nom delete mode 100644 core/text.nom delete mode 100644 lib/base64.nom create mode 100644 lib/base64/init.nom create mode 100644 lib/commandline/init.nom delete mode 100644 lib/consolecolor.nom create mode 100644 lib/consolecolor/init.nom create mode 100644 lib/core/collections.nom create mode 100644 lib/core/control_flow.nom create mode 100644 lib/core/coroutines.nom create mode 100644 lib/core/errors.nom create mode 100644 lib/core/id.nom create mode 100644 lib/core/init.nom create mode 100644 lib/core/io.nom create mode 100644 lib/core/math.nom create mode 100644 lib/core/metaprogramming.nom create mode 100644 lib/core/operators.nom create mode 100644 lib/core/text.nom delete mode 100644 lib/file_hash.nom create mode 100644 lib/file_hash/init.nom create mode 100644 lib/filesystem/init.nom delete mode 100644 lib/os.nom create mode 100644 lib/progressbar/init.nom create mode 100644 lib/shell/init.nom create mode 100755 lib/tools/find.nom create mode 100755 lib/tools/format.nom create mode 100755 lib/tools/parse.nom create mode 100755 lib/tools/repl.nom create mode 100755 lib/tools/replace.nom create mode 100755 lib/tools/test.nom create mode 100755 lib/tools/upgrade.nom delete mode 100755 tools/find.nom delete mode 100755 tools/format.nom delete mode 100755 tools/parse.nom delete mode 100755 tools/repl.nom delete mode 100755 tools/replace.nom delete mode 100755 tools/test.nom delete mode 100755 tools/upgrade.nom diff --git a/.gitignore b/.gitignore index ab1178f..458db87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -core/*.lua lib/*.lua -tests/*.lua +lib/*/*.lua .pending-post-commit diff --git a/Makefile b/Makefile index 6f4befe..2497dde 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,11 @@ UNINSTALL_VERSION= MOON_FILES= code_obj.moon error_handling.moon files.moon nomsu.moon nomsu_compiler.moon \ syntax_tree.moon containers.moon bitops.moon parser.moon pretty_errors.moon \ string2.moon nomsu_decompiler.moon nomsu_environment.moon bootstrap.moon -LUA_FILES= code_obj.lua consolecolors.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \ +LUA_FILES= code_obj.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \ syntax_tree.lua containers.lua bitops.lua parser.lua pretty_errors.lua \ string2.lua nomsu_decompiler.lua nomsu_environment.lua bootstrap.lua -CORE_NOM_FILES= $(wildcard core/**.nom) -CORE_LUA_FILES= $(patsubst %.nom,%.lua,$(CORE_NOM_FILES)) -LIB_NOM_FILES= $(wildcard lib/**.nom) +CORE_NOM_FILES=$(shell cat lib/core/init.nom | sed -n 's;export "\(.*\)";lib/\1.nom;p') lib/core/init.nom +LIB_NOM_FILES= $(CORE_NOM_FILES) $(wildcard lib/*.nom) $(filter-out $(CORE_NOM_FILES),$(wildcard lib/*/*.nom)) LIB_LUA_FILES= $(patsubst %.nom,%.lua,$(LIB_NOM_FILES)) PEG_FILES= $(wildcard nomsu.*.peg) GET_VERSION= $(LUA_BIN) nomsu.lua --version @@ -29,9 +28,9 @@ all: lua optimize .PHONY: test test: lua optimize @echo "\033[1;4mRunning unoptimized tests...\033[0m" - @$(LUA_BIN) nomsu.lua -O0 tools/test.nom $(CORE_NOM_FILES) $(LIB_NOM_FILES) + @$(LUA_BIN) nomsu.lua -O0 -t test $(LIB_NOM_FILES) @echo "\n\033[1;4mRunning optimized tests...\033[0m" - @$(LUA_BIN) nomsu.lua -O1 tools/test.nom $(CORE_LUA_FILES) $(LIB_LUA_FILES) + @$(LUA_BIN) nomsu.lua -O1 -t test $(LIB_LUA_FILES) %.lua: %.moon @moonc $< @@ -40,18 +39,18 @@ test: lua optimize @$(LUA_BIN) nomsu.lua -c $< .DELETE_ON_ERROR: version -version: $(LUA_FILES) $(CORE_NOM_FILES) $(LIB_NOM_FILES) +version: $(LUA_FILES) $(LIB_NOM_FILES) @$(LUA_BIN) nomsu.lua --version > version || exit lua: $(LUA_FILES) .PHONY: optimize -optimize: lua $(CORE_LUA_FILES) $(LIB_LUA_FILES) +optimize: lua $(LIB_LUA_FILES) .PHONY: clean clean: @echo "\033[1mDeleting...\033[0m" - @rm -rvf version core/**.lua lib/**.lua tools/**.lua compatibility/**.lua + @rm -rvf version lib/*.lua lib/*/*.lua compatibility/*.lua .PHONY: install install: lua version optimize @@ -74,12 +73,12 @@ install: lua version optimize fi; \ version="`cat version`"; \ mkdir -pv $$prefix/bin $$prefix/lib/nomsu/$$version $$prefix/share/nomsu/$$version $$prefix/share/man/man1 $$packagepath/nomsu \ - && echo "#!$(LUA_BIN)\\nlocal NOMSU_VERSION, NOMSU_PREFIX, NOMSU_PACKAGEPATH = [[$$version]], [[$$prefix]], [[$$packagepath]]" \ + && echo "#!$(LUA_BIN)\\nlocal NOMSU_VERSION, NOMSU_PREFIX, NOMSU_PACKAGEPATH = [[$$version]], [[$$prefix]], [[$$packagepath/nomsu]]" \ | cat - nomsu.lua > $$prefix/bin/nomsu$$version \ && chmod +x $$prefix/bin/nomsu$$version \ && cp -v nomsu $$prefix/bin \ && cp -v doc/nomsu.1 $$prefix/share/man/man1 \ - && cp -rv $(LUA_FILES) $(PEG_FILES) core lib compatibility tools $$prefix/share/nomsu/$$version; + && cp -rv $(LUA_FILES) $(PEG_FILES) lib compatibility $$prefix/share/nomsu/$$version; .PHONY: uninstall uninstall: version @@ -88,17 +87,10 @@ uninstall: version read -p $$'\033[1mWhere do you want to uninstall Nomsu from? (default: /usr/local) \033[0m' prefix; \ fi; \ if [[ ! $$prefix ]]; then prefix="/usr/local"; fi; \ - packagepath="$(PACKAGEPATH)"; \ - if [[ ! $$packagepath ]]; then \ - read -p $$'\033[1mWhere have your Nomsu packages been installed? (default: /opt) \033[0m' packagepath; \ - fi; \ - if [[ ! $$packagepath ]]; then packagepath="/opt"; fi; \ echo "\033[1mNomsu will be uninstalled from:\033[0m"; \ echo " $$prefix/bin"; \ echo " $$prefix/lib"; \ echo " $$prefix/share"; \ - echo "\033[1mNomsu packages will be uninstalled from:\033[0m"; \ - echo " $$packagepath/nomsu"; \ read -p $$'\033[1mis this okay? [Y/n]\033[0m ' ans; \ if [[ $$ans =~ ^[Nn] ]]; then exit; fi; \ echo "\033[1mDeleting...\033[0m"; \ @@ -118,12 +110,17 @@ uninstall: version fi; \ if [ "`ls $$prefix/lib/nomsu 2>/dev/null`" == "" ]; then rm -rvf $$prefix/lib/nomsu; fi;\ if [ "`ls $$prefix/share/nomsu 2>/dev/null`" == "" ]; then rm -rvf $$prefix/share/nomsu; fi;\ + echo $$'\033[1mDone.\033[0m'; + +uninstallpackages: + @packagepath="$(PACKAGEPATH)"; \ + if [[ ! $$packagepath ]]; then \ + read -p $$'\033[1mWhere have your Nomsu packages been installed? (default: /opt) \033[0m' packagepath; \ + fi; \ + if [[ ! $$packagepath ]]; then packagepath="/opt"; fi; \ if [ -d $$packagepath/nomsu ]; then \ read -p $$'\033[1mDo you want to delete all installed libraries from /opt? [y/n] \033[0m' confirm; \ if [[ $$confirm == "y" ]]; then \ rm -rvf $$packagepath/nomsu; \ fi; \ - fi; \ - echo $$'\033[1mDone.\033[0m'; - -# eof + fi; diff --git a/README.md b/README.md index a8873f0..695fbde 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,7 @@ All `.moon` files have been precompiled into corresponding `.lua` files, so you * [string2.moon](string2.moon) - A library defining some extra functionality for strings. * [syntax\_tree.moon](syntax_tree.moon) - Datastructures used for Nomsu Abstract Syntax Trees. * [examples/how\_do\_i.nom](examples/how_do_i.nom) - A simple walkthrough of some of the features of Nomsu, written in Nomsu code. **This is a good place to start.** -* [core/\*.nom](core) - Core language definitions of stuff like control flow, operators, and metaprogramming, broken down into different files. -* [lib/\*.nom](lib) - Optional language libraries for stuff you might want, like interfacing with the OS, or doing Object Oriented Programming. +* [lib/\*/\*.nom](lib) - Language libraries, including the core language stuff like control flow, operators, and metaprogramming (in [lib/core](lib/core)) and optional language libraries for stuff you might want. * [compatibility/\*.nom](compatibility) - Code for automatically upgrading Nomsu code from old versions to the current version. * [tools/\*.nom](tools) - A set of utilities useful for doing code manipulation actions. * [Makefile](Makefile) - Rules for building/installing the compiler. @@ -72,14 +71,14 @@ All `.moon` files have been precompiled into corresponding `.lua` files, so you ## Versioning -Nomsu uses the following versioning scheme: `[syntax version].[core library API version].[compiler internal API version].[lib/ API version]`. Which means: +Nomsu uses the following versioning scheme: `[syntax version].[core library API version].[compiler internal API version].[minor version]`. Which means: -* Any code that parses on `Nomsu X.a.b.c.d` will also parse on Nomsu `X.p.w.r.s` -* Any code that compiles on Nomsu `X.Y.a.b.c` will also compile on Nomsu `X.Y.p.q.r` and run without any differences, as long as it only depends on the behavior of the core library functions (i.e. stuff defined in [core/\*.nom](core)), and doesn't mess with the compiler internals at all. -* Any code that compiles on Nomsu `X.Y.Z.a.b` will also compile on Nomsu `X.Y.Z.p.q` and run without any differences, even if it messes with the compiler internals, as long as it doesn't use anything from [lib/\*.nom](lib). -* Any code that compiles on Nomsu `X.Y.Z.W` will also compile on any other Nomsu `X.Y.Z.W` and run without any differences, even if it uses stuff from [lib/\*.nom](lib). +* Any code that parses on `Nomsu X.a.b.c` will also parse on Nomsu `X.p.w.r` +* Any code that compiles on Nomsu `X.Y.a.b` will also compile on Nomsu `X.Y.p.q` and run without any differences, as long as it only depends on the behavior of the core library functions (i.e. stuff defined in [lib/core/\*.nom](lib/core)), and doesn't mess with the compiler internals at all. +* Any code that compiles on Nomsu `X.Y.Z.a` will also compile on Nomsu `X.Y.Z.p` and run without any differences, unless if it messes with the compiler internals. +* Any code that compiles on Nomsu `X.Y.Z.W` will also compile on any other Nomsu `X.Y.Z.W` and run without any differences. -When Nomsu is istalled via `make install`, all of Nomsu's lua files and [core/\*.nom](core) and [lib/\*.nom](lib) files are stored in `$PREFIX/lib/nomsu/$NOMSU_VERSION` and the Nomsu executable is installed to `$PREFIX/bin/nomsu$NOMSU_VERSION`, along with the file `nomsu` (the version-selection script), which goes to `$PREFIX/bin/nomsu`. When `make uninstall` is run, all those files are deleted (except for `nomsu`, if there are other versions installed). +When Nomsu is istalled via `make install`, all of Nomsu's lua files and [lib/](lib) files are stored in `$PREFIX/share/nomsu/$NOMSU_VERSION` and the Nomsu executable is installed to `$PREFIX/bin/nomsu$NOMSU_VERSION`, along with the file `nomsu` (the version-selection script), which goes to `$PREFIX/bin/nomsu`. When `make uninstall` is run, all those files are deleted (except for `nomsu`, if there are other versions installed). To run different versions, use the `-V` flag, which will select the latest version matching the specified pattern. For example, if you have v1.0.0.0, v1.0.2.1, and 1.1.0.0 installed, then `nomsu` will run v1.1.0.0, `nomsu -V 1.0` will run v1.0.2.1, and `nomsu -V 1.0.0.0` will run v1.0.0.0. diff --git a/bootstrap.lua b/bootstrap.lua index 10d6f3b..c257965 100644 --- a/bootstrap.lua +++ b/bootstrap.lua @@ -157,6 +157,12 @@ local compile_actions = { end, ["nomsu environment name"] = function(self) return LuaCode('"_ENV"') + end, + ["this file was run directly"] = function(self) + return LuaCode('WAS_RUN_DIRECTLY') + end, + ["the command line arguments"] = function(self) + return LuaCode('COMMAND_LINE_ARGS') end } return compile_actions diff --git a/bootstrap.moon b/bootstrap.moon index f5b5900..05bd2a6 100644 --- a/bootstrap.moon +++ b/bootstrap.moon @@ -118,6 +118,8 @@ compile_actions = { ["Lua version"]: (code)=> LuaCode("_VERSION") ["nomsu environment"]: ()=> LuaCode("_ENV") ["nomsu environment name"]: ()=> LuaCode('"_ENV"') + ["this file was run directly"]: => LuaCode('WAS_RUN_DIRECTLY') + ["the command line arguments"]: => LuaCode('COMMAND_LINE_ARGS') } return compile_actions diff --git a/compatibility/compatibility.nom b/compatibility/compatibility.nom index e0449c8..444be11 100644 --- a/compatibility/compatibility.nom +++ b/compatibility/compatibility.nom @@ -3,7 +3,7 @@ This file contains code for defining ways to upgrade code between different versions of Nomsu. -use "lib/os" +use "filesystem" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -68,18 +68,33 @@ externally [ $tree upgraded to $end_version from $start_version ] all mean: unless ($tree is syntax tree): return $tree + ($ver as list) means (($ as number) for $ in $ver matching "[0-9]+") + + (Ver $) means: + [$lib, $ver] = ($, match "(.*)/([0-9.]+)") + if $lib: + return {.lib = $lib, .version = ($ver as list)} + return {.version = ($ as list)} + + $start = (Ver $start_version) + $end = (Ver $end_version) + assume $start.lib == $end.lib + + $seen = {} $versions = {} - for $v = $ in $UPGRADES: - $versions.$v = (yes) - - for $v = $ in $ACTION_UPGRADES: - $versions.$v = (yes) + for $v = $ in $UPGRADES: $versions.$v = (yes) + for $v = $ in $ACTION_UPGRADES: $versions.$v = (yes) + $versions = [ + :for $v = $ in $versions: + if ((Ver $v).lib == $start.lib): + add $v + ] - $versions = ((keys in $versions) sorted by $ -> ($ as list)) + sort $versions by $ -> ($ as list) for $ver in $versions: - if (($ver as list) <= ($start_version as list)): do next $ver - if (($ver as list) > ($end_version as list)): stop $ver + if (($ver as list) <= $start.version): do next $ver + if (($ver as list) > $end.version): stop $ver if $ACTION_UPGRADES.$ver: $tree = $tree with $ ->: @@ -115,12 +130,3 @@ externally ($tree upgraded to $end_version) means externally ($tree upgraded) means $tree upgraded from ($tree.version or (Nomsu version)) to (Nomsu version) - -externally (use $path from version $version) means: - for $filename in (files for $path): - if (=lua "LOADED[\$filename]"): - do next $filename - $file = (read file $filename) - $tree = (parse $file from $filename) - $tree = (upgrade $tree from $version) - run tree $tree diff --git a/compatibility/init.nom b/compatibility/init.nom new file mode 100644 index 0000000..056428f --- /dev/null +++ b/compatibility/init.nom @@ -0,0 +1,19 @@ + +export "compatibility/compatibility" +export "compatibility/2" +export "compatibility/2.3" +export "compatibility/2.4" +export "compatibility/2.5" +export "compatibility/2.5.5.5" +export "compatibility/3" +export "compatibility/3.5.5.6" +export "compatibility/3.6" +export "compatibility/3.7" +export "compatibility/3.8" +export "compatibility/4.8.10" +export "compatibility/4.9" +export "compatibility/4.10.12.7" +export "compatibility/4.11" +export "compatibility/4.12" +export "compatibility/5.13" +export "compatibility/6.14" diff --git a/consolecolors.lua b/consolecolors.lua deleted file mode 100644 index 650bc81..0000000 --- a/consolecolors.lua +++ /dev/null @@ -1,41 +0,0 @@ --- --- This file contains the ANSI escapes for console colors --- - -local colors = { - -- attributes - reset = 0, - clear = 0, - bright = 1, - dim = 2, - underscore = 4, - blink = 5, - reverse = 7, - hidden = 8, - - -- foreground - black = 30, - red = 31, - green = 32, - yellow = 33, - blue = 34, - magenta = 35, - cyan = 36, - white = 37, - - -- background - onblack = 40, - onred = 41, - ongreen = 42, - onyellow = 43, - onblue = 44, - onmagenta = 45, - oncyan = 46, - onwhite = 47, -} - -local _M = {} -for c, v in pairs(colors) do - _M[c] = string.char(27)..("[%dm"):format(v) -end -return _M diff --git a/core/collections.nom b/core/collections.nom deleted file mode 100644 index 4cf54cd..0000000 --- a/core/collections.nom +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains code that supports manipulating and using collections like lists - and dictionaries. - -use "core/metaprogramming" -use "core/control_flow" -use "core/operators" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# List functionality: -test: - $list = [1, 2, 3, 4, 5] - $visited = {} - for $i = $x in $list: - $visited.$i = (yes) - assume ($visited == {.1, .2, .3, .4, .5}) - $visited = {} - for $x in $list: - $visited.$x = (yes) - assume ($visited == {.1, .2, .3, .4, .5}) - assume (($list, 2 nd to last) == 4) - assume (($list, first) == 1) - assume ($list, has 3) - assume (($list, index of 3) == 3) - assume ((size of $list) == 5) - $list, add 6 - assume (($list, last) == 6) - $list, pop - assume (($list, last) == 5) - $list, remove index 1 - assume (($list, first) == 2) - assume (([1, 2] + [3, 4]) == [1, 2, 3, 4]) - -# Dict functionality -test: - $dict = {.x = 1, .y = 2, .z = 3} - assume (size of $dict) == 3 - assume [: for $k = $v in {.x = 1}: add {.key = $k, .value = $v}] == - [{.key = "x", .value = 1}] - assume ({.x = 1, .y = 1} + {.y = 10, .z = 10}) == {.x = 1, .y = 11, .z = 10} - assume ({.x = 1, .y = 1} | {.y = 10, .z = 10}) == {.x = 1, .y = 1, .z = 10} - assume ({.x = 1, .y = 1} & {.y = 10, .z = 10}) == {.y = 1} - assume ({.x = 1, .y = 1} ~ {.y = 10, .z = 10}) == {.x = 1, .z = 10} - -test: - assume (([[1, 2], [3, 4]] flattened) == [1, 2, 3, 4]) - -externally ($lists flattened) means: - $flat = [] - for $item in recursive $lists: - if ($item is a "List"): - for $ in $item: - recurse $item on $ - ..else: - $flat, add $item - return $flat - -test: - assume ((entries in {.x = 1}) == [{.key = "x", .value = 1}]) - -(entries in $dict) parses as [ - : for $k = $v in $dict: - add {.key = $k, .value = $v} -] - -test: - assume ((keys in {.x = 1}) == ["x"]) - -[keys in $dict, keys of $dict] all parse as [: for $k = $v in $dict: add $k] -test: - assume ((values in {.x = 1}) == [1]) -[values in $dict, values of $dict] all parse as [: for $k = $v in $dict: add $v] - -# Metatable stuff -test: - $t = {} - set $t's metatable to {.__tostring = ($ -> "XXX")} - assume ("\$t" == "XXX") - -(set $dict's metatable to $metatable) compiles to - "setmetatable(\($dict as lua expr), \($metatable as lua expr));" - -[$'s metatable, $'metatable] all compile to "getmetatable(\($ as lua expr))" -test: - assume (({} with fallback $ -> ($ + 1)).10 == 11) - -($dict with fallback $key -> $value) compiles to (" - (function(d) - local mt = {} - for k,v in pairs(getmetatable(d) or {}) do mt[k] = v end - mt.__index = function(self, \($key as lua expr)) - local value = \($value as lua expr) - self[\($key as lua expr)] = value - return value - end - return setmetatable(d, mt) - end)(\($dict as lua expr)) -") - -# Sorting -test: - $x = [3, 1, 2] - sort $x - assume ($x == [1, 2, 3]) - sort $x by $ = (- $) - assume ($x == [3, 2, 1]) - $keys = {.1 = 999, .2 = 0, .3 = 50} - sort $x by $ = $keys.$ - assume ($x == [2, 3, 1]) -(sort $items) compiles to "table.sort(\($items as lua expr));" -[sort $items by $item = $key_expr, sort $items by $item -> $key_expr] -..all parse as - do: - $keys = ({} with fallback $item -> $key_expr) - lua> "table.sort(\$items, function(x,y) return \$keys[x] < \$keys[y] end)" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - assume ((sorted [3, 1, 2]) == [1, 2, 3]) - -externally [$items sorted, sorted $items] all mean: - $copy = [: for $ in $items: add $] - sort $copy - return $copy - -[$items sorted by $item = $key, $items sorted by $item -> $key] all parse as - result of: - $copy = [: for $ in $items: add $] - sort $copy by $item = $key - return $copy - -test: - assume ((unique [1, 2, 1, 3, 2, 3]) == [1, 2, 3]) - -externally (unique $items) means: - $unique = [] - $seen = {} - for $ in $items: - unless $seen.$: - $unique, add $ - $seen.$ = (yes) - return $unique diff --git a/core/control_flow.nom b/core/control_flow.nom deleted file mode 100644 index b0c4f27..0000000 --- a/core/control_flow.nom +++ /dev/null @@ -1,489 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains compile-time actions that define basic control flow structures - like "if" statements and loops. - -use "core/metaprogramming" -use "core/operators" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# No-Op -test: - do nothing -(do nothing) compiles to "" - -# Conditionals -test: - if (no): - fail "conditional fail" - -(if $condition $if_body) compiles to (" - if \($condition as lua expr) then - \($if_body as lua) - end -") - -test: - unless (yes): - fail "conditional fail" - -(unless $condition $unless_body) parses as (if (not $condition) $unless_body) -[ - if $condition $if_body else $else_body, unless $condition $else_body else $if_body -] all compile to (" - if \($condition as lua expr) then - \($if_body as lua) - else - \($else_body as lua) - end -") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# Conditional expression (ternary operator) -# Note: this uses a function instead of "(condition and if_expr or else_expr)" - because that breaks if $if_expr is falsey, e.g. "x < 5 and false or 99" -test: - assume ((1 if (yes) else 2) == 1) - assume ((1 if (no) else 2) == 2) - -[ - $when_true_expr if $condition else $when_false_expr - $when_true_expr if $condition otherwise $when_false_expr - $when_false_expr unless $condition else $when_true_expr - $when_false_expr unless $condition then $when_true_expr -] all compile to: - # If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic - equivalent of a conditional expression: (cond and if_true or if_false) - if {.Text, .List, .Dict, .Number}.($when_true_expr.type): - return - Lua (" - (\($condition as lua expr) and \($when_true_expr as lua expr) or \ - ..\($when_false_expr as lua expr)) - ") - ..else: - # Otherwise, need to do an anonymous inline function (yuck, too bad lua - doesn't have a proper ternary operator!) - To see why this is necessary consider: (random()<.5 and false or 99) - return - Lua (" - ((function() - if \($condition as lua expr) then - return \($when_true_expr as lua expr) - else - return \($when_false_expr as lua expr) - end - end)()) - ") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# GOTOs -test: - $i = 0 - --- $loop --- - $i += 1 - unless ($i == 10): - go to $loop - assume ($i == 10) - --- (Loop) --- - $i -= 1 - unless ($i == 0): - go to (Loop) - assume ($i == 0) - -(--- $label ---) compiles to (" - ::label_\( - ($label.stub, as lua id) if ($label.type == "Action") else - $label as lua identifier - ):: -") - -(go to $label) compiles to (" - goto label_\( - ($label.stub, as lua id) if ($label.type == "Action") else - $label as lua identifier - ) -") - -# Basic loop control -(stop $var) compiles to: - if $var: - return (Lua "goto stop_\($var as lua identifier)") - ..else: - return (Lua "break") - -(do next $var) compiles to: - if $var: - return (Lua "goto continue_\($var as lua identifier)") - ..else: - return (Lua "goto continue") - -(---stop $var ---) compiles to "::stop_\($var as lua identifier)::" -(---next $var ---) compiles to "::continue_\($var as lua identifier)::" - -# While loops -test: - $x = 0 - repeat while ($x < 10): $x += 1 - assume ($x == 10) - repeat while ($x < 20): stop - assume ($x == 10) - repeat while ($x < 20): - $x += 1 - if (yes): - do next - fail "Failed to 'do next'" - assume ($x == 20) - -(repeat while $condition $body) compiles to: - $lua = - Lua (" - while \($condition as lua expr) do - \($body as lua) - ") - - if ($body has subtree \(do next)): - $lua, add "\n ::continue::" - - $lua, add "\nend --while-loop" - return $lua - -(repeat $body) parses as (repeat while (yes) $body) -(repeat until $condition $body) parses as (repeat while (not $condition) $body) - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - $nums = [] - for $x in 1 to 5: - $nums, add $x - assume ($nums == [1, 2, 3, 4, 5]) - $nums = [] - for $x in 1 to 5 via 2: - $nums, add $x - assume ($nums == [1, 3, 5]) - $nums = [] - for $outer in 1 to 100: - for $inner in $outer to ($outer + 2): - if ($inner == 2): - $nums, add -2 - do next $inner - $nums, add $inner - if ($inner == 5): - stop $outer - assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5]) - -# Numeric range for loops -[ - for $var in $start to $stop by $step $body - for $var in $start to $stop via $step $body -] all compile to: - # This uses Lua's approach of only allowing loop-scoped variables in a loop - $lua = - Lua (" - for \($var as lua identifier)=\($start as lua expr),\($stop as lua expr),\ - ..\($step as lua expr) do - ") - $lua, add "\n " ($body as lua) - if ($body has subtree \(do next)): - $lua, add "\n ::continue::" - - if ($body has subtree \(do next $var)): - $lua, add "\n " (\(---next $var ---) as lua) - - $lua, add "\nend -- numeric for " ($var as lua identifier) " loop" - if ($body has subtree \(stop $var)): - $lua = - Lua (" - do -- scope for (stop \($var as lua identifier)) - \$lua - \(\(---stop $var ---) as lua) - end -- scope for (stop \($var as lua identifier)) - ") - return $lua - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(for $var in $start to $stop $body) parses as - for $var in $start to $stop via 1 $body - -test: - $x = 0 - repeat 5 times: - $x += 1 - assume $x == 5 - -(repeat $n times $body) parses as (for (=lua "_XXX_") in 1 to $n $body) -test: - $a = [10, 20, 30, 40, 50] - $b = [] - for $x in $a: - $b, add $x - assume ($a == $b) - $b = [] - for $x in $a: - if ($x == 10): - do next $x - - if ($x == 50): - stop $x - - $b, add $x - assume ($b == [20, 30, 40]) - -# For-each loop (lua's "ipairs()") -(for $var in $iterable at $i $body) compiles to: - # This uses Lua's approach of only allowing loop-scoped variables in a loop - $lua = - Lua (" - for \($i as lua identifier),\($var as lua identifier) in ipairs(\($iterable as lua expr)) do - \; - ") - $lua, add ($body as lua) - if ($body has subtree \(do next)): - $lua, add "\n ::continue::" - - if ($body has subtree \(do next $var)): - $lua, add "\n " (\(---next $var ---) as lua) - - $lua, add "\nend --for \($var as lua identifier) loop" - if ($body has subtree \(stop $var)): - $inner_lua = $lua - $lua = (Lua "do -- scope for stopping for-loop\n ") - $lua, add $inner_lua "\n " - $lua, add (\(---stop $var ---) as lua) - $lua, add "\nend -- end of scope for stopping for-loop" - return $lua - -(for $var in $iterable $body) parses as - for $var in $iterable at (=lua "__") $body - -test: - $d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50} - $result = [] - for $k = $v in $d: - if ($k == "a"): - do next $k - - if ($v == 20): - do next $v - - $result, add "\$k = \$v" - assume (($result sorted) == ["c = 30", "d = 40", "e = 50"]) - -# Dict iteration (lua's "pairs()") -[for $key = $value in $iterable $body, for $key $value in $iterable $body] -..all compile to: - $lua = - Lua (" - for \($key as lua identifier),\($value as lua identifier) in pairs(\ - ..\($iterable as lua expr)) do - ") - $lua, add "\n " ($body as lua) - if ($body has subtree \(do next)): - $lua, add "\n ::continue::" - - if ($body has subtree \(do next $key)): - $lua, add "\n " (\(---next $key ---) as lua) - - if ($body has subtree \(do next $value)): - $lua, add "\n " (\(---next $value ---) as lua) - - $lua, add "\nend --foreach-loop" - $stop_labels = (Lua "") - if ($body has subtree \(stop $key)): - $stop_labels, add "\n" (\(---stop $key ---) as lua) - - if ($body has subtree \(stop $value)): - $stop_labels, add "\n" (\(---stop $value ---) as lua) - - if ((size of "\$stop_labels") > 0): - $inner_lua = $lua - $lua = (Lua "do -- scope for stopping for $ = $ loop\n ") - $lua, add $inner_lua $stop_labels "\nend" - - return $lua - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - when: - (1 == 2) (100 < 0): - fail "bad conditional" - (1 == 0) (1 == 1) $not_a_variable.x: do nothing - (1 == 1): - fail "bad conditional" - - (1 == 2): - fail "bad conditional" - - else: - fail "bad conditional" - -# Multi-branch conditional (if..elseif..else) -(when $body) compiles to: - $code = (Lua "") - $clause = "if" - $else_allowed = (yes) - unless ($body.type is "Block"): - compile error at $body "'if' expected a Block, but got a \($body.type)." - "Perhaps you forgot to put a ':' after 'if'?" - - for $line in $body: - unless - (($line.type is "Action") and ((size of $line) >= 2)) and - $line.(size of $line) is "Block" syntax tree - ..: - compile error at $line "Invalid line for the body of an 'if' block." (" - Each line should contain one or more conditional expressions followed by a block, or "else"\ - .. followed by a block. - ") - $action = $line.(size of $line) - if (($line.1 is "else") and ((size of $line) == 2)): - unless $else_allowed: - compile error at $line "You can't have two 'else' blocks." - "Merge all of the 'else' blocks together." - - unless ((size of "\$code") > 0): - compile error at $line - .."You can't have an 'else' block without a preceding condition" (" - If you want the code in this block to always execute, you don't need a conditional block \ - ..around it. Otherwise, make sure the 'else' block comes last. - ") - - $code, add "\nelse\n " ($action as lua) - $else_allowed = (no) - ..else: - $code, add $clause " " - for $i in 1 to ((size of $line) - 1): - if ($i > 1): - $code, add " or " - $code, add ($line.$i as lua expr) - $code, add " then\n " ($action as lua) - $clause = "\nelseif" - - if ((size of "\$code") == 0): - compile error at $body "'if' block has an empty body." - "This means nothing would happen, so the 'if' block should be deleted." - - $code, add "\nend --when" - return $code - -test: - if 5 is: - 1 2 3: - fail "bad switch statement" - - 4 5: - do nothing - - 5 6: - fail "bad switch statement" - - else: - fail "bad switch statement" - -# Switch statement -[if $branch_value is $body, when $branch_value is $body] all compile to: - $code = (Lua "") - $clause = "if" - $else_allowed = (yes) - define mangler - unless ($body.type is "Block"): - compile error at $body "'if' expected a Block, but got a \($body.type)" - "Perhaps you forgot to put a ':' after the 'is'?" - - for $line in $body: - unless - (($line.type is "Action") and ((size of $line) >= 2)) and - $line.(size of $line) is "Block" syntax tree - ..: - compile error at $line "Invalid line for 'if' block." (" - Each line should contain expressions followed by a block, or "else" followed by a block - ") - $action = $line.(size of $line) - if (($line.1 is "else") and ((size of $line) == 2)): - unless $else_allowed: - compile error at $line "You can't have two 'else' blocks." - "Merge all of the 'else' blocks together." - - unless ((size of "\$code") > 0): - compile error at $line - .."You can't have an 'else' block without a preceding condition" (" - If you want the code in this block to always execute, you don't need a conditional block \ - ..around it. Otherwise, make sure the 'else' block comes last. - ") - - $code, add "\nelse\n " ($action as lua) - $else_allowed = (no) - ..else: - $code, add $clause " " - for $i in 1 to ((size of $line) - 1): - if ($i > 1): - $code, add " or " - $code, add "\(mangle "branch value") == " ($line.$i as lua expr) - $code, add " then\n " ($action as lua) - $clause = "\nelseif" - - if ((size of "\$code") == 0): - compile error at $body "'if' block has an empty body." - "This means nothing would happen, so the 'if' block should be deleted." - - $code, add "\nend --when" - return - Lua (" - do --if $ is... - local \(mangle "branch value") = \($branch_value as lua expr) - \$code - end -- if $ is... - ") - -# Do/finally -(do $action) compiles to (" - do - \($action as lua) - end -- do -") - -test: - assume ((result of: return 99) == 99) - -# Inline thunk: -(result of $body) compiles to "\(\(-> $body) as lua)()" -test: - $t = [1, [2, [[3], 4], 5, [[[6]]]]] - $flat = [] - for $ in recursive $t: - if ((lua type of $) is "table"): - for $2 in $: - recurse $ on $2 - ..else: - $flat, add $ - assume (sorted $flat) == [1, 2, 3, 4, 5, 6] - -# Recurion control flow -(recurse $v on $x) compiles to - Lua "table.insert(_stack_\($v as lua expr), \($x as lua expr))" -(for $var in recursive $structure $body) compiles to: - $lua = - Lua (" - do - local _stack_\($var as lua expr) = List{\($structure as lua expr)} - while #_stack_\($var as lua expr) > 0 do - \($var as lua expr) = table.remove(_stack_\($var as lua expr), 1) - \($body as lua) - ") - - if ($body has subtree \(do next)): - $lua, add "\n ::continue::" - - if ($body has subtree \(do next $var)): - $lua, add "\n \(\(---next $var ---) as lua)" - - $lua, add "\n end -- Recursive loop" - if ($body has subtree \(stop $var)): - $lua, add "\n \(\(---stop $var ---) as lua)" - $lua, add "\nend -- Recursive scope" - return $lua diff --git a/core/coroutines.nom b/core/coroutines.nom deleted file mode 100644 index 6a99f7e..0000000 --- a/core/coroutines.nom +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines the code that creates and manipulates coroutines - -use "core/metaprogramming" -use "core/operators" -use "core/control_flow" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - $co = - ->: - yield 4 - yield 5 - repeat 3 times: - yield 6 - $nums = [] - for $ in coroutine $co: - $nums, add $ - - unless ($nums == [4, 5, 6, 6, 6]): - fail "Coroutine iteration failed" - - $d = {.x = 0} - $co2 = - coroutine: - $d.x += 1 - yield 1 - $d.x += 1 - yield - $d.x += 1 - repeat while ((coroutine status of $co2) != "dead"): resume $co2 - assume $d.x == 3 -(coroutine $body) parses as (coroutine from (-> $body)) -(for $ in coroutine $co $body) compiles to (" - for \($ as lua expr) in coroutine_wrap(\($co as lua expr)) do - \($body as lua) - end -") diff --git a/core/errors.nom b/core/errors.nom deleted file mode 100644 index 45fc8c5..0000000 --- a/core/errors.nom +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains basic error reporting code - -use "core/metaprogramming" -use "core/operators" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(fail $msg) compiles to "error(\(($msg as lua expr) if $msg else "nil"), 0);" -(assume $condition) compiles to: - lua> (" - local \$assumption = 'Assumption failed: '..tostring((\$condition):get_source_code()) - ") - - return - Lua (" - if not \($condition as lua expr) then - error(\(quote "\$assumption"), 0) - end - ") - -(assume $a == $b) compiles to: - lua> "local \$assumption = 'Assumption failed: '..tostring(\(\($a == $b) as nomsu))" - - define mangler - - return - Lua (" - do - local \(mangle "a"), \(mangle "b") = \($a as lua expr), \($b as lua expr) - if \(mangle "a") ~= \(mangle "b") then - error(\(quote "\$assumption").."\\n"..tostring(\(mangle "a")).." != "..tostring(\ - ..\(mangle "b")), 0) - end - end - ") - -test: - try: fail - $worked = (no) - try: - fail "xx" - ..if it fails with $failure: - $worked = (yes) - ..if it succeeds: - fail "'try' incorrectly ran success case." - assume $failure == "xx" - unless $worked: - fail "'try' failed to recover from failure" - -# Try/except -[ - try $action if it succeeds $success if it fails with $msg $fallback - try $action if it fails with $msg $fallback if it succeeds $success -] all compile to: - $success_lua = ($success as lua) - if ((#"\$success_lua") > 0): - $success_lua, add "\n" - $success_lua, prepend "-- Success:\n" - $success_lua, - add "if not _fell_through then return table.unpack(_result, 2) end" - $fallback_lua = ($fallback as lua) - if ((#"\$fallback_lua") > 0): - $msg_lua = ($msg as lua expr) - if ((#"\$msg_lua") > 0): - $fallback_lua, prepend "\n\$msg_lua = _result[2]\n" - if ($msg_lua, text, is lua id): - $fallback_lua, add free vars [($msg_lua, text)] - $fallback_lua, prepend "-- Failure:" - return - Lua (" - do - local _fell_through = false - local _result = {pcall(function() - \($action as lua) - _fell_through = true - end)} - if _result[1] then - \$success_lua - else - \$fallback_lua - end - end - ") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(try $action) parses as - try $action if it succeeds (do nothing) if it fails (do nothing) - -(try $action if it fails $fallback) parses as - try $action if it succeeds (do nothing) if it fails $fallback - -(try $action if it fails with $msg $fallback) parses as - try $action if it succeeds (do nothing) if it fails with $msg $fallback - -(try $action if it succeeds $success) parses as - try $action if it succeeds $success if it fails (do nothing) - -(try $action if it fails $fallback if it succeeds $success) parses as - try $action if it fails with (=lua "") $fallback if it succeeds $success - -(try $action if it succeeds $success if it fails $fallback) parses as - try $action if it succeeds $success if it fails with (=lua "") $fallback - -test: - $success = (no) - try: - do: fail - ..then always: - $success = (yes) - ..if it succeeds: - fail "'try ... then always ...' didn't propagate failure" - - unless $success: - fail "'try ... then always ...' didn't execute the 'always' code" - -(do $action then always $final_action) compiles to (" - do -- do/then always - local _fell_through = false - local _results = {pcall(function() - \($action as lua) - _fell_through = true - end)} - \($final_action as lua) - if not _results[1] then error(_results[2], 0) end - if not _fell_through then return table.unpack(_results, 2) end - end -") diff --git a/core/id.nom b/core/id.nom deleted file mode 100644 index d2427b5..0000000 --- a/core/id.nom +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - A simple UUID function based on RFC 4122: http://www.ietf.org/rfc/rfc4122.txt - -use "core/metaprogramming" -use "core/operators" -use "core/math" -use "core/collections" -use "core/control_flow" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -$NaN_surrogate = {} -$nil_surrogate = {} -$obj_by_id = {} -set $obj_by_id's metatable to {.__mode = "v"} -$id_by_obj = {} -set $id_by_obj's metatable to { - .__mode = "k" - .__index = - for ($self $key): - if ($key == (nil)): - return $self.$nil_surrogate - - if ($key != $key): - return $self.$NaN_surrogate - - --- (retry) --- - $id = (uuid) - if ($obj_by_id.$id != (nil)): go to (retry) - $self.$key = $id - $obj_by_id.$id = $key - return $id -} - -externally (uuid) means: - # Set all the other bits to randomly (or pseudo-randomly) chosen values. - $bytes = [ - # time-low, time-mid, time-high-and-version - randint (2 ^ (4 * 8)), randint (2 ^ (2 * 8)), randint (2 ^ (2 * 8 - 4)) - # clock-seq-and-reserved, clock-seq-low - randint (2 ^ (1 * 8 - 2)), randint (2 ^ (1 * 8)), randint (2 ^ (3 * 8)) - # node - randint (2 ^ (3 * 8)) - ] - - # Set the four most significant bits (bits 12 through 15) of the - # time_hi_and_version field to the 4-bit version number from - # Section 4.1.3. - $bytes.3 += 0x4000 - - # Set the two most significant bits (bits 6 and 7) of the - # clock_seq_hi_and_reserved to zero and one, respectively. - $bytes.4 += 0xC0 - return (=lua "('%08x-%04x-%04x-%02x%02x-%6x%6x'):format(unpack(\$bytes))") - -# For strict identity checking, use ($x's id) == ($y's id) -test: - assume (([] == []) and ((id of []) != (id of []))) - seed random with 0 - $x = [] - assume ((id of $x) == (id of $x)) - seed random with 0 - assume ((id of $x) != (id of [])) - seed random -externally [id of $, $'s id, $'id] all mean $id_by_obj.$ diff --git a/core/init.nom b/core/init.nom deleted file mode 100644 index 0c8051d..0000000 --- a/core/init.nom +++ /dev/null @@ -1,11 +0,0 @@ -# Export everything -export "core/metaprogramming" -export "core/operators" -export "core/control_flow" -export "core/errors" -export "core/collections" -export "core/coroutines" -export "core/math" -export "core/id" -export "core/io" -export "core/text" diff --git a/core/io.nom b/core/io.nom deleted file mode 100644 index 7afe889..0000000 --- a/core/io.nom +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains basic input/output code - -use "core/metaprogramming" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(say $message) compiles to: - lua> (" - if \$message.type == "Text" then - return LuaCode("say(", \($message as lua expr), ");"); - else - return LuaCode("say(tostring(", \($message as lua expr), "));"); - end - ") - -(say $message inline) compiles to: - lua> (" - if \$message.type == "Text" then - return LuaCode("io.write(", \($message as lua expr), ")"); - else - return LuaCode("io.write(tostring(", \($message as lua expr), "))"); - end - ") - -externally (ask $prompt) means: - $io.write $prompt - return ($io.read()) diff --git a/core/math.nom b/core/math.nom deleted file mode 100644 index 685ab1e..0000000 --- a/core/math.nom +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines some common math literals and functions - -use "core/metaprogramming" -use "core/text" -use "core/operators" -use "core/control_flow" -use "core/collections" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# Literals: -test: - unless (all of [inf, NaN, pi, tau, golden ratio, e]): - fail "math constants failed" - $nan = (NaN) - unless ($nan != $nan): - fail "NaN failed" -[infinity, inf] all compile to "math.huge" -[not a number, NaN, nan] all compile to "(0/0)" -[pi, Pi, PI] all compile to "math.pi" -[tau, Tau, TAU] all compile to "(2*math.pi)" -(golden ratio) compiles to "((1+math.sqrt(5))/2)" -(e) compiles to "math.exp(1)" - -# Functions: -test: - assume (("5" as a number) == 5) -external $($ as a number) = $(tonumber $) -external $($ as number) = $(tonumber $) -test: - unless - all of [ - abs 5, | 5 |, sqrt 5, √ 5, sine 5, cosine 5, tangent 5, arc sine 5, arc cosine 5 - arc tangent 5, arc tangent 5 / 10, hyperbolic sine 5, hyperbolic cosine 5 - hyperbolic tangent 5, e^ 5, ln 5, log 5 base 2, floor 5, ceiling 5, round 5 - ] - ..: - fail "math functions failed" -external [$(absolute value $), $(absolute value of $), $(| $ |), $(abs $)] = - [$math.abs, $math.abs, $math.abs, $math.abs] -external [$(square root $), $(square root of $), $(√ $), $(sqrt $)] = - [$math.sqrt, $math.sqrt, $math.sqrt, $math.sqrt] -external [$(sine $), $(sin $)] = [$math.sin, $math.sin] -external [$(cosine $), $(cos $)] = [$math.cos, $math.cos] -external [$(tangent $), $(tan $)] = [$math.tan, $math.tan] -external [$(arc sine $), $(asin $)] = [$math.asin, $math.asin] -external [$(arc cosine $), $(acos $)] = [$math.acos, $math.acos] -external [$(arc tangent $), $(atan $)] = [$math.atan, $math.atan] -external [$(arc tangent $y / $x), $(atan2 $y $x)] = [$math.atan2, $math.atan2] -external [$(hyperbolic sine $), $(sinh $)] = [$math.sinh, $math.sinh] -external [$(hyperbolic cosine $), $(cosh $)] = [$math.cosh, $math.cosh] -external [$(hyperbolic tangent $), $(tanh $)] = [$math.tanh, $math.tanh] -external [$(e^ $), $(exp $)] = [$math.exp, $math.exp] -external [$(natural log $), $(ln $), $(log $), $(log $ base $)] = [$math.log, $math.log, $math.log, $math.log] -external $(floor $) = $math.floor -external [$(ceiling $), $(ceil $)] = [$math.ceil, $math.ceil] -externally [round $, $ rounded] all mean - floor ($ + 0.5) -test: - unless ((463 to the nearest 100) == 500): fail "rounding failed" - unless ((2.6 to the nearest 0.25) == 2.5): fail "rounding failed" - -externally ($n to the nearest $rounder) means - $rounder * (floor ($n / $rounder + 0.5)) - -# Any/all -externally [all of $items, all $items] all mean: - for $ in $items: - unless $: - return (no) - return (yes) -[not all of $items, not all $items] all parse as (not (all of $items)) -externally [any of $items, any $items] all mean: - for $ in $items: - if $: - return (yes) - return (no) -[none of $items, none $items] all parse as (not (any of $items)) - -# Sum/product -externally [sum of $items, sum $items] all mean: - $total = 0 - for $ in $items: - $total += $ - return $total - -externally [product of $items, product $items] all mean: - $prod = 1 - for $ in $items: - $prod *= $ - return $prod - -externally [avg of $items, average of $items] all mean - (sum of $items) / (size of $items) - -# Min/max -externally [min of $items, smallest of $items, lowest of $items] all mean: - $best = (nil) - for $ in $items: - if (($best == (nil)) or ($ < $best)): $best = $ - return $best - -externally [ - max of $items, biggest of $items, largest of $items, highest of $items -] all mean: - $best = (nil) - for $ in $items: - if (($best == (nil)) or ($ > $best)): $best = $ - return $best - -test: - assume ((min of [3, -4, 1, 2] by $ = ($ * $)) == 1) - assume ((max of [3, -4, 1, 2] by $ = ($ * $)) == -4) - -(min of $items by $item = $value_expr) parses as - result of: - $best = (nil) - $best_key = (nil) - for $item in $items: - $key = $value_expr - if (($best == (nil)) or ($key < $best_key)): - $best = $item - $best_key = $key - return $best - -(max of $items by $item = $value_expr) parses as - result of: - $best = (nil) - $best_key = (nil) - for $item in $items: - $key = $value_expr - if (($best == (nil)) or ($key > $best_key)): - $best = $item - $best_key = $key - return $best - -test: - assume (100 clamped between 0 and 10) == 10 - -externally ($ clamped between $min and $max) means: - when: - ($ < $min): - return $min - - ($ > $max): - return $max - - else: - return $ - -test: - assume (-0.1 smoothed by 2.7) == 0 - assume (0 smoothed by 2.7) == 0 - assume (0.5 smoothed by 2.7) == 0.5 - assume (1 smoothed by 2.7) == 1 - assume (1.1 smoothed by 2.7) == 1 - -externally ($ smoothed by $smoothness) means: - $ = ($ clamped between 0 and 1) - if ($smoothness == 0): return $ - $k = (2 ^ $smoothness) - if ($ < 0.5): - return (0.5 * (2 * $) ^ $k) - ..else: - return (1 - 0.5 * (2 - 2 * $) ^ $k) - -test: - assume (5 to 7 mixed by -1.0) == 5 - assume (5 to 7 mixed by 0.0) == 5 - assume (5 to 7 mixed by 0.5) == 6 - assume (5 to 7 mixed by 1.0) == 7 - assume (5 to 7 mixed by 2.0) == 7 - -externally ($lo to $hi mixed by $amount) means: - $ = ($amount clamped between 0 and 1) - return ((1 - $) * $lo + $ * $hi) - -test: - assume ([0, 1, 11] mixed by 0.0) == 0 - assume ([0, 1, 11] mixed by 0.25) == 0.5 - assume ([0, 1, 11] mixed by 0.5) == 1 - assume ([0, 1, 11] mixed by 0.75) == 6 - assume ([0, 1, 11] mixed by 1.0) == 11 - assume ([99] mixed by 0.5) == 99 - -externally ($nums mixed by $amount) means: - $ = ($amount clamped between 0 and 1) - $i = (1 + ($ * ((#$nums) - 1))) - if ((floor $i) == (#$nums)): - return $nums.(floor $i) - [$lo, $hi] = [$nums.(floor $i), $nums.(floor ($i + 1))] - return ($lo to $hi mixed by ($i mod 1)) - -# Random functions -externally (seed random with $) means: - lua> (" - math.randomseed(\$); - for i=1,20 do math.random(); end - ") -(seed random) parses as (seed random with (=lua "os.time()")) -[random number, random, rand] all compile to "math.random()" -[random int $n, random integer $n, randint $n] all compile to - "math.random(\($n as lua expr))" - -[random from $low to $high, random number from $low to $high, rand $low $high] -..all compile to "math.random(\($low as lua expr), \($high as lua expr))" - -externally [ - random choice from $elements, random choice $elements, random $elements -] all mean (=lua "\$elements[math.random(#\$elements)]") diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom deleted file mode 100644 index fa29a8a..0000000 --- a/core/metaprogramming.nom +++ /dev/null @@ -1,454 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This File contains actions for making actions and compile-time actions and some helper - functions to make that easier. - -lua> "NOMSU_CORE_VERSION = 14" -lua> "NOMSU_LIB_VERSION = 8" -lua> (" - do - local mangle_index = 0 - function mangler() - local my_mangle_index = mangle_index - mangle_index = mangle_index + 1 - return function(varname) - return (varname..(("\\3%X"):format(my_mangle_index))):as_lua_id() - end - end - end - COMPILE_RULES["define mangler"] = function(\(nomsu environment)) - return LuaCode("local mangle = mangler()") - end -") - -lua> (" - COMPILE_RULES["1 ->"] = function(\(nomsu environment), \$args, \$body) - if \$args and not \$body then \$args, \$body = {}, \$args end - local body_lua = SyntaxTree:is_instance(\$body) and \(nomsu environment):compile(\$body) or \$body - if SyntaxTree:is_instance(\$body) and \$body.type ~= "Block" then body_lua:prepend("\ - ..return ") end - local lua = LuaCode("(function(") - if SyntaxTree:is_instance(\$args) and (\$args.type == "Action" or \$args.type == "MethodCall") then - \$args = \$args:get_args() - elseif SyntaxTree:is_instance(\$args) and \$args.type == "Var" then \$args = {\$args} end - for i, arg in ipairs(\$args) do - local arg_lua = SyntaxTree:is_instance(arg) and \(nomsu environment):compile(arg):text() or arg - if arg_lua == "..." then - if i < #\$args then - compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, - "Extra arguments must come last.", "Try removing any arguments after \ - ..(*extra arguments*)") - end - elseif not arg_lua:is_lua_id() then - compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, - "This does not compile to a Lua identifier, so it can't be used as a function \ - ..argument.", - "This should probably be a Nomsu variable instead (like $x).") - end - lua:add(i > 1 and ", " or "", arg_lua) - body_lua:remove_free_vars({arg_lua}) - end - body_lua:declare_locals() - lua:add(")\\n ", body_lua, "\\nend)") - return lua - end - COMPILE_RULES["->"] = COMPILE_RULES["1 ->"] - COMPILE_RULES["for"] = COMPILE_RULES["1 ->"] -") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - (five) compiles to "5" - -test: - unless ((five) == 5): - fail "Compile to expression failed." - (loc x) compiles to "local x = 99;" - -test: - lua> "do" - loc x - unless ($x is 99): - fail "Compile to statements with locals failed." - lua> "end" - unless ($x is (nil)): - fail "Failed to properly localize a variable." - - (asdf) compiles to: - $tmp = "" - return (Lua $tmp) - -test: - asdf - unless ($tmp is (nil)): - fail "compile to is leaking variables" - -lua> (" - COMPILE_RULES["1 compiles to"] = function(env, \$action, \$body) - local \$args = List{"\(nomsu environment)", unpack(\$action:get_args())} - if \$body.type == "Text" then - \$body = SyntaxTree{source=\$body.source, type="Action", "Lua", \$body} - end - return LuaCode("COMPILE_RULES[", \$action:get_stub():as_lua(), - "] = ", \(\($args -> $body) as lua)) - end -") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -($actions all compile to $body) compiles to: - lua> (" - if \$actions.type ~= "List" then - compile_error(\$actions, "This should be a list of actions.") - end - local lua = \(\($actions.1 compiles to $body) as lua) - local \$args = List{"\(nomsu environment)", unpack(\$actions[1]:get_args())} - local \$compiled_args = List{"\(nomsu environment)"}; - for i=2,#\$args do \$compiled_args[i] = \(nomsu environment):compile(\$args[i]) end - for i=2,#\$actions do - local alias = \$actions[i] - local \$alias_args = List{"\(nomsu environment)", unpack(alias:get_args())} - lua:add("\\nCOMPILE_RULES[", alias:get_stub():as_lua(), "] = ") - if \$alias_args == \$args then - lua:add("COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "]") - else - lua:add("function(") - local \$compiled_alias_args = List{"\(nomsu environment)"}; - for i=2,#\$alias_args do \$compiled_alias_args[i] = \(nomsu environment):compile(\$alias_args[i]) end - lua:concat_add(\$compiled_alias_args, ", ") - lua:add(") return COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "](") - lua:concat_add(\$compiled_args, ", ") - lua:add(") end") - end - end - return lua - ") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - (foo $x) means "outer" - with [$(foo $)]: - (foo $x) means: - $y = ($x + 1) - return $y - - unless ((foo 10) == 11): - fail "Action didn't work." - - unless ($y is (nil)): - fail "Action leaked a local into globals." - - (baz $) parses as (foo $) - assume ((foo 1) == "outer") - -($action means $body) compiles to: - lua> (" - - local lua = LuaCode() - if \$action.type == "MethodCall" then - lua:add(\(nomsu environment):compile(\$action[1]), ".", \$action[2]:get_stub():as_lua_id()) - elseif \$action.type == "Action" then - lua:add(\$action:get_stub():as_lua_id()) - lua:add_free_vars({\$action:get_stub():as_lua_id()}) - else - compile_error_at(\$action, "Expected an action or method call here") - end - lua:add(" = ", \(\($action -> $body) as lua), ";") - return lua - ") - -($actions all mean $body) compiles to: - lua> (" - local lua = \(\($actions.1 means $body) as lua) - local first_def = (\$actions[1].type == "MethodCall" - and LuaCode(\(nomsu environment):compile(\$actions[1][1]), ".", \$actions[1]:get_stub():as_lua_id()) - or LuaCode(\$actions[1]:get_stub():as_lua_id())) - local \$args = List(\$actions[1]:get_args()) - for i=2,#\$actions do - local alias = \$actions[i] - local \$alias_args = List(alias:get_args()) - lua:add("\\n") - if alias.type == "MethodCall" then - lua:add(\(nomsu environment):compile(alias[1]), ".", alias:get_stub():as_lua_id()) - else - lua:add(alias:get_stub():as_lua_id()) - lua:add_free_vars({alias_name}) - end - if \$args == \$alias_args then - lua:add(" = ", first_def, ";") - else - lua:add(" = ", \(\($alias_args -> $actions.1) as lua), ";") - end - end - return lua - ") - -test: - externally (baz1) means: - return "baz1" - externally (baz2) means "baz2" - -test: - assume ((baz1) == "baz1") - assume ((baz2) == "baz2") - -(externally $action means $body) compiles to: - lua> (" - local lua = \(\($action means $body) as lua) - lua:remove_free_vars({\$action:get_stub():as_lua_id()}) - return lua - ") - -(externally $actions all mean $body) compiles to: - lua> (" - local lua = \(\($actions all mean $body) as lua) - lua:remove_free_vars(table.map(\$actions, function(a) return a:get_stub():as_lua_id() end)) - return lua - ") - -test: - (swap $x and $y) parses as - do: - $tmp = $x - $x = $y - $y = $tmp - -test: - [$1, $2] = [1, 2] - swap $1 and $2 - unless (($1 == 2) and ($2 == 1)): - fail "'parse $ as $' failed on 'swap $ and $'" - [$tmp, $tmp2] = [1, 2] - swap $tmp and $tmp2 - unless (($tmp == 2) and ($tmp2 == 1)): - fail "'parse $ as $' variable mangling failed." - -($actions all parse as $body) compiles to: - lua> (" - local replacements = {} - if \$actions.type ~= "List" then - compile_error(\$actions, "This should be a list.") - end - for i,arg in ipairs(\$actions[1]:get_args()) do - replacements[arg[1]] = \(nomsu environment):compile(arg):text() - end - local function make_tree(t) - if SyntaxTree:is_instance(t) and t.type == "Var" then - if replacements[t:as_var()] then - return replacements[t:as_var()] - else - return "SyntaxTree{mangle("..t:as_var():as_lua().."), type="..t.type:as_lua()..", \ - ..source="..tostring(t.source):as_lua().."}" - end - elseif SyntaxTree:is_instance(t) then - local ret = {} - local i = 1 - for k, v in pairs(t) do - if k == i then - ret[#ret+1] = make_tree(t[i]) - i = i + 1 - elseif k == "source" then - ret[#ret+1] = k.."= "..tostring(v):as_lua() - elseif lua_type_of(k) == 'string' and k:is_a_lua_id() then - ret[#ret+1] = k.."= "..make_tree(v) - else - ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v) - end - end - return "SyntaxTree{"..table.concat(ret, ", ").."}" - elseif lua_type_of(t) == 'number' then - return tostring(t) - else - return t:as_lua() - end - end - local \$new_body = LuaCode:from(\$body.source, - "local mangle = mangler()", - "\\nreturn ", make_tree(\$body)) - local ret = \(\($actions all compile to $new_body) as lua) - return ret - ") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -[$action parses as $body] all parse as ([$action] all parse as $body) -externally ($tree as lua expr) means: - lua> (" - local tree_lua = \(nomsu environment):compile(\$tree) - if \$tree.type == 'Block' then - tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()') - elseif \$tree.type == 'MethodCall' and #\$tree > 2 then - compile_error_at(\$tree, "This must be a single value instead of "..(#\$tree - 1).."\ - .. method calls.", - "Replace this with a single method call.") - end - return tree_lua - ") - -externally [$var as lua identifier, $var as lua id] all mean: - lua> (" - local lua = \($var as lua) - if not lua:text():is_a_lua_id() then - compile_error(\$var, - "This is supposed to be something that compiles to a valid Lua identifier.", - "This should probably be a variable.") - end - return lua - ") - -test: - (num args (*extra arguments*)) means (select "#" (*extra arguments*)) - assume (num args 1 2 3) == 3 - (extra args (*extra arguments*)) means [*extra arguments*] - assume (extra args 1 2 3) == [1, 2, 3] - (third arg (*extra arguments*)) means (select 3 (*extra arguments*)) - assume (third arg 5 6 7 8) == 7 - -(*extra arguments*) compiles to "..." - -($ is syntax tree) compiles to "SyntaxTree:is_instance(\($ as lua expr))" - -externally ($ is $kind syntax tree) means - =lua "SyntaxTree:is_instance(\$) and \$.type == \$kind" - -($tree with $t -> $replacement) compiles to (" - \($tree as lua expr):map(function(\($t as lua expr)) - \( - =lua (" - \$replacement.type == 'Block' and \($replacement as lua) or 'return '..\ - ..\($replacement as lua expr):text() - ") - ) - end) -") - -externally ($tree with vars $replacements) means - =lua (" - \$tree:map(function(\$t) - if \$t.type == "Var" then - return \$replacements[\$t:as_var()] - end - end) - ") - -(tree $tree with vars $replacements) compiles to (" - \(=lua "(\$tree):as_lua()"):map(function(t) - if t.type == "Var" then - return \($replacements as lua expr)[t:as_var()] - end - end) -") - -($tree has subtree $match_tree) compiles to (" - (function() - local match_tree = \($match_tree as lua expr) - for subtree in coroutine_wrap(function() \($tree as lua expr):map(yield) end) do - if subtree == match_tree then return true end - end - end)() -") - -externally (match $tree with $patt) means: - lua> (" - if \$patt.type == "Var" then return Dict{[\$patt:as_var()]=\$tree} end - if \$patt.type == "Action" and \$patt:get_stub() ~= \$tree:get_stub() then return nil end - if #\$patt ~= #\$tree then return nil end - local matches = Dict{} - for \($i)=1,#\$patt do - if SyntaxTree:is_instance(\$tree[\$i]) then - local submatch = \(match $tree.$i with $patt.$i) - if not submatch then return nil end - for k,v in pairs(submatch) do - if matches[k] and matches[k] ~= v then return nil end - matches[k] = v - end - end - end - return matches - ") - -test: - assume - ( - quote (" - one - "two" - ") - ) == "\"one\\n\\\"two\\\"\"" - -(quote $s) compiles to "tostring(\($s as lua expr)):as_lua()" -test: - assume (lua type of {}) == "table" - assume (type of {}) == "Dict" - assume ({} is a "Dict") - assume ("" is text) - assume ("" isn't a "Dict") -externally ($ is text) means (=lua "\(lua type of $) == 'string'") -externally [$ is not text, $ isn't text] all mean - =lua "\(lua type of $) ~= 'string'" - -externally (type of $) means: - lua> (" - local lua_type = \(lua type of $) - if lua_type == 'string' then return 'Text' - elseif lua_type == 'table' or lua_type == 'userdata' then - local mt = getmetatable(\$) - if mt and mt.__type then return mt.__type end - end - return lua_type - ") - -[$ is a $type, $ is an $type] all parse as ((type of $) == $type) -[$ isn't a $type, $ isn't an $type, $ is not a $type, $ is not an $type] -..all parse as ((type of $) != $type) - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - (foo) means: - return 100 200 300 - assume (select 2 (foo)) == 200 - -# Return statement is wrapped in a do..end block because Lua is unhappy if you - put code after a return statement, unless you wrap it in a block. -(return (*extra arguments*)) compiles to: - lua> (" - local lua = \(Lua "do return ") - for i=1,select('#',...) do - if i > 1 then lua:add(", ") end - lua:add(\(nomsu environment):compile((select(i, ...)))) - end - lua:add(" end") - return lua - ") - -# Literals -(yes) compiles to "true" -(no) compiles to "false" -[nothing, nil, null] all compile to "nil" -(Nomsu syntax version) compiles to "NOMSU_SYNTAX_VERSION" -(Nomsu compiler version) compiles to "NOMSU_COMPILER_VERSION" -(core version) compiles to "NOMSU_CORE_VERSION" -(lib version) compiles to "NOMSU_LIB_VERSION" -(command line args) compiles to "COMMAND_LINE_ARGS" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# - (with local compile actions $body) compiles to (" - do - local OLD_RULES = COMPILE_RULES - local OLD_ENV = \(nomsu environment) - local \(nomsu environment) = setmetatable({ - COMPILE_RULES=setmetatable({}, {__index=OLD_RULES}) - }, {__index=OLD_ENV}) - \($body as lua) - end - ") - -externally (Nomsu version) means: - return (" - \(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version) - ") diff --git a/core/operators.nom b/core/operators.nom deleted file mode 100644 index dee76b6..0000000 --- a/core/operators.nom +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains definitions of operators like "+" and "and". - -use "core/metaprogramming" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - assume (all [1 < 2, 2 > 1, 1 <= 2, 2 >= 1, 1 == 1, 1 != 2]) - -# Comparison Operators -($x < $y) compiles to "(\($x as lua expr) < \($y as lua expr))" -($x > $y) compiles to "(\($x as lua expr) > \($y as lua expr))" -($x <= $y) compiles to "(\($x as lua expr) <= \($y as lua expr))" -($x >= $y) compiles to "(\($x as lua expr) >= \($y as lua expr))" -[$a is $b, $a == $b] all compile to "(\($a as lua expr) == \($b as lua expr))" -[$a isn't $b, $a is not $b, $a not= $b, $a != $b] all compile to - "(\($a as lua expr) ~= \($b as lua expr))" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - $x = 10 - assume ($x == 10) - [$x, $y] = [10, 20] - unless (($x == 10) and ($y == 20)): - fail "mutli-assignment failed." - [$x, $y] = [$y, $x] - unless (($y == 10) and ($x == 20)): - fail "swapping vars failed." - $vals = [4, 5] - [$x, $y] = (unpack $vals) - unless (($x == 4) and ($y == 5)): - fail "unpacking failed" - -# Variable assignment operator -($var = $value) compiles to: - lua> (" - local lua = LuaCode() - if \$var.type == "List" then - for i, \$assignment in ipairs(\$var) do - if i > 1 then lua:add(", ") end - local assignment_lua = \($assignment as lua expr) - lua:add(assignment_lua) - if \$assignment.type == 'Var' then - lua:add_free_vars({assignment_lua:text()}) - end - end - lua:add(' = ') - if \$value.type == "List" then - if #\$value ~= #\$var then - compile_error_at(\$value, - "This assignment has too "..(#\$value > #\$var and "many" or "few").." values.", - "Make sure it has the same number of values on the left and right hand side \ - ..of the '=' operator.") - end - for i, \$val in ipairs(\$value) do - if i > 1 then lua:add(", ") end - local val_lua = \($val as lua expr) - lua:add(val_lua) - end - lua:add(";") - else - lua:add(\($value as lua expr), ';') - end - else - local var_lua = \($var as lua expr) - lua:add(var_lua) - if \$var.type == 'Var' then - lua:add_free_vars({var_lua:text()}) - end - lua:add(' = ', \($value as lua expr)) - end - return lua - ") - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - [$foozle, $y] = ["outer", "outer"] - externally (set global x local y) means: - external $foozle = "inner" - $y = "inner" - set global x local y - unless (($foozle == "inner") and ($y == "outer")): fail "external failed." -(external $var = $value) compiles to: - $lua = ((SyntaxTree {.type = "Action", .source = $var.source, .1 = $var, .2 = "=", .3 = $value}) as lua) - $lua, remove free vars - return $lua -test: - [$foozle, $y] = ["outer", "outer"] - externally (set global x local y) means: - with external [$foozle]: - $foozle = "inner" - $y = "inner" - set global x local y - unless (($foozle == "inner") and ($y == "outer")): - fail "'with external' failed." - -(with external $externs $body) compiles to: - $body_lua = ($body as lua) - lua> (" - \$body_lua:remove_free_vars(table.map(\$externs, function(v) return \(nomsu environment):compile(v):text() end)) - ") - return $body_lua - -test: - [$x, $y] = [1, 2] - with [$z, $x = 999]: - assume $z == (nil) - $z = 999 - unless ($z == 999): - fail "'with' failed." - - unless ($x == 999): - fail "'with' assignment failed." - - unless ($x == 1): - fail "'with' scoping failed" - - unless ($z == (nil)): - fail "'with' scoping failed" - -(with $assignments $body) compiles to: - lua> (" - local \$defs = LuaCode() - for i, \$item in ipairs(\$assignments) do - if i > 1 then \$defs:add("\\n") end - local item_lua = \($item as lua) - if \$item.type == 'Action' and \$item.stub == '1 =' then - item_lua:remove_free_vars(item_lua.free_vars) - end - \$defs:add("local ", item_lua, ";") - end - ") - - return - Lua (" - do - \$defs - \($body as lua) - end -- 'with' block - ") - -# Math Operators -test: - unless ((5 wrapped around 2) == 1): - fail "mod not working" - -[$x wrapped around $y, $x mod $y] all compile to - "((\($x as lua expr)) % (\($y as lua expr)))" - -# 3-part chained comparisons -# (uses a lambda to avoid re-evaluating middle value, while still being an expression) -test: - $calls = 0 - (one) means: - external $calls = ($calls + 1) - return 1 - - unless (0 <= (one) <= 2): - fail "Three-way chained comparison failed." - - unless ($calls == 1): - fail "Three-way comparison evaluated middle value multiple times" -($x < $y < $z) parses as ((($a $b $c) -> (($a < $b) and ($b < $c))) $x $y $z) -($x <= $y < $z) parses as ((($a $b $c) -> (($a <= $b) and ($b < $c))) $x $y $z) -($x < $y <= $z) parses as ((($a $b $c) -> (($a < $b) and ($b <= $c))) $x $y $z) -($x <= $y <= $z) parses as ((($a $b $c) -> (($a <= $b) and ($b <= $c))) $x $y $z) -($x > $y > $z) parses as ((($a $b $c) -> (($a > $b) and ($b > $c))) $x $y $z) -($x >= $y > $z) parses as ((($a $b $c) -> (($a >= $b) and ($b > $c))) $x $y $z) -($x > $y >= $z) parses as ((($a $b $c) -> (($a > $b) and ($b >= $c))) $x $y $z) -($x >= $y >= $z) parses as ((($a $b $c) -> (($a >= $b) and ($b >= $c))) $x $y $z) - -# TODO: optimize for common case where x,y,z are all either variables or number literals -# Boolean Operators -test: - (barfer) means (fail "short circuiting failed") - assume (((no) and (barfer)) == (no)) - assume ((no) or (yes)) - assume ((yes) or (barfer)) -($x and $y) compiles to "(\($x as lua expr) and \($y as lua expr))" -($x or $y) compiles to "(\($x as lua expr) or \($y as lua expr))" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# Bitwise Operators -# TODO: implement OR, XOR, AND for multiple operands? -test: - assume ((~ (~ 5)) == 5) - assume ((1 | 4) == 5) - assume ((1 ~ 3) == 2) - assume ((1 & 3) == 1) - assume ((1 << 2) == 4) - assume ((4 >> 2) == 1) - -# Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise - fall back to bit.bor(), bit.band(), etc. -lua> "if \((is jit) or ((Lua version) == "Lua 5.2")) then" -[NOT $, ~ $] all compile to "bit.bnot(\($ as lua expr))" -[$x OR $y, $x | $y] all compile to - "bit.bor(\($x as lua expr), \($y as lua expr))" - -[$x XOR $y, $x ~ $y] all compile to - "bit.bxor(\($x as lua expr), \($y as lua expr))" - -[$x AND $y, $x & $y] all compile to - "bit.band(\($x as lua expr), \($y as lua expr))" - -[$x LSHIFT $shift, $x << $shift] all compile to - "bit.lshift(\($x as lua expr), \($shift as lua expr))" - -[$x RSHIFT $shift, $x >> $shift] all compile to - "bit.rshift(\($x as lua expr), \($shift as lua expr))" - -lua> "else" -[NOT $, ~ $] all compile to "~(\($ as lua expr))" -[$x OR $y, $x | $y] all compile to "(\($x as lua expr) | \($y as lua expr))" -[$x XOR $y, $x ~ $y] all compile to "(\($x as lua expr) ~ \($y as lua expr))" -[$x AND $y, $x & $y] all compile to "(\($x as lua expr) & \($y as lua expr))" -[$x LSHIFT $shift, $x << $shift] all compile to - "(\($x as lua expr) << \($shift as lua expr))" - -[$x RSHIFT $shift, $x >> $shift] all compile to - "(\($x as lua expr) >> \($shift as lua expr))" - -lua> "end" - -# Unary operators -test: - assume ((- 5) == -5) - assume ((not (yes)) == (no)) -(- $) compiles to "(- \($ as lua expr))" -(not $) compiles to "(not \($ as lua expr))" -test: - assume ((size of [1, 2, 3]) == 3) - assume ((#[1, 2, 3]) == 3) -[#$list, size of $list] all compile to "(#\($list as lua expr))" -($list is empty) compiles to "(#\($list as lua expr) == 0)" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# Update operators -test: - $x = 1 - $x += 1 - unless ($x == 2): - fail "+= failed" - $x *= 2 - unless ($x == 4): - fail "*= failed" - wrap $x around 3 - unless ($x == 1): - fail "wrap around failed" -($var += $) parses as ($var = (($var or 0) + $)) -($var -= $) parses as ($var = (($var or 0) - $)) -($var *= $) parses as ($var = (($var or 1) * $)) -($var /= $) parses as ($var = ($var / $)) -($var ^= $) parses as ($var = ($var ^ $)) -($var and= $) parses as ($var = ($var and $)) -($var or= $) parses as ($var = ($var or $)) -(wrap $var around $) parses as ($var = ($var wrapped around $)) diff --git a/core/text.nom b/core/text.nom deleted file mode 100644 index 1351af6..0000000 --- a/core/text.nom +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file contains some definitions of text escape sequences, including ANSI console - color codes. - -use "core/metaprogramming" -use "core/operators" -use "core/control_flow" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -test: - assume "\[1, 2, 3]" == "[1, 2, 3]" - assume "foo = \(1 + 2)!" == "foo = 3!" - assume (" - one - two - ") == (" - one - two - ") - assume "nogap" == "nogap" - assume (["x", "y"], joined with ",") == "x,y" - assume (["x", "y"], joined) == "xy" - assume ("BAR", byte 2) == 65 - assume ("BAR", bytes 1 to 2) == [66, 65] - assume ("asdf", capitalized) == "Asdf" - assume ("asdf", uppercase) == "ASDF" - assume ("asdf", with "s" -> "X") == "aXdf" - assume - (" - one - two - - "), lines - ..== ["one", "two", ""] - - ($spec とは $body) parses as ($spec means $body) - -test: - $こんにちは = "こんにちは" - ($ と言う) とは "\($)世界" - assume ($こんにちは と言う) == "こんにちは世界" - -($expr for $match in $text matching $patt) compiles to: - define mangler - return - Lua (" - (function() - local \(mangle "comprehension") = List{} - for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do - \(mangle "comprehension")[#\(mangle "comprehension")+1] = \($expr as lua) - end - return \(mangle "comprehension") - end)() - ") - -test: - assume "\n" == (newline) - -test: - assume (0xDEADBEEF as hex) == "0xDEADBEEF" - -externally ($num as hex) means: - if ($num < 0): - return ("-0x%X", formatted with (- $num)) - ..else: - return ("0x%X", formatted with $num) - -# Text literals -$escapes = { - .nl = "\n", .newline = "\n", .tab = "\t", .bell = "\a", .cr = "\r", ."carriage return" = "\r" - .backspace = "\b", ."form feed" = "\f", .formfeed = "\f", ."vertical tab" = "\v" -} - -for $name = $str in $escapes: - with [$lua = (Lua (quote $str))]: - $(COMPILE RULES).$name = (-> $lua) diff --git a/error_handling.lua b/error_handling.lua index 76b4390..27022aa 100644 --- a/error_handling.lua +++ b/error_handling.lua @@ -1,5 +1,5 @@ -local files = require("files") local debug_getinfo = debug.getinfo +local Files = require("files") local RED = "\027[31m" local BRIGHT_RED = "\027[31;1m" local RESET = "\027[0m" @@ -119,7 +119,6 @@ print_error = function(error_message, start_fn, stop_fn) filename, start = calling_fn.source:match('@([^[]*)%[([0-9]+)]') end assert(filename) - local file = files.read(filename) if calling_fn.name then do local tmp = calling_fn.name:match("^A_([a-zA-Z0-9_]*)$") @@ -134,8 +133,10 @@ print_error = function(error_message, start_fn, stop_fn) else name = "main chunk" end + local file = Files.read(filename) + local lines = file and file:lines() or { } do - local err_line = files.get_line(file, calling_fn.currentline) + local err_line = lines[calling_fn.currentline] if err_line then local offending_statement = tostring(BRIGHT_RED) .. tostring(err_line:match("^[ ]*(.*)")) .. tostring(RESET) line = tostring(YELLOW) .. tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n " .. tostring(offending_statement) .. tostring(RESET) @@ -144,13 +145,6 @@ print_error = function(error_message, start_fn, stop_fn) end end else - local file - ok, file = pcall(function() - return files.read(calling_fn.short_src) - end) - if not ok then - file = nil - end local line_num if name == nil then local search_level = level @@ -189,6 +183,13 @@ print_error = function(error_message, start_fn, stop_fn) end end end + local file, lines + do + file = Files.read(calling_fn.short_src) + if file then + lines = file:lines() + end + end if file and (calling_fn.short_src:match("%.moon$") or file:match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' then local char = MOON_SOURCE_MAP[file][calling_fn.currentline] line_num = 1 @@ -206,7 +207,7 @@ print_error = function(error_message, start_fn, stop_fn) end if file then do - local err_line = files.get_line(file, line_num) + local err_line = lines[line_num] if err_line then local offending_statement = tostring(BRIGHT_RED) .. tostring(err_line:match("^[ ]*(.*)$")) .. tostring(RESET) line = line .. ("\n " .. offending_statement) diff --git a/error_handling.moon b/error_handling.moon index 46580d6..c22a3ae 100644 --- a/error_handling.moon +++ b/error_handling.moon @@ -1,6 +1,6 @@ -- This file contains the logic for making nicer error messages -files = require "files" debug_getinfo = debug.getinfo +Files = require "files" export SOURCE_MAP RED = "\027[31m" @@ -78,7 +78,6 @@ print_error = (error_message, start_fn, stop_fn)-> if not filename filename,start = calling_fn.source\match('@([^[]*)%[([0-9]+)]') assert(filename) - file = files.read(filename) -- TODO: get name properly name = if calling_fn.name if tmp = calling_fn.name\match("^A_([a-zA-Z0-9_]*)$") @@ -86,14 +85,14 @@ print_error = (error_message, start_fn, stop_fn)-> else "action '#{calling_fn.name}'" else "main chunk" - if err_line = files.get_line(file, calling_fn.currentline) + file = Files.read(filename) + lines = file and file\lines! or {} + if err_line = lines[calling_fn.currentline] offending_statement = "#{BRIGHT_RED}#{err_line\match("^[ ]*(.*)")}#{RESET}" line = "#{YELLOW}#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}#{RESET}" else line = "#{YELLOW}#{filename}:#{calling_fn.currentline} in #{name}#{RESET}" else - ok, file = pcall ->files.read(calling_fn.short_src) - if not ok then file = nil local line_num if name == nil search_level = level @@ -117,6 +116,10 @@ print_error = (error_message, start_fn, stop_fn)-> name = "upvalue '#{varname}'" if not varname\match("%(") break + + local file, lines + if file = Files.read(calling_fn.short_src) + lines = file\lines! if file and (calling_fn.short_src\match("%.moon$") or file\match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' char = MOON_SOURCE_MAP[file][calling_fn.currentline] @@ -131,7 +134,7 @@ print_error = (error_message, start_fn, stop_fn)-> line = "#{BLUE}#{calling_fn.short_src}:#{calling_fn.currentline} in #{name or '?'}#{RESET}" if file - if err_line = files.get_line(file, line_num) + if err_line = lines[line_num] offending_statement = "#{BRIGHT_RED}#{err_line\match("^[ ]*(.*)$")}#{RESET}" line ..= "\n "..offending_statement io.stderr\write(line,"\n") diff --git a/examples/how_do_i.nom b/examples/how_do_i.nom index 14e7481..906ef74 100644 --- a/examples/how_do_i.nom +++ b/examples/how_do_i.nom @@ -6,11 +6,9 @@ is considered part of the comment (including any deeper-level indented text) The comment ends when the indentation ends -# How do I import a file? -use "lib/os" -# How do I import all the files in a directory? -use "lib" +# How do I import a libarary? +use "consolecolor" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/files.lua b/files.lua index 036471f..0c8558d 100644 --- a/files.lua +++ b/files.lua @@ -213,12 +213,4 @@ Files.get_line = function(str, line_no) end return (str:sub(start, stop - 2)) end -local get_lines = re.compile([[ lines <- {| line (%nl line)* |} - line <- {[^%nl]*} -]], { - nl = lpeg.P("\r") ^ -1 * lpeg.P("\n") -}) -Files.get_lines = function(str) - return get_lines:match(str) -end return Files diff --git a/files.moon b/files.moon index 3950ce3..a9fbb5c 100644 --- a/files.moon +++ b/files.moon @@ -134,11 +134,4 @@ Files.get_line = (str, line_no)-> return unless stop return (str\sub(start, stop - 2)) -get_lines = re.compile([[ - lines <- {| line (%nl line)* |} - line <- {[^%nl]*} -]], nl:lpeg.P("\r")^-1 * lpeg.P("\n")) - -Files.get_lines = (str)-> get_lines\match(str) - return Files diff --git a/lib/base64.nom b/lib/base64.nom deleted file mode 100644 index 54f785f..0000000 --- a/lib/base64.nom +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines actions for encoding/decoding base 64, as specified in: - https://tools.ietf.org/html/rfc4648 - -$b64_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -$b64_chars = [: for $ in 1 to (size of $b64_str): add ($b64_str, character $)] -$reverse_b64 = {: for $c in $b64_chars at $i: add $c = ($i - 1)} -$reverse_b64."=" = 64 -set $reverse_b64's metatable to {.__index = (-> 0)} -test: - $cases = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"] - for $len = $encoded in $cases: - $plain = ("foobar", from 1 to ($len - 1)) - assume (base64 $plain) == $encoded - assume (base64 decode $encoded) == $plain - -externally [base64 $str, base64 encode $str, $str base64] all mean: - $chars = [] - for $i in 1 to (size of $str) via 3: - $bytes = [=lua "\$str:byte(\$i, \($i + 2))"] - $chars, add $b64_chars.((($bytes.1 & 252) >> 2) + 1) - if (size of $bytes) is: - 3: - $chars, add $b64_chars.((($bytes.1 & 3) << 4) + (($bytes.2 & 240) >> 4) + 1) - $chars, add $b64_chars.((($bytes.2 & 15) << 2) + (($bytes.3 & 192) >> 6) + 1) - $chars, add $b64_chars.(($bytes.3 & 63) + 1) - - 2: - $chars, add $b64_chars.((($bytes.1 & 3) << 4) + (($bytes.2 & 240) >> 4) + 1) - $chars, add $b64_chars.((($bytes.2 & 15) << 2) + 1) - $chars, add "=" - - 1: - $chars, add $b64_chars.((($bytes.1 & 3) << 4) + 1) - $chars, add "=" - $chars, add "=" - return ($chars, joined) - -externally (chr $) means (=lua "string.char(\$)") -externally [decode base64 $str, $str base64 decoded, base64 decode $str] all mean: - $chars = [] - for $i in 1 to (size of $str) via 4: - $indices = [: for $j in $i to ($i + 3): add $reverse_b64.($str, character $j)] - $chars, add (chr (($indices.1 << 2) + (($indices.2 & 48) >> 4))) - if (($str, character ($i + 2)) == "="): stop - $chars, add (chr ((($indices.2 & 15) << 4) + (($indices.3 & 60) >> 2))) - if (($str, character ($i + 3)) == "="): stop - $chars, add (chr ((($indices.3 & 3) << 6) + $indices.4)) - return ($chars, joined) diff --git a/lib/base64/init.nom b/lib/base64/init.nom new file mode 100644 index 0000000..54f785f --- /dev/null +++ b/lib/base64/init.nom @@ -0,0 +1,50 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines actions for encoding/decoding base 64, as specified in: + https://tools.ietf.org/html/rfc4648 + +$b64_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +$b64_chars = [: for $ in 1 to (size of $b64_str): add ($b64_str, character $)] +$reverse_b64 = {: for $c in $b64_chars at $i: add $c = ($i - 1)} +$reverse_b64."=" = 64 +set $reverse_b64's metatable to {.__index = (-> 0)} +test: + $cases = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"] + for $len = $encoded in $cases: + $plain = ("foobar", from 1 to ($len - 1)) + assume (base64 $plain) == $encoded + assume (base64 decode $encoded) == $plain + +externally [base64 $str, base64 encode $str, $str base64] all mean: + $chars = [] + for $i in 1 to (size of $str) via 3: + $bytes = [=lua "\$str:byte(\$i, \($i + 2))"] + $chars, add $b64_chars.((($bytes.1 & 252) >> 2) + 1) + if (size of $bytes) is: + 3: + $chars, add $b64_chars.((($bytes.1 & 3) << 4) + (($bytes.2 & 240) >> 4) + 1) + $chars, add $b64_chars.((($bytes.2 & 15) << 2) + (($bytes.3 & 192) >> 6) + 1) + $chars, add $b64_chars.(($bytes.3 & 63) + 1) + + 2: + $chars, add $b64_chars.((($bytes.1 & 3) << 4) + (($bytes.2 & 240) >> 4) + 1) + $chars, add $b64_chars.((($bytes.2 & 15) << 2) + 1) + $chars, add "=" + + 1: + $chars, add $b64_chars.((($bytes.1 & 3) << 4) + 1) + $chars, add "=" + $chars, add "=" + return ($chars, joined) + +externally (chr $) means (=lua "string.char(\$)") +externally [decode base64 $str, $str base64 decoded, base64 decode $str] all mean: + $chars = [] + for $i in 1 to (size of $str) via 4: + $indices = [: for $j in $i to ($i + 3): add $reverse_b64.($str, character $j)] + $chars, add (chr (($indices.1 << 2) + (($indices.2 & 48) >> 4))) + if (($str, character ($i + 2)) == "="): stop + $chars, add (chr ((($indices.2 & 15) << 4) + (($indices.3 & 60) >> 2))) + if (($str, character ($i + 3)) == "="): stop + $chars, add (chr ((($indices.3 & 3) << 6) + $indices.4)) + return ($chars, joined) diff --git a/lib/commandline/init.nom b/lib/commandline/init.nom new file mode 100644 index 0000000..f28e02b --- /dev/null +++ b/lib/commandline/init.nom @@ -0,0 +1,11 @@ +# + A library defining some command line program functionality + +(command line program with $args $body) parses as: + externally (run with $args) means $body + if (this file was run directly): + run with (the command line arguments) + +externally (usage $) means: + say "Usage: \$" + exit 1 diff --git a/lib/consolecolor.nom b/lib/consolecolor.nom deleted file mode 100644 index d1da247..0000000 --- a/lib/consolecolor.nom +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines actions for ANSI console color escape codes. - -test: - $ = (bright "\(green)Color test passed.") - -$colors = { - .normal = 0, ."reset color" = 0, .bright = 1, .bold = 1, .dim = 2, .italic = 3 - .underscore = 4, ."slow blink" = 5, ."fast blink" = 6, .reverse = 7, .inverse = 7 - .inverted = 7, .hidden = 8 - # There's some other codes, but they're not currently implemented - .black = 30, .red = 31, .green = 32, .yellow = 33, .blue = 34, .magenta = 35 - .cyan = 36, .white = 37, ."on black" = 40, ."on red" = 41, ."on green" = 42 - ."on yellow" = 43, ."on blue" = 44, ."on magenta" = 45, ."on cyan" = 46 - ."on white" = 47 -} - -for $name = $colornum in $colors: - $colornum = "\$colornum" - $(COMPILE RULES).$name = - for ($compile $text): - if $text: - return (Lua "('\\027[\($colornum)m'..\($text as lua expr)..'\\027[0m')") - ..else: - return (Lua "'\\027[\($colornum)m'") diff --git a/lib/consolecolor/init.nom b/lib/consolecolor/init.nom new file mode 100644 index 0000000..d1da247 --- /dev/null +++ b/lib/consolecolor/init.nom @@ -0,0 +1,26 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines actions for ANSI console color escape codes. + +test: + $ = (bright "\(green)Color test passed.") + +$colors = { + .normal = 0, ."reset color" = 0, .bright = 1, .bold = 1, .dim = 2, .italic = 3 + .underscore = 4, ."slow blink" = 5, ."fast blink" = 6, .reverse = 7, .inverse = 7 + .inverted = 7, .hidden = 8 + # There's some other codes, but they're not currently implemented + .black = 30, .red = 31, .green = 32, .yellow = 33, .blue = 34, .magenta = 35 + .cyan = 36, .white = 37, ."on black" = 40, ."on red" = 41, ."on green" = 42 + ."on yellow" = 43, ."on blue" = 44, ."on magenta" = 45, ."on cyan" = 46 + ."on white" = 47 +} + +for $name = $colornum in $colors: + $colornum = "\$colornum" + $(COMPILE RULES).$name = + for ($compile $text): + if $text: + return (Lua "('\\027[\($colornum)m'..\($text as lua expr)..'\\027[0m')") + ..else: + return (Lua "'\\027[\($colornum)m'") diff --git a/lib/core/collections.nom b/lib/core/collections.nom new file mode 100644 index 0000000..4cf54cd --- /dev/null +++ b/lib/core/collections.nom @@ -0,0 +1,145 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains code that supports manipulating and using collections like lists + and dictionaries. + +use "core/metaprogramming" +use "core/control_flow" +use "core/operators" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# List functionality: +test: + $list = [1, 2, 3, 4, 5] + $visited = {} + for $i = $x in $list: + $visited.$i = (yes) + assume ($visited == {.1, .2, .3, .4, .5}) + $visited = {} + for $x in $list: + $visited.$x = (yes) + assume ($visited == {.1, .2, .3, .4, .5}) + assume (($list, 2 nd to last) == 4) + assume (($list, first) == 1) + assume ($list, has 3) + assume (($list, index of 3) == 3) + assume ((size of $list) == 5) + $list, add 6 + assume (($list, last) == 6) + $list, pop + assume (($list, last) == 5) + $list, remove index 1 + assume (($list, first) == 2) + assume (([1, 2] + [3, 4]) == [1, 2, 3, 4]) + +# Dict functionality +test: + $dict = {.x = 1, .y = 2, .z = 3} + assume (size of $dict) == 3 + assume [: for $k = $v in {.x = 1}: add {.key = $k, .value = $v}] == + [{.key = "x", .value = 1}] + assume ({.x = 1, .y = 1} + {.y = 10, .z = 10}) == {.x = 1, .y = 11, .z = 10} + assume ({.x = 1, .y = 1} | {.y = 10, .z = 10}) == {.x = 1, .y = 1, .z = 10} + assume ({.x = 1, .y = 1} & {.y = 10, .z = 10}) == {.y = 1} + assume ({.x = 1, .y = 1} ~ {.y = 10, .z = 10}) == {.x = 1, .z = 10} + +test: + assume (([[1, 2], [3, 4]] flattened) == [1, 2, 3, 4]) + +externally ($lists flattened) means: + $flat = [] + for $item in recursive $lists: + if ($item is a "List"): + for $ in $item: + recurse $item on $ + ..else: + $flat, add $item + return $flat + +test: + assume ((entries in {.x = 1}) == [{.key = "x", .value = 1}]) + +(entries in $dict) parses as [ + : for $k = $v in $dict: + add {.key = $k, .value = $v} +] + +test: + assume ((keys in {.x = 1}) == ["x"]) + +[keys in $dict, keys of $dict] all parse as [: for $k = $v in $dict: add $k] +test: + assume ((values in {.x = 1}) == [1]) +[values in $dict, values of $dict] all parse as [: for $k = $v in $dict: add $v] + +# Metatable stuff +test: + $t = {} + set $t's metatable to {.__tostring = ($ -> "XXX")} + assume ("\$t" == "XXX") + +(set $dict's metatable to $metatable) compiles to + "setmetatable(\($dict as lua expr), \($metatable as lua expr));" + +[$'s metatable, $'metatable] all compile to "getmetatable(\($ as lua expr))" +test: + assume (({} with fallback $ -> ($ + 1)).10 == 11) + +($dict with fallback $key -> $value) compiles to (" + (function(d) + local mt = {} + for k,v in pairs(getmetatable(d) or {}) do mt[k] = v end + mt.__index = function(self, \($key as lua expr)) + local value = \($value as lua expr) + self[\($key as lua expr)] = value + return value + end + return setmetatable(d, mt) + end)(\($dict as lua expr)) +") + +# Sorting +test: + $x = [3, 1, 2] + sort $x + assume ($x == [1, 2, 3]) + sort $x by $ = (- $) + assume ($x == [3, 2, 1]) + $keys = {.1 = 999, .2 = 0, .3 = 50} + sort $x by $ = $keys.$ + assume ($x == [2, 3, 1]) +(sort $items) compiles to "table.sort(\($items as lua expr));" +[sort $items by $item = $key_expr, sort $items by $item -> $key_expr] +..all parse as + do: + $keys = ({} with fallback $item -> $key_expr) + lua> "table.sort(\$items, function(x,y) return \$keys[x] < \$keys[y] end)" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + assume ((sorted [3, 1, 2]) == [1, 2, 3]) + +externally [$items sorted, sorted $items] all mean: + $copy = [: for $ in $items: add $] + sort $copy + return $copy + +[$items sorted by $item = $key, $items sorted by $item -> $key] all parse as + result of: + $copy = [: for $ in $items: add $] + sort $copy by $item = $key + return $copy + +test: + assume ((unique [1, 2, 1, 3, 2, 3]) == [1, 2, 3]) + +externally (unique $items) means: + $unique = [] + $seen = {} + for $ in $items: + unless $seen.$: + $unique, add $ + $seen.$ = (yes) + return $unique diff --git a/lib/core/control_flow.nom b/lib/core/control_flow.nom new file mode 100644 index 0000000..b0c4f27 --- /dev/null +++ b/lib/core/control_flow.nom @@ -0,0 +1,489 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains compile-time actions that define basic control flow structures + like "if" statements and loops. + +use "core/metaprogramming" +use "core/operators" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# No-Op +test: + do nothing +(do nothing) compiles to "" + +# Conditionals +test: + if (no): + fail "conditional fail" + +(if $condition $if_body) compiles to (" + if \($condition as lua expr) then + \($if_body as lua) + end +") + +test: + unless (yes): + fail "conditional fail" + +(unless $condition $unless_body) parses as (if (not $condition) $unless_body) +[ + if $condition $if_body else $else_body, unless $condition $else_body else $if_body +] all compile to (" + if \($condition as lua expr) then + \($if_body as lua) + else + \($else_body as lua) + end +") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Conditional expression (ternary operator) +# Note: this uses a function instead of "(condition and if_expr or else_expr)" + because that breaks if $if_expr is falsey, e.g. "x < 5 and false or 99" +test: + assume ((1 if (yes) else 2) == 1) + assume ((1 if (no) else 2) == 2) + +[ + $when_true_expr if $condition else $when_false_expr + $when_true_expr if $condition otherwise $when_false_expr + $when_false_expr unless $condition else $when_true_expr + $when_false_expr unless $condition then $when_true_expr +] all compile to: + # If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic + equivalent of a conditional expression: (cond and if_true or if_false) + if {.Text, .List, .Dict, .Number}.($when_true_expr.type): + return + Lua (" + (\($condition as lua expr) and \($when_true_expr as lua expr) or \ + ..\($when_false_expr as lua expr)) + ") + ..else: + # Otherwise, need to do an anonymous inline function (yuck, too bad lua + doesn't have a proper ternary operator!) + To see why this is necessary consider: (random()<.5 and false or 99) + return + Lua (" + ((function() + if \($condition as lua expr) then + return \($when_true_expr as lua expr) + else + return \($when_false_expr as lua expr) + end + end)()) + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# GOTOs +test: + $i = 0 + --- $loop --- + $i += 1 + unless ($i == 10): + go to $loop + assume ($i == 10) + --- (Loop) --- + $i -= 1 + unless ($i == 0): + go to (Loop) + assume ($i == 0) + +(--- $label ---) compiles to (" + ::label_\( + ($label.stub, as lua id) if ($label.type == "Action") else + $label as lua identifier + ):: +") + +(go to $label) compiles to (" + goto label_\( + ($label.stub, as lua id) if ($label.type == "Action") else + $label as lua identifier + ) +") + +# Basic loop control +(stop $var) compiles to: + if $var: + return (Lua "goto stop_\($var as lua identifier)") + ..else: + return (Lua "break") + +(do next $var) compiles to: + if $var: + return (Lua "goto continue_\($var as lua identifier)") + ..else: + return (Lua "goto continue") + +(---stop $var ---) compiles to "::stop_\($var as lua identifier)::" +(---next $var ---) compiles to "::continue_\($var as lua identifier)::" + +# While loops +test: + $x = 0 + repeat while ($x < 10): $x += 1 + assume ($x == 10) + repeat while ($x < 20): stop + assume ($x == 10) + repeat while ($x < 20): + $x += 1 + if (yes): + do next + fail "Failed to 'do next'" + assume ($x == 20) + +(repeat while $condition $body) compiles to: + $lua = + Lua (" + while \($condition as lua expr) do + \($body as lua) + ") + + if ($body has subtree \(do next)): + $lua, add "\n ::continue::" + + $lua, add "\nend --while-loop" + return $lua + +(repeat $body) parses as (repeat while (yes) $body) +(repeat until $condition $body) parses as (repeat while (not $condition) $body) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + $nums = [] + for $x in 1 to 5: + $nums, add $x + assume ($nums == [1, 2, 3, 4, 5]) + $nums = [] + for $x in 1 to 5 via 2: + $nums, add $x + assume ($nums == [1, 3, 5]) + $nums = [] + for $outer in 1 to 100: + for $inner in $outer to ($outer + 2): + if ($inner == 2): + $nums, add -2 + do next $inner + $nums, add $inner + if ($inner == 5): + stop $outer + assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5]) + +# Numeric range for loops +[ + for $var in $start to $stop by $step $body + for $var in $start to $stop via $step $body +] all compile to: + # This uses Lua's approach of only allowing loop-scoped variables in a loop + $lua = + Lua (" + for \($var as lua identifier)=\($start as lua expr),\($stop as lua expr),\ + ..\($step as lua expr) do + ") + $lua, add "\n " ($body as lua) + if ($body has subtree \(do next)): + $lua, add "\n ::continue::" + + if ($body has subtree \(do next $var)): + $lua, add "\n " (\(---next $var ---) as lua) + + $lua, add "\nend -- numeric for " ($var as lua identifier) " loop" + if ($body has subtree \(stop $var)): + $lua = + Lua (" + do -- scope for (stop \($var as lua identifier)) + \$lua + \(\(---stop $var ---) as lua) + end -- scope for (stop \($var as lua identifier)) + ") + return $lua + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(for $var in $start to $stop $body) parses as + for $var in $start to $stop via 1 $body + +test: + $x = 0 + repeat 5 times: + $x += 1 + assume $x == 5 + +(repeat $n times $body) parses as (for (=lua "_XXX_") in 1 to $n $body) +test: + $a = [10, 20, 30, 40, 50] + $b = [] + for $x in $a: + $b, add $x + assume ($a == $b) + $b = [] + for $x in $a: + if ($x == 10): + do next $x + + if ($x == 50): + stop $x + + $b, add $x + assume ($b == [20, 30, 40]) + +# For-each loop (lua's "ipairs()") +(for $var in $iterable at $i $body) compiles to: + # This uses Lua's approach of only allowing loop-scoped variables in a loop + $lua = + Lua (" + for \($i as lua identifier),\($var as lua identifier) in ipairs(\($iterable as lua expr)) do + \; + ") + $lua, add ($body as lua) + if ($body has subtree \(do next)): + $lua, add "\n ::continue::" + + if ($body has subtree \(do next $var)): + $lua, add "\n " (\(---next $var ---) as lua) + + $lua, add "\nend --for \($var as lua identifier) loop" + if ($body has subtree \(stop $var)): + $inner_lua = $lua + $lua = (Lua "do -- scope for stopping for-loop\n ") + $lua, add $inner_lua "\n " + $lua, add (\(---stop $var ---) as lua) + $lua, add "\nend -- end of scope for stopping for-loop" + return $lua + +(for $var in $iterable $body) parses as + for $var in $iterable at (=lua "__") $body + +test: + $d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50} + $result = [] + for $k = $v in $d: + if ($k == "a"): + do next $k + + if ($v == 20): + do next $v + + $result, add "\$k = \$v" + assume (($result sorted) == ["c = 30", "d = 40", "e = 50"]) + +# Dict iteration (lua's "pairs()") +[for $key = $value in $iterable $body, for $key $value in $iterable $body] +..all compile to: + $lua = + Lua (" + for \($key as lua identifier),\($value as lua identifier) in pairs(\ + ..\($iterable as lua expr)) do + ") + $lua, add "\n " ($body as lua) + if ($body has subtree \(do next)): + $lua, add "\n ::continue::" + + if ($body has subtree \(do next $key)): + $lua, add "\n " (\(---next $key ---) as lua) + + if ($body has subtree \(do next $value)): + $lua, add "\n " (\(---next $value ---) as lua) + + $lua, add "\nend --foreach-loop" + $stop_labels = (Lua "") + if ($body has subtree \(stop $key)): + $stop_labels, add "\n" (\(---stop $key ---) as lua) + + if ($body has subtree \(stop $value)): + $stop_labels, add "\n" (\(---stop $value ---) as lua) + + if ((size of "\$stop_labels") > 0): + $inner_lua = $lua + $lua = (Lua "do -- scope for stopping for $ = $ loop\n ") + $lua, add $inner_lua $stop_labels "\nend" + + return $lua + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + when: + (1 == 2) (100 < 0): + fail "bad conditional" + (1 == 0) (1 == 1) $not_a_variable.x: do nothing + (1 == 1): + fail "bad conditional" + + (1 == 2): + fail "bad conditional" + + else: + fail "bad conditional" + +# Multi-branch conditional (if..elseif..else) +(when $body) compiles to: + $code = (Lua "") + $clause = "if" + $else_allowed = (yes) + unless ($body.type is "Block"): + compile error at $body "'if' expected a Block, but got a \($body.type)." + "Perhaps you forgot to put a ':' after 'if'?" + + for $line in $body: + unless + (($line.type is "Action") and ((size of $line) >= 2)) and + $line.(size of $line) is "Block" syntax tree + ..: + compile error at $line "Invalid line for the body of an 'if' block." (" + Each line should contain one or more conditional expressions followed by a block, or "else"\ + .. followed by a block. + ") + $action = $line.(size of $line) + if (($line.1 is "else") and ((size of $line) == 2)): + unless $else_allowed: + compile error at $line "You can't have two 'else' blocks." + "Merge all of the 'else' blocks together." + + unless ((size of "\$code") > 0): + compile error at $line + .."You can't have an 'else' block without a preceding condition" (" + If you want the code in this block to always execute, you don't need a conditional block \ + ..around it. Otherwise, make sure the 'else' block comes last. + ") + + $code, add "\nelse\n " ($action as lua) + $else_allowed = (no) + ..else: + $code, add $clause " " + for $i in 1 to ((size of $line) - 1): + if ($i > 1): + $code, add " or " + $code, add ($line.$i as lua expr) + $code, add " then\n " ($action as lua) + $clause = "\nelseif" + + if ((size of "\$code") == 0): + compile error at $body "'if' block has an empty body." + "This means nothing would happen, so the 'if' block should be deleted." + + $code, add "\nend --when" + return $code + +test: + if 5 is: + 1 2 3: + fail "bad switch statement" + + 4 5: + do nothing + + 5 6: + fail "bad switch statement" + + else: + fail "bad switch statement" + +# Switch statement +[if $branch_value is $body, when $branch_value is $body] all compile to: + $code = (Lua "") + $clause = "if" + $else_allowed = (yes) + define mangler + unless ($body.type is "Block"): + compile error at $body "'if' expected a Block, but got a \($body.type)" + "Perhaps you forgot to put a ':' after the 'is'?" + + for $line in $body: + unless + (($line.type is "Action") and ((size of $line) >= 2)) and + $line.(size of $line) is "Block" syntax tree + ..: + compile error at $line "Invalid line for 'if' block." (" + Each line should contain expressions followed by a block, or "else" followed by a block + ") + $action = $line.(size of $line) + if (($line.1 is "else") and ((size of $line) == 2)): + unless $else_allowed: + compile error at $line "You can't have two 'else' blocks." + "Merge all of the 'else' blocks together." + + unless ((size of "\$code") > 0): + compile error at $line + .."You can't have an 'else' block without a preceding condition" (" + If you want the code in this block to always execute, you don't need a conditional block \ + ..around it. Otherwise, make sure the 'else' block comes last. + ") + + $code, add "\nelse\n " ($action as lua) + $else_allowed = (no) + ..else: + $code, add $clause " " + for $i in 1 to ((size of $line) - 1): + if ($i > 1): + $code, add " or " + $code, add "\(mangle "branch value") == " ($line.$i as lua expr) + $code, add " then\n " ($action as lua) + $clause = "\nelseif" + + if ((size of "\$code") == 0): + compile error at $body "'if' block has an empty body." + "This means nothing would happen, so the 'if' block should be deleted." + + $code, add "\nend --when" + return + Lua (" + do --if $ is... + local \(mangle "branch value") = \($branch_value as lua expr) + \$code + end -- if $ is... + ") + +# Do/finally +(do $action) compiles to (" + do + \($action as lua) + end -- do +") + +test: + assume ((result of: return 99) == 99) + +# Inline thunk: +(result of $body) compiles to "\(\(-> $body) as lua)()" +test: + $t = [1, [2, [[3], 4], 5, [[[6]]]]] + $flat = [] + for $ in recursive $t: + if ((lua type of $) is "table"): + for $2 in $: + recurse $ on $2 + ..else: + $flat, add $ + assume (sorted $flat) == [1, 2, 3, 4, 5, 6] + +# Recurion control flow +(recurse $v on $x) compiles to + Lua "table.insert(_stack_\($v as lua expr), \($x as lua expr))" +(for $var in recursive $structure $body) compiles to: + $lua = + Lua (" + do + local _stack_\($var as lua expr) = List{\($structure as lua expr)} + while #_stack_\($var as lua expr) > 0 do + \($var as lua expr) = table.remove(_stack_\($var as lua expr), 1) + \($body as lua) + ") + + if ($body has subtree \(do next)): + $lua, add "\n ::continue::" + + if ($body has subtree \(do next $var)): + $lua, add "\n \(\(---next $var ---) as lua)" + + $lua, add "\n end -- Recursive loop" + if ($body has subtree \(stop $var)): + $lua, add "\n \(\(---stop $var ---) as lua)" + $lua, add "\nend -- Recursive scope" + return $lua diff --git a/lib/core/coroutines.nom b/lib/core/coroutines.nom new file mode 100644 index 0000000..6a99f7e --- /dev/null +++ b/lib/core/coroutines.nom @@ -0,0 +1,40 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines the code that creates and manipulates coroutines + +use "core/metaprogramming" +use "core/operators" +use "core/control_flow" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + $co = + ->: + yield 4 + yield 5 + repeat 3 times: + yield 6 + $nums = [] + for $ in coroutine $co: + $nums, add $ + + unless ($nums == [4, 5, 6, 6, 6]): + fail "Coroutine iteration failed" + + $d = {.x = 0} + $co2 = + coroutine: + $d.x += 1 + yield 1 + $d.x += 1 + yield + $d.x += 1 + repeat while ((coroutine status of $co2) != "dead"): resume $co2 + assume $d.x == 3 +(coroutine $body) parses as (coroutine from (-> $body)) +(for $ in coroutine $co $body) compiles to (" + for \($ as lua expr) in coroutine_wrap(\($co as lua expr)) do + \($body as lua) + end +") diff --git a/lib/core/errors.nom b/lib/core/errors.nom new file mode 100644 index 0000000..c878bb7 --- /dev/null +++ b/lib/core/errors.nom @@ -0,0 +1,131 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains basic error reporting code + +use "core/metaprogramming" +use "core/operators" +use "core/control_flow" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(fail $msg) compiles to "error(\(($msg as lua expr) if $msg else "nil"), 0);" +(assume $condition) compiles to: + lua> (" + local \$assumption = 'Assumption failed: '..tostring((\$condition):get_source_code()) + ") + + return + Lua (" + if not \($condition as lua expr) then + error(\(quote "\$assumption"), 0) + end + ") + +(assume $a == $b) compiles to: + lua> "local \$assumption = 'Assumption failed: '..tostring(\(\($a == $b) as nomsu))" + + define mangler + + return + Lua (" + do + local \(mangle "a"), \(mangle "b") = \($a as lua expr), \($b as lua expr) + if \(mangle "a") ~= \(mangle "b") then + error(\(quote "\$assumption").."\\n"..tostring(\(mangle "a")).." != "..tostring(\ + ..\(mangle "b")), 0) + end + end + ") + +test: + try: fail + $worked = (no) + try: + fail "xx" + ..if it fails with $failure: + $worked = (yes) + ..if it succeeds: + fail "'try' incorrectly ran success case." + assume $failure == "xx" + unless $worked: + fail "'try' failed to recover from failure" + +# Try/except +[ + try $action if it succeeds $success if it fails with $msg $fallback + try $action if it fails with $msg $fallback if it succeeds $success +] all compile to: + $success_lua = ($success as lua) + if ((#"\$success_lua") > 0): + $success_lua, add "\n" + $success_lua, prepend "-- Success:\n" + $success_lua, + add "if not _fell_through then return table.unpack(_result, 2) end" + $fallback_lua = ($fallback as lua) + if ((#"\$fallback_lua") > 0): + $msg_lua = ($msg as lua expr) + if ((#"\$msg_lua") > 0): + $fallback_lua, prepend "\n\$msg_lua = _result[2]\n" + if ($msg_lua, text, is lua id): + $fallback_lua, add free vars [($msg_lua, text)] + $fallback_lua, prepend "-- Failure:" + return + Lua (" + do + local _fell_through = false + local _result = {pcall(function() + \($action as lua) + _fell_through = true + end)} + if _result[1] then + \$success_lua + else + \$fallback_lua + end + end + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(try $action) parses as + try $action if it succeeds (do nothing) if it fails (do nothing) + +(try $action if it fails $fallback) parses as + try $action if it succeeds (do nothing) if it fails $fallback + +(try $action if it fails with $msg $fallback) parses as + try $action if it succeeds (do nothing) if it fails with $msg $fallback + +(try $action if it succeeds $success) parses as + try $action if it succeeds $success if it fails (do nothing) + +(try $action if it fails $fallback if it succeeds $success) parses as + try $action if it fails with (=lua "") $fallback if it succeeds $success + +(try $action if it succeeds $success if it fails $fallback) parses as + try $action if it succeeds $success if it fails with (=lua "") $fallback + +test: + $success = (no) + try: + do: fail + ..then always: + $success = (yes) + ..if it succeeds: + fail "'try ... then always ...' didn't propagate failure" + + unless $success: + fail "'try ... then always ...' didn't execute the 'always' code" + +(do $action then always $final_action) compiles to (" + do -- do/then always + local _fell_through = false + local _results = {pcall(function() + \($action as lua) + _fell_through = true + end)} + \($final_action as lua) + if not _results[1] then error(_results[2], 0) end + if not _fell_through then return table.unpack(_results, 2) end + end +") diff --git a/lib/core/id.nom b/lib/core/id.nom new file mode 100644 index 0000000..d2427b5 --- /dev/null +++ b/lib/core/id.nom @@ -0,0 +1,66 @@ +#!/usr/bin/env nomsu -V6.14 +# + A simple UUID function based on RFC 4122: http://www.ietf.org/rfc/rfc4122.txt + +use "core/metaprogramming" +use "core/operators" +use "core/math" +use "core/collections" +use "core/control_flow" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +$NaN_surrogate = {} +$nil_surrogate = {} +$obj_by_id = {} +set $obj_by_id's metatable to {.__mode = "v"} +$id_by_obj = {} +set $id_by_obj's metatable to { + .__mode = "k" + .__index = + for ($self $key): + if ($key == (nil)): + return $self.$nil_surrogate + + if ($key != $key): + return $self.$NaN_surrogate + + --- (retry) --- + $id = (uuid) + if ($obj_by_id.$id != (nil)): go to (retry) + $self.$key = $id + $obj_by_id.$id = $key + return $id +} + +externally (uuid) means: + # Set all the other bits to randomly (or pseudo-randomly) chosen values. + $bytes = [ + # time-low, time-mid, time-high-and-version + randint (2 ^ (4 * 8)), randint (2 ^ (2 * 8)), randint (2 ^ (2 * 8 - 4)) + # clock-seq-and-reserved, clock-seq-low + randint (2 ^ (1 * 8 - 2)), randint (2 ^ (1 * 8)), randint (2 ^ (3 * 8)) + # node + randint (2 ^ (3 * 8)) + ] + + # Set the four most significant bits (bits 12 through 15) of the + # time_hi_and_version field to the 4-bit version number from + # Section 4.1.3. + $bytes.3 += 0x4000 + + # Set the two most significant bits (bits 6 and 7) of the + # clock_seq_hi_and_reserved to zero and one, respectively. + $bytes.4 += 0xC0 + return (=lua "('%08x-%04x-%04x-%02x%02x-%6x%6x'):format(unpack(\$bytes))") + +# For strict identity checking, use ($x's id) == ($y's id) +test: + assume (([] == []) and ((id of []) != (id of []))) + seed random with 0 + $x = [] + assume ((id of $x) == (id of $x)) + seed random with 0 + assume ((id of $x) != (id of [])) + seed random +externally [id of $, $'s id, $'id] all mean $id_by_obj.$ diff --git a/lib/core/init.nom b/lib/core/init.nom new file mode 100644 index 0000000..0c8051d --- /dev/null +++ b/lib/core/init.nom @@ -0,0 +1,11 @@ +# Export everything +export "core/metaprogramming" +export "core/operators" +export "core/control_flow" +export "core/errors" +export "core/collections" +export "core/coroutines" +export "core/math" +export "core/id" +export "core/io" +export "core/text" diff --git a/lib/core/io.nom b/lib/core/io.nom new file mode 100644 index 0000000..7afe889 --- /dev/null +++ b/lib/core/io.nom @@ -0,0 +1,29 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains basic input/output code + +use "core/metaprogramming" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(say $message) compiles to: + lua> (" + if \$message.type == "Text" then + return LuaCode("say(", \($message as lua expr), ");"); + else + return LuaCode("say(tostring(", \($message as lua expr), "));"); + end + ") + +(say $message inline) compiles to: + lua> (" + if \$message.type == "Text" then + return LuaCode("io.write(", \($message as lua expr), ")"); + else + return LuaCode("io.write(tostring(", \($message as lua expr), "))"); + end + ") + +externally (ask $prompt) means: + $io.write $prompt + return ($io.read()) diff --git a/lib/core/math.nom b/lib/core/math.nom new file mode 100644 index 0000000..685ab1e --- /dev/null +++ b/lib/core/math.nom @@ -0,0 +1,212 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines some common math literals and functions + +use "core/metaprogramming" +use "core/text" +use "core/operators" +use "core/control_flow" +use "core/collections" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Literals: +test: + unless (all of [inf, NaN, pi, tau, golden ratio, e]): + fail "math constants failed" + $nan = (NaN) + unless ($nan != $nan): + fail "NaN failed" +[infinity, inf] all compile to "math.huge" +[not a number, NaN, nan] all compile to "(0/0)" +[pi, Pi, PI] all compile to "math.pi" +[tau, Tau, TAU] all compile to "(2*math.pi)" +(golden ratio) compiles to "((1+math.sqrt(5))/2)" +(e) compiles to "math.exp(1)" + +# Functions: +test: + assume (("5" as a number) == 5) +external $($ as a number) = $(tonumber $) +external $($ as number) = $(tonumber $) +test: + unless + all of [ + abs 5, | 5 |, sqrt 5, √ 5, sine 5, cosine 5, tangent 5, arc sine 5, arc cosine 5 + arc tangent 5, arc tangent 5 / 10, hyperbolic sine 5, hyperbolic cosine 5 + hyperbolic tangent 5, e^ 5, ln 5, log 5 base 2, floor 5, ceiling 5, round 5 + ] + ..: + fail "math functions failed" +external [$(absolute value $), $(absolute value of $), $(| $ |), $(abs $)] = + [$math.abs, $math.abs, $math.abs, $math.abs] +external [$(square root $), $(square root of $), $(√ $), $(sqrt $)] = + [$math.sqrt, $math.sqrt, $math.sqrt, $math.sqrt] +external [$(sine $), $(sin $)] = [$math.sin, $math.sin] +external [$(cosine $), $(cos $)] = [$math.cos, $math.cos] +external [$(tangent $), $(tan $)] = [$math.tan, $math.tan] +external [$(arc sine $), $(asin $)] = [$math.asin, $math.asin] +external [$(arc cosine $), $(acos $)] = [$math.acos, $math.acos] +external [$(arc tangent $), $(atan $)] = [$math.atan, $math.atan] +external [$(arc tangent $y / $x), $(atan2 $y $x)] = [$math.atan2, $math.atan2] +external [$(hyperbolic sine $), $(sinh $)] = [$math.sinh, $math.sinh] +external [$(hyperbolic cosine $), $(cosh $)] = [$math.cosh, $math.cosh] +external [$(hyperbolic tangent $), $(tanh $)] = [$math.tanh, $math.tanh] +external [$(e^ $), $(exp $)] = [$math.exp, $math.exp] +external [$(natural log $), $(ln $), $(log $), $(log $ base $)] = [$math.log, $math.log, $math.log, $math.log] +external $(floor $) = $math.floor +external [$(ceiling $), $(ceil $)] = [$math.ceil, $math.ceil] +externally [round $, $ rounded] all mean + floor ($ + 0.5) +test: + unless ((463 to the nearest 100) == 500): fail "rounding failed" + unless ((2.6 to the nearest 0.25) == 2.5): fail "rounding failed" + +externally ($n to the nearest $rounder) means + $rounder * (floor ($n / $rounder + 0.5)) + +# Any/all +externally [all of $items, all $items] all mean: + for $ in $items: + unless $: + return (no) + return (yes) +[not all of $items, not all $items] all parse as (not (all of $items)) +externally [any of $items, any $items] all mean: + for $ in $items: + if $: + return (yes) + return (no) +[none of $items, none $items] all parse as (not (any of $items)) + +# Sum/product +externally [sum of $items, sum $items] all mean: + $total = 0 + for $ in $items: + $total += $ + return $total + +externally [product of $items, product $items] all mean: + $prod = 1 + for $ in $items: + $prod *= $ + return $prod + +externally [avg of $items, average of $items] all mean + (sum of $items) / (size of $items) + +# Min/max +externally [min of $items, smallest of $items, lowest of $items] all mean: + $best = (nil) + for $ in $items: + if (($best == (nil)) or ($ < $best)): $best = $ + return $best + +externally [ + max of $items, biggest of $items, largest of $items, highest of $items +] all mean: + $best = (nil) + for $ in $items: + if (($best == (nil)) or ($ > $best)): $best = $ + return $best + +test: + assume ((min of [3, -4, 1, 2] by $ = ($ * $)) == 1) + assume ((max of [3, -4, 1, 2] by $ = ($ * $)) == -4) + +(min of $items by $item = $value_expr) parses as + result of: + $best = (nil) + $best_key = (nil) + for $item in $items: + $key = $value_expr + if (($best == (nil)) or ($key < $best_key)): + $best = $item + $best_key = $key + return $best + +(max of $items by $item = $value_expr) parses as + result of: + $best = (nil) + $best_key = (nil) + for $item in $items: + $key = $value_expr + if (($best == (nil)) or ($key > $best_key)): + $best = $item + $best_key = $key + return $best + +test: + assume (100 clamped between 0 and 10) == 10 + +externally ($ clamped between $min and $max) means: + when: + ($ < $min): + return $min + + ($ > $max): + return $max + + else: + return $ + +test: + assume (-0.1 smoothed by 2.7) == 0 + assume (0 smoothed by 2.7) == 0 + assume (0.5 smoothed by 2.7) == 0.5 + assume (1 smoothed by 2.7) == 1 + assume (1.1 smoothed by 2.7) == 1 + +externally ($ smoothed by $smoothness) means: + $ = ($ clamped between 0 and 1) + if ($smoothness == 0): return $ + $k = (2 ^ $smoothness) + if ($ < 0.5): + return (0.5 * (2 * $) ^ $k) + ..else: + return (1 - 0.5 * (2 - 2 * $) ^ $k) + +test: + assume (5 to 7 mixed by -1.0) == 5 + assume (5 to 7 mixed by 0.0) == 5 + assume (5 to 7 mixed by 0.5) == 6 + assume (5 to 7 mixed by 1.0) == 7 + assume (5 to 7 mixed by 2.0) == 7 + +externally ($lo to $hi mixed by $amount) means: + $ = ($amount clamped between 0 and 1) + return ((1 - $) * $lo + $ * $hi) + +test: + assume ([0, 1, 11] mixed by 0.0) == 0 + assume ([0, 1, 11] mixed by 0.25) == 0.5 + assume ([0, 1, 11] mixed by 0.5) == 1 + assume ([0, 1, 11] mixed by 0.75) == 6 + assume ([0, 1, 11] mixed by 1.0) == 11 + assume ([99] mixed by 0.5) == 99 + +externally ($nums mixed by $amount) means: + $ = ($amount clamped between 0 and 1) + $i = (1 + ($ * ((#$nums) - 1))) + if ((floor $i) == (#$nums)): + return $nums.(floor $i) + [$lo, $hi] = [$nums.(floor $i), $nums.(floor ($i + 1))] + return ($lo to $hi mixed by ($i mod 1)) + +# Random functions +externally (seed random with $) means: + lua> (" + math.randomseed(\$); + for i=1,20 do math.random(); end + ") +(seed random) parses as (seed random with (=lua "os.time()")) +[random number, random, rand] all compile to "math.random()" +[random int $n, random integer $n, randint $n] all compile to + "math.random(\($n as lua expr))" + +[random from $low to $high, random number from $low to $high, rand $low $high] +..all compile to "math.random(\($low as lua expr), \($high as lua expr))" + +externally [ + random choice from $elements, random choice $elements, random $elements +] all mean (=lua "\$elements[math.random(#\$elements)]") diff --git a/lib/core/metaprogramming.nom b/lib/core/metaprogramming.nom new file mode 100644 index 0000000..936f9bd --- /dev/null +++ b/lib/core/metaprogramming.nom @@ -0,0 +1,459 @@ +#!/usr/bin/env nomsu -V6.14 +# + This File contains actions for making actions and compile-time actions and some helper + functions to make that easier. + +lua> "NOMSU_CORE_VERSION = 14" +lua> "NOMSU_LIB_VERSION = 8" +lua> (" + do + local mangle_index = 0 + function mangler() + local my_mangle_index = mangle_index + mangle_index = mangle_index + 1 + return function(varname) + return (varname..(("\\3%X"):format(my_mangle_index))):as_lua_id() + end + end + end + COMPILE_RULES["define mangler"] = function(\(nomsu environment)) + return LuaCode("local mangle = mangler()") + end +") + +lua> (" + COMPILE_RULES["1 ->"] = function(\(nomsu environment), \$args, \$body) + if \$args and not \$body then \$args, \$body = {}, \$args end + local body_lua = SyntaxTree:is_instance(\$body) and \(nomsu environment):compile(\$body) or \$body + if SyntaxTree:is_instance(\$body) and \$body.type ~= "Block" then body_lua:prepend("\ + ..return ") end + local lua = LuaCode("(function(") + if SyntaxTree:is_instance(\$args) and (\$args.type == "Action" or \$args.type == "MethodCall") then + \$args = \$args:get_args() + elseif SyntaxTree:is_instance(\$args) and \$args.type == "Var" then \$args = {\$args} end + for i, arg in ipairs(\$args) do + local arg_lua = SyntaxTree:is_instance(arg) and \(nomsu environment):compile(arg):text() or arg + if arg_lua == "..." then + if i < #\$args then + compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, + "Extra arguments must come last.", "Try removing any arguments after \ + ..(*extra arguments*)") + end + elseif not arg_lua:is_lua_id() then + compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, + "This does not compile to a Lua identifier, so it can't be used as a function \ + ..argument.", + "This should probably be a Nomsu variable instead (like $x).") + end + lua:add(i > 1 and ", " or "", arg_lua) + body_lua:remove_free_vars({arg_lua}) + end + body_lua:declare_locals() + lua:add(")\\n ", body_lua, "\\nend)") + return lua + end + COMPILE_RULES["->"] = COMPILE_RULES["1 ->"] + COMPILE_RULES["for"] = COMPILE_RULES["1 ->"] +") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (five) compiles to "5" + +test: + unless ((five) == 5): + fail "Compile to expression failed." + (loc x) compiles to "local x = 99;" + +test: + lua> "do" + loc x + unless ($x is 99): + fail "Compile to statements with locals failed." + lua> "end" + unless ($x is (nil)): + fail "Failed to properly localize a variable." + + (asdf) compiles to: + $tmp = "" + return (Lua $tmp) + +test: + asdf + unless ($tmp is (nil)): + fail "compile to is leaking variables" + +lua> (" + COMPILE_RULES["1 compiles to"] = function(\(nomsu environment), \$action, \$body) + local \$args = List{"\(nomsu environment)", unpack(\$action:get_args())} + if \$body.type == "Text" then + \$body = SyntaxTree{source=\$body.source, type="Action", "Lua", \$body} + end + return LuaCode("COMPILE_RULES[", \$action:get_stub():as_lua(), + "] = ", \(\($args -> $body) as lua)) + end +") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +($actions all compile to $body) compiles to: + lua> (" + if \$actions.type ~= "List" then + compile_error(\$actions, "This should be a list of actions.") + end + local lua = \(\($actions.1 compiles to $body) as lua) + local \$args = List{"\(nomsu environment)", unpack(\$actions[1]:get_args())} + local \$compiled_args = List{"\(nomsu environment)"}; + for i=2,#\$args do \$compiled_args[i] = \(nomsu environment):compile(\$args[i]) end + for i=2,#\$actions do + local alias = \$actions[i] + local \$alias_args = List{"\(nomsu environment)", unpack(alias:get_args())} + lua:add("\\nCOMPILE_RULES[", alias:get_stub():as_lua(), "] = ") + if \$alias_args == \$args then + lua:add("COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "]") + else + lua:add("function(") + local \$compiled_alias_args = List{"\(nomsu environment)"}; + for i=2,#\$alias_args do \$compiled_alias_args[i] = \(nomsu environment):compile(\$alias_args[i]) end + lua:concat_add(\$compiled_alias_args, ", ") + lua:add(") return COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "](") + lua:concat_add(\$compiled_args, ", ") + lua:add(") end") + end + end + return lua + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (foo $x) means "outer" + with [$(foo $)]: + (foo $x) means: + $y = ($x + 1) + return $y + + unless ((foo 10) == 11): + fail "Action didn't work." + + unless ($y is (nil)): + fail "Action leaked a local into globals." + + (baz $) parses as (foo $) + assume ((foo 1) == "outer") + +($action means $body) compiles to: + lua> (" + + local lua = LuaCode() + if \$action.type == "MethodCall" then + lua:add(\(nomsu environment):compile(\$action[1]), ".", \$action[2]:get_stub():as_lua_id()) + elseif \$action.type == "Action" then + lua:add(\$action:get_stub():as_lua_id()) + lua:add_free_vars({\$action:get_stub():as_lua_id()}) + else + compile_error_at(\$action, "Expected an action or method call here") + end + lua:add(" = ", \(\($action -> $body) as lua), ";") + return lua + ") + +($actions all mean $body) compiles to: + lua> (" + local lua = \(\($actions.1 means $body) as lua) + local first_def = (\$actions[1].type == "MethodCall" + and LuaCode(\(nomsu environment):compile(\$actions[1][1]), ".", \$actions[1]:get_stub():as_lua_id()) + or LuaCode(\$actions[1]:get_stub():as_lua_id())) + local \$args = List(\$actions[1]:get_args()) + for i=2,#\$actions do + local alias = \$actions[i] + local \$alias_args = List(alias:get_args()) + lua:add("\\n") + if alias.type == "MethodCall" then + lua:add(\(nomsu environment):compile(alias[1]), ".", alias:get_stub():as_lua_id()) + else + lua:add(alias:get_stub():as_lua_id()) + lua:add_free_vars({alias_name}) + end + if \$args == \$alias_args then + lua:add(" = ", first_def, ";") + else + lua:add(" = ", \(\($alias_args -> $actions.1) as lua), ";") + end + end + return lua + ") + +test: + externally (baz1) means: + return "baz1" + externally (baz2) means "baz2" + +test: + assume ((baz1) == "baz1") + assume ((baz2) == "baz2") + +(externally $action means $body) compiles to: + lua> (" + local lua = \(\($action means $body) as lua) + lua:remove_free_vars({\$action:get_stub():as_lua_id()}) + return lua + ") + +(externally $actions all mean $body) compiles to: + lua> (" + local lua = \(\($actions all mean $body) as lua) + lua:remove_free_vars(table.map(\$actions, function(a) return a:get_stub():as_lua_id() end)) + return lua + ") + +test: + (swap $x and $y) parses as + do: + $tmp = $x + $x = $y + $y = $tmp + +test: + [$1, $2] = [1, 2] + swap $1 and $2 + unless (($1 == 2) and ($2 == 1)): + fail "'parse $ as $' failed on 'swap $ and $'" + [$tmp, $tmp2] = [1, 2] + swap $tmp and $tmp2 + unless (($tmp == 2) and ($tmp2 == 1)): + fail "'parse $ as $' variable mangling failed." + +($actions all parse as $body) compiles to: + lua> (" + local replacements = {} + if \$actions.type ~= "List" then + compile_error(\$actions, "This should be a list.") + end + for i,arg in ipairs(\$actions[1]:get_args()) do + replacements[arg[1]] = \(nomsu environment):compile(arg):text() + end + local function make_tree(t) + if SyntaxTree:is_instance(t) and t.type == "Var" then + if replacements[t:as_var()] then + return replacements[t:as_var()] + else + return "SyntaxTree{mangle("..t:as_var():as_lua().."), type="..t.type:as_lua()..", \ + ..source="..tostring(t.source):as_lua().."}" + end + elseif SyntaxTree:is_instance(t) then + local ret = {} + local i = 1 + for k, v in pairs(t) do + if k == i then + ret[#ret+1] = make_tree(t[i]) + i = i + 1 + elseif k == "source" then + ret[#ret+1] = k.."= "..tostring(v):as_lua() + elseif lua_type_of(k) == 'string' and k:is_a_lua_id() then + ret[#ret+1] = k.."= "..make_tree(v) + else + ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v) + end + end + return "SyntaxTree{"..table.concat(ret, ", ").."}" + elseif lua_type_of(t) == 'number' then + return tostring(t) + else + return t:as_lua() + end + end + local \$new_body = LuaCode:from(\$body.source, + "local mangle = mangler()", + "\\nreturn ", make_tree(\$body)) + local ret = \(\($actions all compile to $new_body) as lua) + return ret + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +[$action parses as $body] all parse as ([$action] all parse as $body) +externally (in (nomsu environment) $tree as lua expr) means: + lua> (" + local tree_lua = \(nomsu environment):compile(\$tree) + if \$tree.type == 'Block' then + tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()') + elseif \$tree.type == 'MethodCall' and #\$tree > 2 then + compile_error_at(\$tree, "This must be a single value instead of "..(#\$tree - 1).."\ + .. method calls.", + "Replace this with a single method call.") + end + return tree_lua + ") + +# Need to make sure the proper environment is used for compilation (i.e. the caller's environment) +($tree as lua expr) compiles to (\(in \(nomsu environment) $tree as lua expr) as lua) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +externally [$var as lua identifier, $var as lua id] all mean: + lua> (" + local lua = \($var as lua) + if not lua:text():is_a_lua_id() then + compile_error(\$var, + "This is supposed to be something that compiles to a valid Lua identifier.", + "This should probably be a variable.") + end + return lua + ") + +test: + (num args (*extra arguments*)) means (select "#" (*extra arguments*)) + assume (num args 1 2 3) == 3 + (extra args (*extra arguments*)) means [*extra arguments*] + assume (extra args 1 2 3) == [1, 2, 3] + (third arg (*extra arguments*)) means (select 3 (*extra arguments*)) + assume (third arg 5 6 7 8) == 7 + +(*extra arguments*) compiles to "..." + +($ is syntax tree) compiles to "SyntaxTree:is_instance(\($ as lua expr))" + +externally ($ is $kind syntax tree) means + =lua "SyntaxTree:is_instance(\$) and \$.type == \$kind" + +($tree with $t -> $replacement) compiles to (" + \($tree as lua expr):map(function(\($t as lua expr)) + \( + =lua (" + \$replacement.type == 'Block' and \($replacement as lua) or 'return '..\ + ..\($replacement as lua expr):text() + ") + ) + end) +") + +externally ($tree with vars $replacements) means + =lua (" + \$tree:map(function(\$t) + if \$t.type == "Var" then + return \$replacements[\$t:as_var()] + end + end) + ") + +(tree $tree with vars $replacements) compiles to (" + \(=lua "(\$tree):as_lua()"):map(function(t) + if t.type == "Var" then + return \($replacements as lua expr)[t:as_var()] + end + end) +") + +($tree has subtree $match_tree) compiles to (" + (function() + local match_tree = \($match_tree as lua expr) + for subtree in coroutine_wrap(function() \($tree as lua expr):map(yield) end) do + if subtree == match_tree then return true end + end + end)() +") + +externally (match $tree with $patt) means: + lua> (" + if \$patt.type == "Var" then return Dict{[\$patt:as_var()]=\$tree} end + if \$patt.type == "Action" and \$patt:get_stub() ~= \$tree:get_stub() then return nil end + if #\$patt ~= #\$tree then return nil end + local matches = Dict{} + for \($i)=1,#\$patt do + if SyntaxTree:is_instance(\$tree[\$i]) then + local submatch = \(match $tree.$i with $patt.$i) + if not submatch then return nil end + for k,v in pairs(submatch) do + if matches[k] and matches[k] ~= v then return nil end + matches[k] = v + end + end + end + return matches + ") + +test: + assume + ( + quote (" + one + "two" + ") + ) == "\"one\\n\\\"two\\\"\"" + +(quote $s) compiles to "tostring(\($s as lua expr)):as_lua()" +test: + assume (lua type of {}) == "table" + assume (type of {}) == "Dict" + assume ({} is a "Dict") + assume ("" is text) + assume ("" isn't a "Dict") +externally ($ is text) means (=lua "\(lua type of $) == 'string'") +externally [$ is not text, $ isn't text] all mean + =lua "\(lua type of $) ~= 'string'" + +externally (type of $) means: + lua> (" + local lua_type = \(lua type of $) + if lua_type == 'string' then return 'Text' + elseif lua_type == 'table' or lua_type == 'userdata' then + local mt = getmetatable(\$) + if mt and mt.__type then return mt.__type end + end + return lua_type + ") + +[$ is a $type, $ is an $type] all parse as ((type of $) == $type) +[$ isn't a $type, $ isn't an $type, $ is not a $type, $ is not an $type] +..all parse as ((type of $) != $type) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (foo) means: + return 100 200 300 + assume (select 2 (foo)) == 200 + +# Return statement is wrapped in a do..end block because Lua is unhappy if you + put code after a return statement, unless you wrap it in a block. +(return (*extra arguments*)) compiles to: + lua> (" + local lua = \(Lua "do return ") + for i=1,select('#',...) do + if i > 1 then lua:add(", ") end + lua:add(\(nomsu environment):compile((select(i, ...)))) + end + lua:add(" end") + return lua + ") + +# Literals +(yes) compiles to "true" +(no) compiles to "false" +[nothing, nil, null] all compile to "nil" +(Nomsu syntax version) compiles to "NOMSU_SYNTAX_VERSION" +(Nomsu compiler version) compiles to "NOMSU_COMPILER_VERSION" +(core version) compiles to "NOMSU_CORE_VERSION" +(lib version) compiles to "NOMSU_LIB_VERSION" +(command line args) compiles to "COMMAND_LINE_ARGS" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# + (with local compile actions $body) compiles to (" + do + local OLD_RULES = COMPILE_RULES + local OLD_ENV = \(nomsu environment) + local \(nomsu environment) = setmetatable({ + COMPILE_RULES=setmetatable({}, {__index=OLD_RULES}) + }, {__index=OLD_ENV}) + \($body as lua) + end + ") + +externally (Nomsu version) means: + return (" + \(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version) + ") diff --git a/lib/core/operators.nom b/lib/core/operators.nom new file mode 100644 index 0000000..dee76b6 --- /dev/null +++ b/lib/core/operators.nom @@ -0,0 +1,263 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains definitions of operators like "+" and "and". + +use "core/metaprogramming" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + assume (all [1 < 2, 2 > 1, 1 <= 2, 2 >= 1, 1 == 1, 1 != 2]) + +# Comparison Operators +($x < $y) compiles to "(\($x as lua expr) < \($y as lua expr))" +($x > $y) compiles to "(\($x as lua expr) > \($y as lua expr))" +($x <= $y) compiles to "(\($x as lua expr) <= \($y as lua expr))" +($x >= $y) compiles to "(\($x as lua expr) >= \($y as lua expr))" +[$a is $b, $a == $b] all compile to "(\($a as lua expr) == \($b as lua expr))" +[$a isn't $b, $a is not $b, $a not= $b, $a != $b] all compile to + "(\($a as lua expr) ~= \($b as lua expr))" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + $x = 10 + assume ($x == 10) + [$x, $y] = [10, 20] + unless (($x == 10) and ($y == 20)): + fail "mutli-assignment failed." + [$x, $y] = [$y, $x] + unless (($y == 10) and ($x == 20)): + fail "swapping vars failed." + $vals = [4, 5] + [$x, $y] = (unpack $vals) + unless (($x == 4) and ($y == 5)): + fail "unpacking failed" + +# Variable assignment operator +($var = $value) compiles to: + lua> (" + local lua = LuaCode() + if \$var.type == "List" then + for i, \$assignment in ipairs(\$var) do + if i > 1 then lua:add(", ") end + local assignment_lua = \($assignment as lua expr) + lua:add(assignment_lua) + if \$assignment.type == 'Var' then + lua:add_free_vars({assignment_lua:text()}) + end + end + lua:add(' = ') + if \$value.type == "List" then + if #\$value ~= #\$var then + compile_error_at(\$value, + "This assignment has too "..(#\$value > #\$var and "many" or "few").." values.", + "Make sure it has the same number of values on the left and right hand side \ + ..of the '=' operator.") + end + for i, \$val in ipairs(\$value) do + if i > 1 then lua:add(", ") end + local val_lua = \($val as lua expr) + lua:add(val_lua) + end + lua:add(";") + else + lua:add(\($value as lua expr), ';') + end + else + local var_lua = \($var as lua expr) + lua:add(var_lua) + if \$var.type == 'Var' then + lua:add_free_vars({var_lua:text()}) + end + lua:add(' = ', \($value as lua expr)) + end + return lua + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + [$foozle, $y] = ["outer", "outer"] + externally (set global x local y) means: + external $foozle = "inner" + $y = "inner" + set global x local y + unless (($foozle == "inner") and ($y == "outer")): fail "external failed." +(external $var = $value) compiles to: + $lua = ((SyntaxTree {.type = "Action", .source = $var.source, .1 = $var, .2 = "=", .3 = $value}) as lua) + $lua, remove free vars + return $lua +test: + [$foozle, $y] = ["outer", "outer"] + externally (set global x local y) means: + with external [$foozle]: + $foozle = "inner" + $y = "inner" + set global x local y + unless (($foozle == "inner") and ($y == "outer")): + fail "'with external' failed." + +(with external $externs $body) compiles to: + $body_lua = ($body as lua) + lua> (" + \$body_lua:remove_free_vars(table.map(\$externs, function(v) return \(nomsu environment):compile(v):text() end)) + ") + return $body_lua + +test: + [$x, $y] = [1, 2] + with [$z, $x = 999]: + assume $z == (nil) + $z = 999 + unless ($z == 999): + fail "'with' failed." + + unless ($x == 999): + fail "'with' assignment failed." + + unless ($x == 1): + fail "'with' scoping failed" + + unless ($z == (nil)): + fail "'with' scoping failed" + +(with $assignments $body) compiles to: + lua> (" + local \$defs = LuaCode() + for i, \$item in ipairs(\$assignments) do + if i > 1 then \$defs:add("\\n") end + local item_lua = \($item as lua) + if \$item.type == 'Action' and \$item.stub == '1 =' then + item_lua:remove_free_vars(item_lua.free_vars) + end + \$defs:add("local ", item_lua, ";") + end + ") + + return + Lua (" + do + \$defs + \($body as lua) + end -- 'with' block + ") + +# Math Operators +test: + unless ((5 wrapped around 2) == 1): + fail "mod not working" + +[$x wrapped around $y, $x mod $y] all compile to + "((\($x as lua expr)) % (\($y as lua expr)))" + +# 3-part chained comparisons +# (uses a lambda to avoid re-evaluating middle value, while still being an expression) +test: + $calls = 0 + (one) means: + external $calls = ($calls + 1) + return 1 + + unless (0 <= (one) <= 2): + fail "Three-way chained comparison failed." + + unless ($calls == 1): + fail "Three-way comparison evaluated middle value multiple times" +($x < $y < $z) parses as ((($a $b $c) -> (($a < $b) and ($b < $c))) $x $y $z) +($x <= $y < $z) parses as ((($a $b $c) -> (($a <= $b) and ($b < $c))) $x $y $z) +($x < $y <= $z) parses as ((($a $b $c) -> (($a < $b) and ($b <= $c))) $x $y $z) +($x <= $y <= $z) parses as ((($a $b $c) -> (($a <= $b) and ($b <= $c))) $x $y $z) +($x > $y > $z) parses as ((($a $b $c) -> (($a > $b) and ($b > $c))) $x $y $z) +($x >= $y > $z) parses as ((($a $b $c) -> (($a >= $b) and ($b > $c))) $x $y $z) +($x > $y >= $z) parses as ((($a $b $c) -> (($a > $b) and ($b >= $c))) $x $y $z) +($x >= $y >= $z) parses as ((($a $b $c) -> (($a >= $b) and ($b >= $c))) $x $y $z) + +# TODO: optimize for common case where x,y,z are all either variables or number literals +# Boolean Operators +test: + (barfer) means (fail "short circuiting failed") + assume (((no) and (barfer)) == (no)) + assume ((no) or (yes)) + assume ((yes) or (barfer)) +($x and $y) compiles to "(\($x as lua expr) and \($y as lua expr))" +($x or $y) compiles to "(\($x as lua expr) or \($y as lua expr))" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Bitwise Operators +# TODO: implement OR, XOR, AND for multiple operands? +test: + assume ((~ (~ 5)) == 5) + assume ((1 | 4) == 5) + assume ((1 ~ 3) == 2) + assume ((1 & 3) == 1) + assume ((1 << 2) == 4) + assume ((4 >> 2) == 1) + +# Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise + fall back to bit.bor(), bit.band(), etc. +lua> "if \((is jit) or ((Lua version) == "Lua 5.2")) then" +[NOT $, ~ $] all compile to "bit.bnot(\($ as lua expr))" +[$x OR $y, $x | $y] all compile to + "bit.bor(\($x as lua expr), \($y as lua expr))" + +[$x XOR $y, $x ~ $y] all compile to + "bit.bxor(\($x as lua expr), \($y as lua expr))" + +[$x AND $y, $x & $y] all compile to + "bit.band(\($x as lua expr), \($y as lua expr))" + +[$x LSHIFT $shift, $x << $shift] all compile to + "bit.lshift(\($x as lua expr), \($shift as lua expr))" + +[$x RSHIFT $shift, $x >> $shift] all compile to + "bit.rshift(\($x as lua expr), \($shift as lua expr))" + +lua> "else" +[NOT $, ~ $] all compile to "~(\($ as lua expr))" +[$x OR $y, $x | $y] all compile to "(\($x as lua expr) | \($y as lua expr))" +[$x XOR $y, $x ~ $y] all compile to "(\($x as lua expr) ~ \($y as lua expr))" +[$x AND $y, $x & $y] all compile to "(\($x as lua expr) & \($y as lua expr))" +[$x LSHIFT $shift, $x << $shift] all compile to + "(\($x as lua expr) << \($shift as lua expr))" + +[$x RSHIFT $shift, $x >> $shift] all compile to + "(\($x as lua expr) >> \($shift as lua expr))" + +lua> "end" + +# Unary operators +test: + assume ((- 5) == -5) + assume ((not (yes)) == (no)) +(- $) compiles to "(- \($ as lua expr))" +(not $) compiles to "(not \($ as lua expr))" +test: + assume ((size of [1, 2, 3]) == 3) + assume ((#[1, 2, 3]) == 3) +[#$list, size of $list] all compile to "(#\($list as lua expr))" +($list is empty) compiles to "(#\($list as lua expr) == 0)" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Update operators +test: + $x = 1 + $x += 1 + unless ($x == 2): + fail "+= failed" + $x *= 2 + unless ($x == 4): + fail "*= failed" + wrap $x around 3 + unless ($x == 1): + fail "wrap around failed" +($var += $) parses as ($var = (($var or 0) + $)) +($var -= $) parses as ($var = (($var or 0) - $)) +($var *= $) parses as ($var = (($var or 1) * $)) +($var /= $) parses as ($var = ($var / $)) +($var ^= $) parses as ($var = ($var ^ $)) +($var and= $) parses as ($var = ($var and $)) +($var or= $) parses as ($var = ($var or $)) +(wrap $var around $) parses as ($var = ($var wrapped around $)) diff --git a/lib/core/text.nom b/lib/core/text.nom new file mode 100644 index 0000000..1351af6 --- /dev/null +++ b/lib/core/text.nom @@ -0,0 +1,78 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file contains some definitions of text escape sequences, including ANSI console + color codes. + +use "core/metaprogramming" +use "core/operators" +use "core/control_flow" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + assume "\[1, 2, 3]" == "[1, 2, 3]" + assume "foo = \(1 + 2)!" == "foo = 3!" + assume (" + one + two + ") == (" + one + two + ") + assume "nogap" == "nogap" + assume (["x", "y"], joined with ",") == "x,y" + assume (["x", "y"], joined) == "xy" + assume ("BAR", byte 2) == 65 + assume ("BAR", bytes 1 to 2) == [66, 65] + assume ("asdf", capitalized) == "Asdf" + assume ("asdf", uppercase) == "ASDF" + assume ("asdf", with "s" -> "X") == "aXdf" + assume + (" + one + two + + "), lines + ..== ["one", "two", ""] + + ($spec とは $body) parses as ($spec means $body) + +test: + $こんにちは = "こんにちは" + ($ と言う) とは "\($)世界" + assume ($こんにちは と言う) == "こんにちは世界" + +($expr for $match in $text matching $patt) compiles to: + define mangler + return + Lua (" + (function() + local \(mangle "comprehension") = List{} + for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do + \(mangle "comprehension")[#\(mangle "comprehension")+1] = \($expr as lua) + end + return \(mangle "comprehension") + end)() + ") + +test: + assume "\n" == (newline) + +test: + assume (0xDEADBEEF as hex) == "0xDEADBEEF" + +externally ($num as hex) means: + if ($num < 0): + return ("-0x%X", formatted with (- $num)) + ..else: + return ("0x%X", formatted with $num) + +# Text literals +$escapes = { + .nl = "\n", .newline = "\n", .tab = "\t", .bell = "\a", .cr = "\r", ."carriage return" = "\r" + .backspace = "\b", ."form feed" = "\f", .formfeed = "\f", ."vertical tab" = "\v" +} + +for $name = $str in $escapes: + with [$lua = (Lua (quote $str))]: + $(COMPILE RULES).$name = (-> $lua) diff --git a/lib/file_hash.nom b/lib/file_hash.nom deleted file mode 100644 index 70446ca..0000000 --- a/lib/file_hash.nom +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines some actions for hashing files and looking up files by hash. - -use "lib/os" -use "lib/base64" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -lua> "local \$use_sha1, \$hashlib = pcall(require, 'openssl.digest')" -test: - assume (hash "hello world") == (hash "hello world") - if ((hash "hello world") == (hash "goodbye")): - fail (" - Hash collision: - (hash "hello world") = \(hash "hello world") - (hash "goodbye") = \(hash "goodbye") - ") - - assume - ( - hash (" - This is a really long string meant to stress test the hashing function and - ensure that it's not overflowing with long inputs. - ") - ) != "inf" - - if ((hash "\000") == (hash "\000\000\000\000\000")): - fail "Incorrect hashing of null strings" - - if $use_sha1: - assume ((hash "hello world") == "Kq5sNclPz7QV2+lfQIuc6R7oRu0=") - -if $use_sha1: - externally (hash $) means: - $hash = (=lua "\$hashlib.new('sha1'):final(\$)") - return (base64 $hash) -..else: - # TODO: remove warning? - say (" - \027[31;1mWARNING: OpenSSL module not found. Defaulting to a non-cryptographically secure \ - ..hash function.\027[0m - ") - - externally (hash $) means: - $bytes = ($, bytes) - $hash = ($bytes.1 << 7) - for $i in 2 to (size of $bytes): - $hash = ((1000003 * $hash) ~ $bytes.$i) - $hash = ($hash ~ (size of $bytes)) - return "\$hash" - -externally (file with hash $hash) means: - for $filename in (files for "."): - $contents = (read file $filename) - $file_hash = (hash $contents) - if ($file_hash == $hash): - return $filename - -(hash of file $filename) parses as (hash (read file $filename)) diff --git a/lib/file_hash/init.nom b/lib/file_hash/init.nom new file mode 100644 index 0000000..7b428cd --- /dev/null +++ b/lib/file_hash/init.nom @@ -0,0 +1,60 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines some actions for hashing files and looking up files by hash. + +use "filesystem" +use "base64" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +lua> "local \$use_sha1, \$hashlib = pcall(require, 'openssl.digest')" +test: + assume (hash "hello world") == (hash "hello world") + if ((hash "hello world") == (hash "goodbye")): + fail (" + Hash collision: + (hash "hello world") = \(hash "hello world") + (hash "goodbye") = \(hash "goodbye") + ") + + assume + ( + hash (" + This is a really long string meant to stress test the hashing function and + ensure that it's not overflowing with long inputs. + ") + ) != "inf" + + if ((hash "\000") == (hash "\000\000\000\000\000")): + fail "Incorrect hashing of null strings" + + if $use_sha1: + assume ((hash "hello world") == "Kq5sNclPz7QV2+lfQIuc6R7oRu0=") + +if $use_sha1: + externally (hash $) means: + $hash = (=lua "\$hashlib.new('sha1'):final(\$)") + return (base64 $hash) +..else: + # TODO: remove warning? + say (" + \027[31;1mWARNING: OpenSSL module not found. Defaulting to a non-cryptographically secure \ + ..hash function.\027[0m + ") + + externally (hash $) means: + $bytes = ($, bytes) + $hash = ($bytes.1 << 7) + for $i in 2 to (size of $bytes): + $hash = ((1000003 * $hash) ~ $bytes.$i) + $hash = ($hash ~ (size of $bytes)) + return "\$hash" + +externally (file with hash $hash) means: + for $filename in (files for "."): + $contents = (read file $filename) + $file_hash = (hash $contents) + if ($file_hash == $hash): + return $filename + +(hash of file $filename) parses as (hash (read file $filename)) diff --git a/lib/filesystem/init.nom b/lib/filesystem/init.nom new file mode 100644 index 0000000..a228bdc --- /dev/null +++ b/lib/filesystem/init.nom @@ -0,0 +1,36 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines some actions that interact with the filesystem. + +externally (files for $path) means: + $files = (=lua "Files.list(\$path)") + if $files: + $files = (List $files) + return $files + +external $(read file $filename) = $Files.read +externally [ + write to file $filename $text, to file $filename write $text + write $text to file $filename +] all mean: + unless ($filename != "stdin"): + fail "Cannot write to stdin" + + lua> (" + local file = io.open(\$filename, 'w') + file:write(\$text) + file:close() + ") + +externally (source lines of $tree) means: + $source = ($tree.source if ($tree is syntax tree) else $tree) + $file = (read file $source.filename) + return + [ + : for $ in ($file, line number at $source.start) to + $file, line number at $source.stop + ..: add ($file, line $) + ], joined with "\n" + +external $(spoof file $text) = $Files.spoof +external $(spoof file $filename = $text) = $Files.spoof diff --git a/lib/os.nom b/lib/os.nom deleted file mode 100644 index 87b3426..0000000 --- a/lib/os.nom +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines some actions that interact with the operating system and filesystem. - -externally (files for $path) means: - $files = (=lua "Files.list(\$path)") - if $files: - $files = (List $files) - return $files - -externally (=sh $cmd) means: - lua> (" - local result = io.popen(\$cmd) - local contents = result:read("*a") - result:close() - return contents - ") - -external $(sh> $) = $os.execute - -test: - read file "lib/os.nom" - -external $(read file $filename) = $Files.read -externally [ - write to file $filename $text, to file $filename write $text - write $text to file $filename -] all mean: - unless ($filename != "stdin"): - fail "Cannot write to stdin" - - lua> (" - local file = io.open(\$filename, 'w') - file:write(\$text) - file:close() - ") - -externally (source lines of $tree) means: - $source = ($tree.source if ($tree is syntax tree) else $tree) - $file = (read file $source.filename) - return - [ - : for $ in ($file, line number at $source.start) to - $file, line number at $source.stop - ..: add ($file, line $) - ], joined with "\n" - -external $(spoof file $text) = $Files.spoof -external $(spoof file $filename = $text) = $Files.spoof diff --git a/lib/progressbar/init.nom b/lib/progressbar/init.nom new file mode 100644 index 0000000..a7ff5aa --- /dev/null +++ b/lib/progressbar/init.nom @@ -0,0 +1,16 @@ +# A progress bar + +externally ($x / $w progress bar) means: + $x = ($x clamped between 0 and $w) + $bits = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"] + $middle = + "" if ($x == $w) else + $bits.(1 + (floor ((#$bits) * ($x mod 1)))) + + return (" + \027[32;40m\($bits, last, rep (floor $x))\$middle\ + ..\(" ", rep ($w - ((floor $x) + 1)))\027[0m + ") + +externally ($w wide $ progress bar) means + ($ * $w) / $w progress bar diff --git a/lib/shell/init.nom b/lib/shell/init.nom new file mode 100644 index 0000000..6a8970e --- /dev/null +++ b/lib/shell/init.nom @@ -0,0 +1,12 @@ +# + This file defines some actions for running shell commands. + +externally (=sh $cmd) means: + lua> (" + local result = io.popen(\$cmd) + local contents = result:read("*a") + result:close() + return contents + ") + +external $(sh> $) = $os.execute diff --git a/lib/things.nom b/lib/things.nom index 84682b3..95f175d 100644 --- a/lib/things.nom +++ b/lib/things.nom @@ -36,7 +36,7 @@ test: assume (($d, bark) == "Bark!") a (Corgi) is a thing: - $it [set up, gets pissed off] like a (Dog) + $it can [set up, gets pissed off] like a (Dog) ($it, as text) means "Dogloaf \{: for $k = $v in $it: add $k = $v}" ($its, sploot) means "sploooot" [($its, bark), ($its, woof)] all mean: diff --git a/lib/tools/find.nom b/lib/tools/find.nom new file mode 100755 index 0000000..aa4bc7b --- /dev/null +++ b/lib/tools/find.nom @@ -0,0 +1,95 @@ +#!/usr/bin/env nomsu -V6.14 +# + This is a tool to find syntax trees matching a pattern. "*" is a wildcard + that will match any subtree, and "**" is a wildcard that will match any + 0 or more subtrees. "**" is greedy, so extra arguments after it will + not match. + + nomsu -t find [flags] "* squared" file1 file2... + + Flags: + -l List only the names of the files with matches + --wildcard= Specify a custom wildcard (in case you need to + match an action with a "*" in the name) + + Output: + :: + + +use "filesystem" +use "consolecolor" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +command line program with $args: + $wildcard = ($args.wildcard or "%*") + $pattern = $args.extras.1 + if (any of [not $pattern, $pattern == "*", $pattern == "**"]): + usage (" + nomsu -t find [-l] [--wildcard=] , where is valid Nomsu code + ") + $pattern = ($pattern, with "\$wildcard\$wildcard" -> "$multi_wildcard") + $pattern = ($pattern, with $wildcard -> "$wildcard") + $pattern_tree = ($pattern parsed) + ($tree matches $patt) means: + when: + (not ($tree is syntax tree)): return (no) + (($patt.type == "Var") and ($patt.1 == "wildcard")): return (yes) + ($tree.type != $patt.type): return (no) + ($tree.type == "Action"): + if (($tree, get stub) != ($patt, get stub)): return (no) + + for $ in 1 to (#$patt): + if ($patt.$ is syntax tree): + if ($patt.$ == \$multi_wildcard): return (yes) + unless ($tree.$ matches $patt.$): return (no) + ..else: + unless ($tree.$ == $patt.$): return (no) + + if ((#$tree) != (#$patt)): return (no) + return (yes) + $filenames = ($args.extras, from 2 to -1) + if ((#$filenames) == 0): + say (" + Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find - + ") + $filenames = ["stdin"] + + for $filename in $filenames: + $file = (read file $filename) + unless $file: + fail "File does not exist: \$filename" + $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) + try: + $tree = ($code parsed) + ..if it fails $msg: + say + red (" + \$filename failed to parse: + \$msg + ") + $tree = (nil) + + unless $tree: + do next $filename + + $results = [] + for $t in recursive $tree: + if ($t matches $pattern_tree): + $line_num = ($file, line number at $t.source.start) + $results, add { + .line = $line_num, .text = "\(blue "\$filename:\$line_num:")\n\(source lines of $t)" + } + + for $sub in $t: + if ($sub is syntax tree): + recurse $t on $sub + + if $args.l: + if ((#$results) > 0): + say $filename + ..else: + sort $results by $ -> $.line + for $ in $results: + say $.text diff --git a/lib/tools/format.nom b/lib/tools/format.nom new file mode 100755 index 0000000..e60d12e --- /dev/null +++ b/lib/tools/format.nom @@ -0,0 +1,45 @@ +#!/usr/bin/env nomsu -V6.14 +# + Auto-format Nomsu code. Usage: + nomsu -t format [-i] file1 file2... + + If the "-i" flag is used, the file will be edited in-place. + If the "-q" flag is used and an error occurs, the original file will be printed. + If no files are passed in, this will read from stdin. + +use "filesystem" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +command line program with $args: + $filenames = $args.extras + if ((#$filenames) == 0): + say (" + Warning: reading from stdin (ctrl-d to abort). To avoid this message, use nomsu -t format - + ") + $filenames = ["stdin"] + + for $filename in $filenames: + $file = (read file $filename) + unless $file: + fail "File does not exist: \$filename" + $leading_indent = ($file, matching "\n*([ ]*)") + $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) + try: + $tree = ($code parsed) + ..if it fails $msg: + if $args.q: + $formatted = $file + ..else: + say $msg + + if ($tree and (not $formatted)): + $formatted = + "\$leading_indent\($tree as nomsu, text, with "\n" -> "\n\$leading_indent")" + + if $formatted: + if $args.i: + write $formatted to file $filename + ..else: + say $formatted inline diff --git a/lib/tools/parse.nom b/lib/tools/parse.nom new file mode 100755 index 0000000..63cc247 --- /dev/null +++ b/lib/tools/parse.nom @@ -0,0 +1,47 @@ +#!/usr/bin/env nomsu -V6.14 +# + Tool to print out a parse tree of files in an easy-to-read format. Usage: + nomsu tools/parse.nom file1 file2 directory1 ... + +use "filesystem" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +externally (print tree $t at indent $indent) means: + if $t.type is: + "Action": + say "\($indent)Action (\($t.stub)):" + for $arg in $t: + if ($arg is syntax tree): + print tree $arg at indent "\$indent " + + "MethodCall": + say "\($indent)MethodCall on:" + print tree $t.1 at indent "\$indent " + print tree $t.2 at indent "\$indent " + + "Number": + say "\$indent\($t.1)" + + "Var": + say "\($indent)$\($t.1)" + + else: + say "\$indent\($t.type):" + for $arg in $t: + when: + ($arg is syntax tree): + print tree $arg at indent "\$indent " + + else: + say "\$indent \(quote $arg)" + +command line program with $args: + for $filename in $args.extras: + $file = (read file $filename) + unless $file: + fail "File does not exist: \$filename" + $nomsu = (NomsuCode from (Source $filename 1 (size of $file)) $file) + $tree = ($nomsu parsed) + print tree $tree at indent "" diff --git a/lib/tools/repl.nom b/lib/tools/repl.nom new file mode 100755 index 0000000..2c0d3df --- /dev/null +++ b/lib/tools/repl.nom @@ -0,0 +1,86 @@ +#!/usr/bin/env nomsu -V6.14 +# + This file defines a Read-Evaluate-Print-Loop (REPL) for Nomsu + +use "consolecolor" +use "filesystem" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +externally (help) means: + say (" + This is the Nomsu v\(Nomsu version) interactive console. + You can type in Nomsu code here and hit 'enter' twice to run it. + To exit, type 'exit' or 'quit' and hit enter twice. + ") + +command line program with $args: + say (" + + \(bright)\(underscore)Welcome to the Nomsu v\(Nomsu version) interactive console!\ + ..\(reset color) + press 'enter' twice to run a command + + ") + + repeat: + say (bright (yellow ">> ")) inline + $buff = [] + repeat: + say (bright) inline + $line = ($io.read "*L") + say (reset color) inline + if (($line == "\n") or (not $line)): + if ((size of $buff) > 0): + # clear the line + say "\027[1A\027[2K" inline + go to (run buffer) + $buff, add ($line, with "\t" -> " ") + say (dim (yellow ".. ")) inline + + --- (run buffer) --- + + if ((size of $buff) == 0): stop + $buff = ($buff, joined) + spoof file $buff + try: + $tree = ($buff parsed) + ..if it fails with $err: + say $err + do next + + unless $tree: + do next + + for $chunk in $tree: + try: + $lua = ($chunk as lua) + ..if it fails with $err: say $err + + unless $lua: + do next + + # TODO: this is a bit hacky, it just defaults variables to global + so that stuff mostly works across multiple lines. It would be + nicer if local variables actually worked. + $lua, remove free vars + try: + $ret = (run $lua) + ..if it fails with $err: say $err + ..if it succeeds: + if (type of $ret) is: + "nil": + do nothing + + "boolean": + say "= \("yes" if $ret else "no")" + + "table": + if $ret.as_nomsu: + say "= \($ret, as nomsu)" + ..else: + say "= \$ret" + + else: + say "= \$ret" diff --git a/lib/tools/replace.nom b/lib/tools/replace.nom new file mode 100755 index 0000000..fdb8e38 --- /dev/null +++ b/lib/tools/replace.nom @@ -0,0 +1,147 @@ +#!/usr/bin/env nomsu -V6.14 +# + This is a tool to replace syntax trees with something new. + + Usage: + nomsu -t replace [-i] [-f] [-q] [--literal="$v1 $v2..."] file1 file2... + + Example: + nomsu -t replace "($1 and $2) and $3" "all of [$1, $2, $3]" my_file.nom + + If the "-i" flag is used, the file(s) will be edited in-place. + When editing in-place, if the "-f" flag is not used, each change will be + run past the user first. + If the "-q" flag is used and a file fails to parse, the original file + contents will be output. + If no files are passed in, this will read from stdin. + +use "filesystem" +use "consolecolor" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +command line program with $args: + if ((#$args.extras) < 2): + fail (" + Usage: nomsu -t replace [--literal="$v1 $v2..."] file1 file2... + ") + $pattern = $args.extras.1 + $replacement = $args.extras.2 + $pattern_tree = ($pattern parsed) + $replacement_tree = ($replacement parsed) + $literal_vars = {} + if $args.literal: + for $var in ($args.literal, all matches of "$([^ ]*)"): + $literal_vars.$var = (yes) + + if (($pattern_tree.type == "Var") and (not $literal_vars.($pattern_tree.1))): + fail "Pattern matches every part of the file." + + $pattern_vars = { + : for $ in recursive $pattern_tree: + if (($.type == "Var") and (not $literal_vars.($.1))): add $.1 + for $child in $: + if ($child is a "Syntax Tree"): + recurse $ on $child + } + + # TODO: support wildcards and unpacking + e.g. nomsu -t replace "test(: $test; *$more_tests)" "*$more_tests; *$test" + ($tree matches $patt with $substitution_values) means: + # TODO: optimize + $substitution_values = {: for $k = $v in $substitution_values: add $k = $v} + when: + (not ($tree is syntax tree)): return (no) + (($patt.type == "Var") and $pattern_vars.($patt.1)): + if $substitution_values.($patt.1): + if ($tree == $substitution_values.($patt.1)): + return $substitution_values + ..else: + return (nil) + ..else: + $substitution_values.($patt.1) = $tree + return $substitution_values + ($tree.type != $patt.type): return (nil) + ($tree.type == "Action"): + if (($tree, get stub) != ($patt, get stub)): return (nil) + + for $ in 1 to (#$patt): + if ($patt.$ is syntax tree): + $new_values = ($tree.$ matches $patt.$ with $substitution_values) + unless $new_values: + return (nil) + + for $k = $v in $new_values: + $substitution_values.$k = $v + ..else: + unless ($tree.$ == $patt.$): return (nil) + + if ((#$tree) != (#$patt)): return (nil) + return $substitution_values + $filenames = ($args.extras, from 3 to -1) + if ((#$filenames) == 0): + say (" + Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find - + ") + $filenames = ["stdin"] + + for $filename in $filenames: + $file = (read file $filename) + unless $file: + fail "File does not exist: \$filename" + $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) + try: + $tree = ($code parsed) + ..if it fails $msg: + if $args.q: + unless $args.i: say $code + ..else: + say $msg + + unless $tree: + do next $filename + + $replaced = {} + $matched = {} + $user_answers = {} + ($tree with replacements) means + $tree, map + for $t: + $values = ($t matches $pattern_tree with {}) + if $values: + $matched.$t = (yes) + for $k = $v in $values: + $values.$k = ($v with replacements) + $ret = ($replacement_tree with vars $values) + if ($args.i and (not $args.f)): + if ($user_answers.$t == (nil)): + if ((#$user_answers) > 0): say "" + $user_answers.$t = "n" + say "\(bright)Should this:" + say (" + \(bright)\(yellow)\("\(($t with replacements) as nomsu)", with "\n" -> "\n ")\ + ..\(reset color) + ") + say "\(bright)..be replaced with:" + + say (" + \(bright)\(blue)\("\($ret as nomsu)", with "\n" -> "\n ")\(reset color) + ") + + $user_answers.$t = (ask "\(bright)..? [Y/n]\(reset color) ") + + if ($user_answers.$t == "n"): return (nil) + $replaced.$t = (yes) + return $ret + $tree2 = ($tree with replacements) + if $args.i: + if ((#$user_answers) > 0): say "" + say (" + \(#$replaced)/\(#$matched) replacement\("" if ((#$replaced) == 1) else "s") in \$filename + ") + + if ((#$replaced) > 0): + write "\($tree2 as nomsu)" to file $filename + ..else: + say ($tree2 as nomsu) diff --git a/lib/tools/test.nom b/lib/tools/test.nom new file mode 100755 index 0000000..4663bd4 --- /dev/null +++ b/lib/tools/test.nom @@ -0,0 +1,65 @@ +#!/usr/bin/env nomsu -V6.14 +# + Tool to run all tests in a file (i.e. the code block inside a call to 'test $'). Usage: + nomsu tools/test.nom file1 file2 directory1 ... + +use "filesystem" +use "consolecolor" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +command line program with $args: + for $filename in $args.extras at $i: + $file = (read file $filename) + unless $file: + fail "Couldn't find \$filename" + + $(test environment) = (new environment) + $(test environment), export $filename + $version = + $file, matching (" + #![^ + ]* nomsu %-V[ ]*([^ + ]*) + ") + $file_tests = [] + for $src = $test in $(test environment).TESTS: + if $version: + $test = (" + #!/usr/bin/env nomsu -V\$version + \$test + ") + $file_tests, add {.test = $test, .source = $src} + + unless ($file_tests is empty): + sort $file_tests by $ -> $.source + say "[ .. ] \$filename" inline + $io.flush() + + if $args.v: say "" + + $failures = [] + for $ in $file_tests: + if $args.v: + say " \(yellow ($.test, with "\n" -> "\n "))" + try: + $(test environment), run $.test + ..if it fails with $msg: + $src = ($Source, from string $.source) + $l1 = ($file, line number at $src.start) + $l2 = ($file, line number at $src.stop) + $failures, add "\(yellow "\($src.filename):\($l1)-\$l2:")\n\(bright (red ($msg, indented)))" + + if ($failures is empty): + if $args.v: + say (green "PASS") + ..else: + say "\r[\(green "PASS")" + ..else: + if $args.v: + say (red (bright "FAIL")) + ..else: + say "\r[\(red (bright "FAIL"))" + say "\($failures, joined with "\n", indented)" + diff --git a/lib/tools/upgrade.nom b/lib/tools/upgrade.nom new file mode 100755 index 0000000..1ef91b9 --- /dev/null +++ b/lib/tools/upgrade.nom @@ -0,0 +1,43 @@ +#!/usr/bin/env nomsu -V6.14 +# + Tool to automatically update code from old versions of Nomsu. Usage: + nomsu tools/upgrade.nom [-i] file1 file2 directory1 ... + If "-i" is the first argument, upgrades will be performed in-place. Otherwise, the + upgraded code will be printed. + +use "compatibility" +use "filesystem" +use "consolecolor" +use "commandline" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +command line program with $args: + $inplace = ($args.i or $args.inplace) + $start_version = $args."upgrade-from" + $version = ($args."upgrade-to" or (Nomsu version)) + $test = ($args.t or $args.test) + for $filename in $args.extras: + $file = (read file $filename) + unless $file: + fail "File does not exist: \$filename" + $leading_indent = ($file, matching "\n*([ ]*)") + $code = (NomsuCode from (Source $filename 1 (size of $file)) $file) + $tree = ($code parsed $start_version) + $uptree = + $tree upgraded from ($start_version or ($tree.version or (Nomsu version))) to + $version + $text = "\$leading_indent\($uptree as nomsu, text, with "\n" -> "\n\$leading_indent")" + when: + $inplace: + say "Upgraded \$filename" + write $text to file $filename + + $test: + if ($uptree == $tree): + say (dim "\$filename will not be changed") + ..else: + say (bright "\$filename will be changed") + + else: + say $text inline diff --git a/nomsu.lua b/nomsu.lua index 88bf986..6f523c8 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -48,16 +48,16 @@ end local sep = "\3" local parser = re.compile([[ args <- {| (flag %sep)* {:files: {| - ( ("-m" %sep)? (!("--" %sep) file)* ("--" %sep) - / file + ( ("-m" %sep)? (file %sep)+ "--" %sep + / file %sep / {~ '' %sep? -> 'nomsu://tools/repl.nom' ~}) |} :} {:nomsu_args: {| (nomsu_flag %sep)* {:extras: {| ({[^%sep]+} %sep)* |} :} |} :} - |} !. + |} ({.+}?) file <- - ( "-e" %sep ({[^%sep]+} -> spoof) %sep - / "-t" %sep {~ {[^%sep]+} -> "nomsu://tools/%1.nom" ~} %sep - / {[^%sep]+} %sep) + ( "-e" %sep ({[^%sep]+} -> spoof) + / "-t" %sep {~ {[^%sep]+} -> "nomsu://tools/%1.nom" ~} + / !"--" {[^%sep]+}) flag <- longflag / shortflag / "-" shortboolflag+ longflag <- @@ -84,8 +84,11 @@ local parser = re.compile([[ args <- {| (flag %sep)* spoof = Files.spoof }) local arg_string = table.concat(arg, sep) .. sep -local args = parser:match(arg_string) -if not args or args.help then +local args, err = parser:match(arg_string) +if not args or err or args.help then + if err then + print("Didn't understand: \x1b[31;1m" .. tostring(err) .. "\x1b[0m") + end print(usage) os.exit(EXIT_FAILURE) end @@ -98,28 +101,35 @@ end nomsu_args.extras = List(args.nomsu_args.extras or { }) local optimization = tonumber(args.optimization or 1) local nomsupath = { } -if NOMSU_VERSION and NOMSU_PREFIX then - if optimization > 0 then - table.insert(nomsupath, tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/?.lua") - table.insert(nomsupath, tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/?/init.lua") - end - table.insert(nomsupath, tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/?.nom") - table.insert(nomsupath, tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/?/init.nom") +local suffixes +if optimization > 0 then + suffixes = { + "?.lua", + "?/init.lua", + "?.nom", + "?/init.nom" + } +else + suffixes = { + "?.nom", + "?/init.nom" + } end -if NOMSU_PACKAGEPATH then - if optimization > 0 then - table.insert(nomsupath, tostring(NOMSU_PACKAGEPATH) .. "/nomsu/?.lua") - table.insert(nomsupath, tostring(NOMSU_PACKAGEPATH) .. "/nomsu/?/init.lua") +local add_path +add_path = function(p) + for _index_0 = 1, #suffixes do + local s = suffixes[_index_0] + table.insert(nomsupath, p .. "/" .. s) end - table.insert(nomsupath, tostring(NOMSU_PACKAGEPATH) .. "/nomsu/?.nom") - table.insert(nomsupath, tostring(NOMSU_PACKAGEPATH) .. "/nomsu/?/init.nom") end -if optimization > 0 then - table.insert(nomsupath, "./?.lua") - table.insert(nomsupath, "./?/init.lua") +if NOMSU_VERSION and NOMSU_PREFIX then + add_path(tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/lib") +else + add_path("./lib") end -table.insert(nomsupath, "./?.nom") -table.insert(nomsupath, "./?/init.nom") +local NOMSU_PACKAGEPATH = NOMSU_PACKAGEPATH or "/opt" +add_path(NOMSU_PACKAGEPATH) +add_path(".") package.nomsupath = table.concat(nomsupath, ";") local nomsu_environment = require('nomsu_environment') nomsu_environment.COMMAND_LINE_ARGS = nomsu_args @@ -143,7 +153,8 @@ run = function() do local nomsu_name = f:match("^nomsu://(.*)%.nom") if nomsu_name then - local path, err = package.searchpath(nomsu_name, package.nomsupath, "/") + local path + path, err = package.searchpath(nomsu_name, package.nomsupath, "/") if not path then error(err) end @@ -171,20 +182,22 @@ run = function() local code = Files.read(filename) local source = Source(filename, 1, #code) code = NomsuCode:from(source, code) - local tree = nomsu_environment._1_parsed(code) + local env = nomsu_environment.new_environment() + env.MODULE_NAME = filename + local tree = env._1_parsed(code) if not (tree.type == 'FileChunks') then tree = { tree } end for chunk_no, chunk in ipairs(tree) do - local lua = nomsu_environment:compile(chunk) + local lua = env:compile(chunk) lua:declare_locals() lua:prepend((chunk_no > 1) and '\n' or '', "-- File " .. tostring(filename) .. " chunk #" .. tostring(chunk_no) .. "\n") if args.verbose then print(lua:text()) end - nomsu_environment:run(chunk) + env:run(chunk) output:write(lua:text(), "\n") end print(("Compiled %-25s -> %s"):format(filename, filename:gsub("%.nom$", ".lua"))) @@ -193,25 +206,31 @@ run = function() local code = Files.read(filename) local source = Source(filename, 1, #code) code = NomsuCode:from(source, code) - local tree = nomsu_environment._1_parsed(code) + local env = nomsu_environment.new_environment() + env.MODULE_NAME = filename + env.WAS_RUN_DIRECTLY = true + local tree = env._1_parsed(code) if not (tree.type == 'FileChunks') then tree = { tree } end for chunk_no, chunk in ipairs(tree) do - local lua = nomsu_environment:compile(chunk) + local lua = env:compile(chunk) lua:declare_locals() lua:prepend((chunk_no > 1) and '\n' or '', "-- File " .. tostring(filename) .. " chunk #" .. tostring(chunk_no) .. "\n") print(lua:text()) - nomsu_environment:run(lua) + env:run(lua) end else local f = Files.read(filename) if filename:match("%.lua$") then f = LuaCode:from(Source(filename, 1, #f), f) end - nomsu_environment:run(f) + local env = nomsu_environment.new_environment() + env.MODULE_NAME = filename + env.WAS_RUN_DIRECTLY = true + env:run(f) end end end diff --git a/nomsu.moon b/nomsu.moon index b837170..472bed5 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -48,16 +48,16 @@ sep = "\3" parser = re.compile([[ args <- {| (flag %sep)* {:files: {| - ( ("-m" %sep)? (!("--" %sep) file)* ("--" %sep) - / file + ( ("-m" %sep)? (file %sep)+ "--" %sep + / file %sep / {~ '' %sep? -> 'nomsu://tools/repl.nom' ~}) |} :} {:nomsu_args: {| (nomsu_flag %sep)* {:extras: {| ({[^%sep]+} %sep)* |} :} |} :} - |} !. + |} ({.+}?) file <- - ( "-e" %sep ({[^%sep]+} -> spoof) %sep - / "-t" %sep {~ {[^%sep]+} -> "nomsu://tools/%1.nom" ~} %sep - / {[^%sep]+} %sep) + ( "-e" %sep ({[^%sep]+} -> spoof) + / "-t" %sep {~ {[^%sep]+} -> "nomsu://tools/%1.nom" ~} + / !"--" {[^%sep]+}) flag <- longflag / shortflag / "-" shortboolflag+ longflag <- @@ -82,8 +82,10 @@ parser = re.compile([[ spoof: Files.spoof }) arg_string = table.concat(arg, sep)..sep -args = parser\match(arg_string) -if not args or args.help +args, err = parser\match(arg_string) +if not args or err or args.help + if err + print("Didn't understand: \x1b[31;1m#{err}\x1b[0m") print usage os.exit(EXIT_FAILURE) nomsu_args = Dict{} @@ -93,25 +95,18 @@ nomsu_args.extras = List(args.nomsu_args.extras or {}) optimization = tonumber(args.optimization or 1) nomsupath = {} +suffixes = if optimization > 0 + {"?.lua", "?/init.lua", "?.nom", "?/init.nom"} +else {"?.nom", "?/init.nom"} +add_path = (p)-> + for s in *suffixes do table.insert(nomsupath, p.."/"..s) if NOMSU_VERSION and NOMSU_PREFIX - if optimization > 0 - table.insert nomsupath, "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/?.lua" - table.insert nomsupath, "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/?/init.lua" - table.insert nomsupath, "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/?.nom" - table.insert nomsupath, "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/?/init.nom" - -if NOMSU_PACKAGEPATH - if optimization > 0 - table.insert nomsupath, "#{NOMSU_PACKAGEPATH}/nomsu/?.lua" - table.insert nomsupath, "#{NOMSU_PACKAGEPATH}/nomsu/?/init.lua" - table.insert nomsupath, "#{NOMSU_PACKAGEPATH}/nomsu/?.nom" - table.insert nomsupath, "#{NOMSU_PACKAGEPATH}/nomsu/?/init.nom" - -if optimization > 0 - table.insert nomsupath, "./?.lua" - table.insert nomsupath, "./?/init.lua" -table.insert nomsupath, "./?.nom" -table.insert nomsupath, "./?/init.nom" + add_path "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/lib" +else + add_path "./lib" +NOMSU_PACKAGEPATH or= "/opt" +add_path NOMSU_PACKAGEPATH +add_path "." package.nomsupath = table.concat(nomsupath, ";") nomsu_environment = require('nomsu_environment') @@ -152,14 +147,16 @@ run = -> code = Files.read(filename) source = Source(filename, 1, #code) code = NomsuCode\from(source, code) - tree = nomsu_environment._1_parsed(code) + env = nomsu_environment.new_environment! + env.MODULE_NAME = filename + tree = env._1_parsed(code) tree = {tree} unless tree.type == 'FileChunks' for chunk_no, chunk in ipairs tree - lua = nomsu_environment\compile(chunk) + lua = env\compile(chunk) lua\declare_locals! lua\prepend((chunk_no > 1) and '\n' or '', "-- File #{filename} chunk ##{chunk_no}\n") if args.verbose then print(lua\text!) - nomsu_environment\run(chunk) + env\run(chunk) output\write(lua\text!, "\n") print ("Compiled %-25s -> %s")\format(filename, filename\gsub("%.nom$", ".lua")) output\close! @@ -167,20 +164,26 @@ run = -> code = Files.read(filename) source = Source(filename, 1, #code) code = NomsuCode\from(source, code) - tree = nomsu_environment._1_parsed(code) + env = nomsu_environment.new_environment! + env.MODULE_NAME = filename + env.WAS_RUN_DIRECTLY = true + tree = env._1_parsed(code) tree = {tree} unless tree.type == 'FileChunks' for chunk_no, chunk in ipairs tree - lua = nomsu_environment\compile(chunk) + lua = env\compile(chunk) lua\declare_locals! lua\prepend((chunk_no > 1) and '\n' or '', "-- File #{filename} chunk ##{chunk_no}\n") print(lua\text!) - nomsu_environment\run(lua) + env\run(lua) else -- Just run the file f = Files.read(filename) if filename\match("%.lua$") f = LuaCode\from(Source(filename, 1, #f), f) - nomsu_environment\run(f) + env = nomsu_environment.new_environment! + env.MODULE_NAME = filename + env.WAS_RUN_DIRECTLY = true + env\run(f) --nomsu_environment.run_file_1_in(filename, nomsu_environment, 0) debugger = if args.debugger == "nil" then {} diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index 9c8c9ac..fee94f3 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -97,6 +97,8 @@ compile = function(self, tree) if ret ~= tree then return self:compile(ret) end + elseif tree.stub == "1 if 2 else" then + require('ldt').breakpoint() end local lua = LuaCode:from(tree.source) lua:add((stub):as_lua_id(), "(") diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index f3530e9..fd7403f 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -68,6 +68,8 @@ compile = (tree)=> return ret if ret != tree return @compile(ret) + elseif tree.stub == "1 if 2 else" + require('ldt').breakpoint! lua = LuaCode\from(tree.source) lua\add((stub)\as_lua_id!,"(") diff --git a/nomsu_environment.lua b/nomsu_environment.lua index 9181d92..94e68e0 100644 --- a/nomsu_environment.lua +++ b/nomsu_environment.lua @@ -64,9 +64,20 @@ do compile, compile_error = _obj_0.compile, _obj_0.compile_error end local _currently_running_files = List({ }) -local nomsu_environment local _module_imports = { } -nomsu_environment = setmetatable({ +local _importer_mt = { + __index = function(self, k) + return _module_imports[self][k] + end +} +local Importer +Importer = function(t, imports) + _module_imports[t] = imports or { } + t._IMPORTS = _module_imports[t] + return setmetatable(t, _importer_mt) +end +local nomsu_environment +nomsu_environment = Importer({ NOMSU_COMPILER_VERSION = 13, NOMSU_SYNTAX_VERSION = max_parser_version, next = next, @@ -230,7 +241,6 @@ nomsu_environment = setmetatable({ end local mod = self:new_environment() mod.MODULE_NAME = package_name - mod.TESTS = Dict({ }) local code = Files.read(path) if path:match("%.lua$") then code = LuaCode:from(Source(path, 1, #code), code) @@ -250,19 +260,41 @@ nomsu_environment = setmetatable({ for k, v in pairs(mod) do imports[k] = v end + local cr_imports = assert(_module_imports[self.COMPILE_RULES]) + for k, v in pairs(mod.COMPILE_RULES) do + cr_imports[k] = v + end return mod end, export = function(self, package_name) local mod = self:load_module(package_name) local imports = assert(_module_imports[self]) for k, v in pairs(_module_imports[mod]) do - imports[k] = v + if rawget(imports, k) == nil then + imports[k] = v + end end for k, v in pairs(mod) do - if k ~= "_G" and k ~= "_ENV" then + if rawget(self, k) == nil then self[k] = v end end + local cr_imports = assert(_module_imports[self.COMPILE_RULES]) + for k, v in pairs(_module_imports[mod.COMPILE_RULES]) do + if rawget(cr_imports, k) == nil then + cr_imports[k] = v + end + end + for k, v in pairs(mod.COMPILE_RULES) do + if rawget(self.COMPILE_RULES, k) == nil then + self.COMPILE_RULES[k] = v + end + end + for k, v in pairs(mod.TESTS) do + if rawget(self.TESTS, k) == nil then + self.TESTS[k] = v + end + end return mod end, run = function(self, to_run) @@ -303,7 +335,7 @@ nomsu_environment = setmetatable({ do local _accum_0 = { } local _len_0 = 1 - for i, line in ipairs(Files.get_lines(lua_string)) do + for i, line in ipairs(lua_string:lines()) do _accum_0[_len_0] = ("%3d|%s"):format(i, line) _len_0 = _len_0 + 1 end @@ -351,27 +383,29 @@ nomsu_environment = setmetatable({ end end, new_environment = function() - local env = { } - do + local env = Importer({ }, (function() local _tbl_0 = { } for k, v in pairs(nomsu_environment) do _tbl_0[k] = v end - _module_imports[env] = _tbl_0 - end + return _tbl_0 + end)()) env._ENV = env env._G = env - setmetatable(env, getmetatable(nomsu_environment)) + env.TESTS = Dict({ }) + env.COMPILE_RULES = Importer({ }, (function() + local _tbl_0 = { } + for k, v in pairs(nomsu_environment.COMPILE_RULES) do + _tbl_0[k] = v + end + return _tbl_0 + end)()) return env end -}, { - __index = function(self, k) - return _module_imports[self][k] - end }) nomsu_environment._ENV = nomsu_environment nomsu_environment._G = nomsu_environment -nomsu_environment.COMPILE_RULES = require('bootstrap') -_module_imports[nomsu_environment] = { } +nomsu_environment.COMPILE_RULES = Importer(require('bootstrap')) +nomsu_environment.MODULE_NAME = "nomsu" SOURCE_MAP = nomsu_environment.SOURCE_MAP return nomsu_environment diff --git a/nomsu_environment.moon b/nomsu_environment.moon index f82bb4e..6049555 100644 --- a/nomsu_environment.moon +++ b/nomsu_environment.moon @@ -34,9 +34,15 @@ for version=1,999 {:tree_to_nomsu, :tree_to_inline_nomsu} = require "nomsu_decompiler" {:compile, :compile_error} = require('nomsu_compiler') _currently_running_files = List{} -- Used to check for circular imports in run_file_1_in -local nomsu_environment _module_imports = {} -nomsu_environment = setmetatable({ +_importer_mt = {__index: (k)=> _module_imports[@][k]} +Importer = (t, imports)-> + _module_imports[t] = imports or {} + t._IMPORTS = _module_imports[t] + return setmetatable(t, _importer_mt) + +local nomsu_environment +nomsu_environment = Importer{ NOMSU_COMPILER_VERSION: 13, NOMSU_SYNTAX_VERSION: max_parser_version -- Lua stuff: :next, unpack: unpack or table.unpack, :setmetatable, :rawequal, :getmetatable, :pcall, @@ -118,7 +124,6 @@ nomsu_environment = setmetatable({ error("Circular import detected:\n "..circle\joined_with("\n..imports ")) mod = @new_environment! mod.MODULE_NAME = package_name - mod.TESTS = Dict{} code = Files.read(path) if path\match("%.lua$") code = LuaCode\from(Source(path, 1, #code), code) @@ -136,16 +141,31 @@ nomsu_environment = setmetatable({ imports = assert _module_imports[@] for k,v in pairs(mod) imports[k] = v + cr_imports = assert _module_imports[@COMPILE_RULES] + for k,v in pairs(mod.COMPILE_RULES) + cr_imports[k] = v return mod export: (package_name)=> mod = @load_module(package_name) imports = assert _module_imports[@] for k,v in pairs(_module_imports[mod]) - imports[k] = v + if rawget(imports, k) == nil + imports[k] = v for k,v in pairs(mod) - if k != "_G" and k != "_ENV" + if rawget(@, k) == nil + --if k != "_G" and k != "_ENV" and k != "COMPILE_RULES" and k != "MODULE_NAME" @[k] = v + cr_imports = assert _module_imports[@COMPILE_RULES] + for k,v in pairs(_module_imports[mod.COMPILE_RULES]) + if rawget(cr_imports, k) == nil + cr_imports[k] = v + for k,v in pairs(mod.COMPILE_RULES) + if rawget(@COMPILE_RULES, k) == nil + @COMPILE_RULES[k] = v + for k,v in pairs(mod.TESTS) + if rawget(@TESTS, k) == nil + @TESTS[k] = v return mod run: (to_run)=> @@ -179,7 +199,7 @@ nomsu_environment = setmetatable({ -- If you replace tostring(source) with "nil", source mapping won't happen run_lua_fn, err = load(lua_string, tostring(source), "t", @) if not run_lua_fn - lines =[("%3d|%s")\format(i,line) for i, line in ipairs Files.get_lines(lua_string)] + lines =[("%3d|%s")\format(i,line) for i, line in ipairs lua_string\lines!] line_numbered_lua = table.concat(lines, "\n") error("Failed to compile generated code:\n\027[1;34m#{line_numbered_lua}\027[0m\n\n#{err}", 0) source_key = tostring(source) @@ -210,19 +230,18 @@ nomsu_environment = setmetatable({ error("Attempt to run unknown thing: "..tostring(to_run)) new_environment: -> - env = {} - _module_imports[env] = {k,v for k,v in pairs(nomsu_environment)} + env = Importer({}, {k,v for k,v in pairs(nomsu_environment)}) env._ENV = env env._G = env - setmetatable(env, getmetatable(nomsu_environment)) + env.TESTS = Dict{} + env.COMPILE_RULES = Importer({}, {k,v for k,v in pairs(nomsu_environment.COMPILE_RULES)}) return env -}, { - __index: (k)=> _module_imports[@][k] -}) +} + nomsu_environment._ENV = nomsu_environment nomsu_environment._G = nomsu_environment -nomsu_environment.COMPILE_RULES = require('bootstrap') -_module_imports[nomsu_environment] = {} +nomsu_environment.COMPILE_RULES = Importer(require('bootstrap')) +nomsu_environment.MODULE_NAME = "nomsu" -- Hacky use of globals: export SOURCE_MAP diff --git a/string2.lua b/string2.lua index 09cb5ea..429ab7f 100644 --- a/string2.lua +++ b/string2.lua @@ -130,6 +130,12 @@ local string2 = { end return table.concat(lines, "\n") end, + indented = function(self, indent) + if indent == nil then + indent = " " + end + return indent .. (gsub(self, "\n", "\n" .. indent)) + end, as_lua = function(self) local escaped = gsub(self, "\\", "\\\\") escaped = gsub(escaped, "\n", "\\n") diff --git a/string2.moon b/string2.moon index e6db628..de2980d 100644 --- a/string2.moon +++ b/string2.moon @@ -51,6 +51,9 @@ string2 = { lines[#lines+1] = line return table.concat(lines, "\n") + indented: (indent=" ")=> + indent..(gsub(@, "\n", "\n"..indent)) + as_lua: => escaped = gsub(@, "\\", "\\\\") escaped = gsub(escaped, "\n", "\\n") diff --git a/tools/find.nom b/tools/find.nom deleted file mode 100755 index f9a63b9..0000000 --- a/tools/find.nom +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This is a tool to find syntax trees matching a pattern. "*" is a wildcard - that will match any subtree, and "**" is a wildcard that will match any - 0 or more subtrees. "**" is greedy, so extra arguments after it will - not match. - - nomsu -t find [flags] "* squared" file1 file2... - - Flags: - -l List only the names of the files with matches - --wildcard= Specify a custom wildcard (in case you need to - match an action with a "*" in the name) - - Output: - :: - - -use "lib/os" -use "lib/consolecolor" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -$wildcard = ($(COMMAND LINE ARGS).wildcard or "%*") -$pattern = $(COMMAND LINE ARGS).extras.1 -if (any of [not $pattern, $pattern == "*", $pattern == "**"]): - fail (" - Usage: nomsu -t find [-l] [--wildcard=] , where is valid Nomsu code - ") -$pattern = ($pattern, with "\$wildcard\$wildcard" -> "$multi_wildcard") -$pattern = ($pattern, with $wildcard -> "$wildcard") -$pattern_tree = ($pattern parsed) -($tree matches $patt) means: - when: - (not ($tree is syntax tree)): return (no) - (($patt.type == "Var") and ($patt.1 == "wildcard")): return (yes) - ($tree.type != $patt.type): return (no) - ($tree.type == "Action"): - if (($tree, get stub) != ($patt, get stub)): return (no) - - for $ in 1 to (#$patt): - if ($patt.$ is syntax tree): - if ($patt.$ == \$multi_wildcard): return (yes) - unless ($tree.$ matches $patt.$): return (no) - ..else: - unless ($tree.$ == $patt.$): return (no) - - if ((#$tree) != (#$patt)): return (no) - return (yes) -$filenames = ($(COMMAND LINE ARGS).extras, from 2 to -1) -if ((#$filenames) == 0): - say (" - Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find - - ") - $filenames = ["stdin"] - -for $filename in $filenames: - $file = (read file $filename) - unless $file: - fail "File does not exist: \$filename" - $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) - try: - $tree = ($code parsed) - ..if it fails $msg: - say - red (" - \$filename failed to parse: - \$msg - ") - $tree = (nil) - - unless $tree: - do next $filename - - $results = [] - for $t in recursive $tree: - if ($t matches $pattern_tree): - $line_num = ($file, line number at $t.source.start) - $results, add { - .line = $line_num, .text = "\(blue "\$filename:\$line_num:")\n\(source lines of $t)" - } - - for $sub in $t: - if ($sub is syntax tree): - recurse $t on $sub - - if $(COMMAND LINE ARGS).l: - if ((#$results) > 0): - say $filename - ..else: - sort $results by $ -> $.line - for $ in $results: - say $.text diff --git a/tools/format.nom b/tools/format.nom deleted file mode 100755 index 07f7980..0000000 --- a/tools/format.nom +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - Auto-format Nomsu code. Usage: - nomsu -t format [-i] file1 file2... - - If the "-i" flag is used, the file will be edited in-place. - If the "-q" flag is used and an error occurs, the original file will be printed. - If no files are passed in, this will read from stdin. - -use "lib/os" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -$filenames = $(COMMAND LINE ARGS).extras -if ((#$filenames) == 0): - say (" - Warning: reading from stdin (ctrl-d to abort). To avoid this message, use nomsu -t format - - ") - $filenames = ["stdin"] - -for $filename in $filenames: - $file = (read file $filename) - unless $file: - fail "File does not exist: \$filename" - $leading_indent = ($file, matching "\n*([ ]*)") - $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) - try: - $tree = ($code parsed) - ..if it fails $msg: - if $(COMMAND LINE ARGS).q: - $formatted = $file - ..else: - say $msg - - if ($tree and (not $formatted)): - $formatted = - "\$leading_indent\($tree as nomsu, text, with "\n" -> "\n\$leading_indent")" - - if $formatted: - if $(COMMAND LINE ARGS).i: - write $formatted to file $filename - ..else: - say $formatted inline diff --git a/tools/parse.nom b/tools/parse.nom deleted file mode 100755 index 22bfdc3..0000000 --- a/tools/parse.nom +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - Tool to print out a parse tree of files in an easy-to-read format. Usage: - nomsu tools/parse.nom file1 file2 directory1 ... - -use "lib/os" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -externally (print tree $t at indent $indent) means: - if $t.type is: - "Action": - say "\($indent)Action (\($t.stub)):" - for $arg in $t: - if ($arg is syntax tree): - print tree $arg at indent "\$indent " - - "MethodCall": - say "\($indent)MethodCall on:" - print tree $t.1 at indent "\$indent " - print tree $t.2 at indent "\$indent " - - "Number": - say "\$indent\($t.1)" - - "Var": - say "\($indent)$\($t.1)" - - else: - say "\$indent\($t.type):" - for $arg in $t: - when: - ($arg is syntax tree): - print tree $arg at indent "\$indent " - - else: - say "\$indent \(quote $arg)" - -for $filename in $(COMMAND LINE ARGS).extras: - $file = (read file $filename) - unless $file: - fail "File does not exist: \$filename" - $nomsu = (NomsuCode from (Source $filename 1 (size of $file)) $file) - $tree = ($nomsu parsed) - print tree $tree at indent "" diff --git a/tools/repl.nom b/tools/repl.nom deleted file mode 100755 index 175d068..0000000 --- a/tools/repl.nom +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This file defines a Read-Evaluate-Print-Loop (REPL) for Nomsu - -use "lib/consolecolor" -use "lib/os" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -externally (help) means: - say (" - This is the Nomsu v\(Nomsu version) interactive console. - You can type in Nomsu code here and hit 'enter' twice to run it. - To exit, type 'exit' or 'quit' and hit enter twice. - ") - -say (" - - \(bright)\(underscore)Welcome to the Nomsu v\(Nomsu version) interactive console!\ - ..\(reset color) - press 'enter' twice to run a command - -") - -repeat: - say (bright (yellow ">> ")) inline - $buff = [] - repeat: - say (bright) inline - $line = ($io.read "*L") - say (reset color) inline - if (($line == "\n") or (not $line)): - if ((size of $buff) > 0): - # clear the line - say "\027[1A\027[2K" inline - go to (run buffer) - $buff, add ($line, with "\t" -> " ") - say (dim (yellow ".. ")) inline - - --- (run buffer) --- - - if ((size of $buff) == 0): stop - $buff = ($buff, joined) - spoof file $buff - try: - $tree = ($buff parsed) - ..if it fails with $err: - say $err - do next - - unless $tree: - do next - - for $chunk in $tree: - try: - $lua = ($chunk as lua) - ..if it fails with $err: say $err - - unless $lua: - do next - - # TODO: this is a bit hacky, it just defaults variables to global - so that stuff mostly works across multiple lines. It would be - nicer if local variables actually worked. - $lua, remove free vars - try: - $ret = (run $lua) - ..if it fails with $err: say $err - ..if it succeeds: - if (type of $ret) is: - "nil": - do nothing - - "boolean": - say "= \("yes" if $ret else "no")" - - "table": - if $ret.as_nomsu: - say "= \($ret, as nomsu)" - ..else: - say "= \$ret" - - else: - say "= \$ret" diff --git a/tools/replace.nom b/tools/replace.nom deleted file mode 100755 index f4df0e4..0000000 --- a/tools/replace.nom +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - This is a tool to replace syntax trees with something new. - - Usage: - nomsu -t replace [-i] [-f] [-q] [--literal="$v1 $v2..."] file1 file2... - - Example: - nomsu -t replace "($1 and $2) and $3" "all of [$1, $2, $3]" my_file.nom - - If the "-i" flag is used, the file(s) will be edited in-place. - When editing in-place, if the "-f" flag is not used, each change will be - run past the user first. - If the "-q" flag is used and a file fails to parse, the original file - contents will be output. - If no files are passed in, this will read from stdin. - -use "lib/os" -use "lib/consolecolor" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if ((#$(COMMAND LINE ARGS).extras) < 2): - fail (" - Usage: nomsu -t replace [--literal="$v1 $v2..."] file1 file2... - ") -$pattern = $(COMMAND LINE ARGS).extras.1 -$replacement = $(COMMAND LINE ARGS).extras.2 -$pattern_tree = ($pattern parsed) -$replacement_tree = ($replacement parsed) -$literal_vars = {} -if $(COMMAND LINE ARGS).literal: - for $var in ($(COMMAND LINE ARGS).literal, all matches of "$([^ ]*)"): - $literal_vars.$var = (yes) - -if (($pattern_tree.type == "Var") and (not $literal_vars.($pattern_tree.1))): - fail "Pattern matches every part of the file." - -$pattern_vars = { - : for $ in recursive $pattern_tree: - if (($.type == "Var") and (not $literal_vars.($.1))): add $.1 - for $child in $: - if ($child is a "Syntax Tree"): - recurse $ on $child -} - -# TODO: support wildcards and unpacking - e.g. nomsu -t replace "test(: $test; *$more_tests)" "*$more_tests; *$test" -($tree matches $patt with $substitution_values) means: - # TODO: optimize - $substitution_values = {: for $k = $v in $substitution_values: add $k = $v} - when: - (not ($tree is syntax tree)): return (no) - (($patt.type == "Var") and $pattern_vars.($patt.1)): - if $substitution_values.($patt.1): - if ($tree == $substitution_values.($patt.1)): - return $substitution_values - ..else: - return (nil) - ..else: - $substitution_values.($patt.1) = $tree - return $substitution_values - ($tree.type != $patt.type): return (nil) - ($tree.type == "Action"): - if (($tree, get stub) != ($patt, get stub)): return (nil) - - for $ in 1 to (#$patt): - if ($patt.$ is syntax tree): - $new_values = ($tree.$ matches $patt.$ with $substitution_values) - unless $new_values: - return (nil) - - for $k = $v in $new_values: - $substitution_values.$k = $v - ..else: - unless ($tree.$ == $patt.$): return (nil) - - if ((#$tree) != (#$patt)): return (nil) - return $substitution_values -$filenames = ($(COMMAND LINE ARGS).extras, from 3 to -1) -if ((#$filenames) == 0): - say (" - Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find - - ") - $filenames = ["stdin"] - -for $filename in $filenames: - $file = (read file $filename) - unless $file: - fail "File does not exist: \$filename" - $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file) - try: - $tree = ($code parsed) - ..if it fails $msg: - if $(COMMAND LINE ARGS).q: - unless $(COMMAND LINE ARGS).i: say $code - ..else: - say $msg - - unless $tree: - do next $filename - - $replaced = {} - $matched = {} - $user_answers = {} - ($tree with replacements) means - $tree, map - for $t: - $values = ($t matches $pattern_tree with {}) - if $values: - $matched.$t = (yes) - for $k = $v in $values: - $values.$k = ($v with replacements) - $ret = ($replacement_tree with vars $values) - if ($(COMMAND LINE ARGS).i and (not $(COMMAND LINE ARGS).f)): - if ($user_answers.$t == (nil)): - if ((#$user_answers) > 0): say "" - $user_answers.$t = "n" - say "\(bright)Should this:" - say (" - \(bright)\(yellow)\("\(($t with replacements) as nomsu)", with "\n" -> "\n ")\ - ..\(reset color) - ") - say "\(bright)..be replaced with:" - - say (" - \(bright)\(blue)\("\($ret as nomsu)", with "\n" -> "\n ")\(reset color) - ") - - $user_answers.$t = (ask "\(bright)..? [Y/n]\(reset color) ") - - if ($user_answers.$t == "n"): return (nil) - $replaced.$t = (yes) - return $ret - $tree2 = ($tree with replacements) - if $(COMMAND LINE ARGS).i: - if ((#$user_answers) > 0): say "" - say (" - \(#$replaced)/\(#$matched) replacement\("" if ((#$replaced) == 1) else "s") in \$filename - ") - - if ((#$replaced) > 0): - write "\($tree2 as nomsu)" to file $filename - ..else: - say ($tree2 as nomsu) diff --git a/tools/test.nom b/tools/test.nom deleted file mode 100755 index 44d783b..0000000 --- a/tools/test.nom +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - Tool to run all tests in a file (i.e. the code block inside a call to 'test $'). Usage: - nomsu tools/test.nom file1 file2 directory1 ... - -use "lib/os" -use "lib/consolecolor" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -for $filename in $(COMMAND LINE ARGS).extras: - $(test environment) = (new environment) - $(test environment), use $filename - $file = (read file $filename) - $version = - $file, matching (" - #![^ - ]* nomsu %-V[ ]*([^ - ]*) - ") - $file_tests = [] - for $src = $test in $(test environment).TESTS: - if $version: - $test = (" - #!/usr/bin/env nomsu -V\$version - \$test - ") - $file_tests, add {.test = $test, .source = $src} - - unless ($file_tests is empty): - sort $file_tests by $ -> $.source - lua> "io.write('[ .. ] ', \$filename); io.flush()" - - if (command line args).v: say "" - - for $ in $file_tests: - if (command line args).v: - say " \(yellow ($.test, with "\n" -> "\n "))" - $(test environment), run $.test - - if (command line args).v: - say (green "PASS") - ..else: - say "\r[\(green "PASS")" diff --git a/tools/upgrade.nom b/tools/upgrade.nom deleted file mode 100755 index b9b8255..0000000 --- a/tools/upgrade.nom +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env nomsu -V6.14 -# - Tool to automatically update code from old versions of Nomsu. Usage: - nomsu tools/upgrade.nom [-i] file1 file2 directory1 ... - If "-i" is the first argument, upgrades will be performed in-place. Otherwise, the - upgraded code will be printed. - -use "compatibility" -use "lib/os" -use "lib/consolecolor" - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -$inplace = ($(COMMAND LINE ARGS).i or $(COMMAND LINE ARGS).inplace) -$start_version = $(COMMAND LINE ARGS)."upgrade-from" -$version = ($(COMMAND LINE ARGS)."upgrade-to" or (Nomsu version)) -$test = ($(COMMAND LINE ARGS).t or $(COMMAND LINE ARGS).test) -for $filename in $(COMMAND LINE ARGS).extras: - $file = (read file $filename) - unless $file: - fail "File does not exist: \$filename" - $leading_indent = ($file, matching "\n*([ ]*)") - $code = (NomsuCode from (Source $filename 1 (size of $file)) $file) - $tree = ($code parsed $start_version) - $uptree = - $tree upgraded from ($start_version or ($tree.version or (Nomsu version))) to - $version - $text = "\$leading_indent\($uptree as nomsu, text, with "\n" -> "\n\$leading_indent")" - when: - $inplace: - say "Upgraded \$filename" - write $text to file $filename - - $test: - if ($uptree == $tree): - say (dim "\$filename will not be changed") - ..else: - say (bright "\$filename will be changed") - - else: - say $text inline -- cgit v1.2.3