From a45aa2624eae02e4af0deb22e9647e457833b6e3 Mon Sep 17 00:00:00 2001 From: Paul Liverman III Date: Fri, 6 Apr 2018 15:30:00 -0700 Subject: [PATCH] stuff --- LIB/init.lua | 8 + LIB/init.moon | 7 + LIB/slab.lua | 180 ++++ LIB/slab.moon | 108 +++ LIB/types/generic.lua | 122 +++ LIB/types/generic.moon | 88 ++ LIB/types/menu.lua | 72 ++ LIB/types/menu.moon | 21 + LIB/types/spinner.lua | 61 ++ LIB/types/spinner.moon | 21 + LIB/types/text.lua | 48 + LIB/types/text.moon | 15 + main.lua | 4 + main.moon | 2 + prototypes/examples/app-bar.lua | 48 + prototypes/examples/badge.lua | 18 + prototypes/examples/bottom-navigation.lua | 17 + prototypes/examples/buttons.lua | 42 + prototypes/examples/chip.lua | 19 + prototypes/examples/divider.lua | 8 + prototypes/scp-clicker/main.lua | 72 ++ prototypes/scp-clicker/main.moon | 122 +-- prototypes/scp-clicker/moon/all.lua | 5 + prototypes/scp-clicker/moon/all.moon | 6 + prototypes/scp-clicker/moon/init.lua | 175 ++++ prototypes/scp-clicker/moon/init.moon | 133 +++ prototypes/scp-clicker/moonscript/base.lua | 153 ++++ prototypes/scp-clicker/moonscript/base.moon | 108 +++ .../scp-clicker/moonscript/cmd/args.lua | 69 ++ .../scp-clicker/moonscript/cmd/args.moon | 47 + .../scp-clicker/moonscript/cmd/coverage.lua | 144 +++ .../scp-clicker/moonscript/cmd/coverage.moon | 82 ++ .../scp-clicker/moonscript/cmd/lint.lua | 323 +++++++ .../scp-clicker/moonscript/cmd/lint.moon | 204 +++++ .../scp-clicker/moonscript/cmd/moonc.lua | 197 ++++ .../scp-clicker/moonscript/cmd/moonc.moon | 195 ++++ .../scp-clicker/moonscript/cmd/watchers.lua | 268 ++++++ .../scp-clicker/moonscript/cmd/watchers.moon | 103 +++ prototypes/scp-clicker/moonscript/compile.lua | 748 ++++++++++++++++ .../scp-clicker/moonscript/compile.moon | 485 ++++++++++ .../moonscript/compile/statement.lua | 240 +++++ .../moonscript/compile/statement.moon | 137 +++ .../scp-clicker/moonscript/compile/value.lua | 325 +++++++ .../scp-clicker/moonscript/compile/value.moon | 210 +++++ prototypes/scp-clicker/moonscript/data.lua | 83 ++ prototypes/scp-clicker/moonscript/data.moon | 37 + prototypes/scp-clicker/moonscript/dump.lua | 41 + prototypes/scp-clicker/moonscript/dump.moon | 18 + prototypes/scp-clicker/moonscript/errors.lua | 117 +++ prototypes/scp-clicker/moonscript/errors.moon | 88 ++ prototypes/scp-clicker/moonscript/init.lua | 5 + prototypes/scp-clicker/moonscript/init.moon | 2 + .../scp-clicker/moonscript/line_tables.lua | 1 + .../scp-clicker/moonscript/line_tables.moon | 1 + prototypes/scp-clicker/moonscript/parse.lua | 245 +++++ prototypes/scp-clicker/moonscript/parse.moon | 361 ++++++++ .../scp-clicker/moonscript/parse/env.lua | 70 ++ .../scp-clicker/moonscript/parse/env.moon | 51 ++ .../scp-clicker/moonscript/parse/literals.lua | 39 + .../moonscript/parse/literals.moon | 38 + .../scp-clicker/moonscript/parse/util.lua | 306 +++++++ .../scp-clicker/moonscript/parse/util.moon | 197 ++++ .../scp-clicker/moonscript/transform.lua | 4 + .../scp-clicker/moonscript/transform.moon | 5 + .../moonscript/transform/accumulator.lua | 110 +++ .../moonscript/transform/accumulator.moon | 71 ++ .../moonscript/transform/class.lua | 486 ++++++++++ .../moonscript/transform/class.moon | 285 ++++++ .../moonscript/transform/comprehension.lua | 54 ++ .../moonscript/transform/comprehension.moon | 30 + .../moonscript/transform/destructure.lua | 233 +++++ .../moonscript/transform/destructure.moon | 128 +++ .../moonscript/transform/names.lua | 118 +++ .../moonscript/transform/names.moon | 51 ++ .../moonscript/transform/statement.lua | 847 ++++++++++++++++++ .../moonscript/transform/statement.moon | 522 +++++++++++ .../moonscript/transform/statements.lua | 115 +++ .../moonscript/transform/statements.moon | 78 ++ .../moonscript/transform/transformer.lua | 74 ++ .../moonscript/transform/transformer.moon | 42 + .../moonscript/transform/value.lua | 265 ++++++ .../moonscript/transform/value.moon | 164 ++++ prototypes/scp-clicker/moonscript/types.lua | 323 +++++++ prototypes/scp-clicker/moonscript/types.moon | 197 ++++ prototypes/scp-clicker/moonscript/util.lua | 213 +++++ prototypes/scp-clicker/moonscript/util.moon | 137 +++ prototypes/scp-clicker/moonscript/version.lua | 7 + .../scp-clicker/moonscript/version.moon | 8 + prototypes/scp-clicker/slab/init.lua | 8 + prototypes/scp-clicker/slab/init.moon | 7 + prototypes/scp-clicker/slab/slab.lua | 180 ++++ prototypes/scp-clicker/slab/slab.moon | 108 +++ prototypes/scp-clicker/slab/types/generic.lua | 90 ++ .../scp-clicker/slab/types/generic.moon | 76 ++ prototypes/scp-clicker/slab/types/menu.lua | 72 ++ prototypes/scp-clicker/slab/types/menu.moon | 21 + prototypes/scp-clicker/slab/types/spinner.lua | 61 ++ .../scp-clicker/slab/types/spinner.moon | 21 + prototypes/scp-clicker/slab/types/text.lua | 48 + prototypes/scp-clicker/slab/types/text.moon | 15 + prototypes/slab.lua | 35 + 101 files changed, 12392 insertions(+), 77 deletions(-) create mode 100644 LIB/init.lua create mode 100644 LIB/init.moon create mode 100644 LIB/slab.lua create mode 100644 LIB/slab.moon create mode 100644 LIB/types/generic.lua create mode 100644 LIB/types/generic.moon create mode 100644 LIB/types/menu.lua create mode 100644 LIB/types/menu.moon create mode 100644 LIB/types/spinner.lua create mode 100644 LIB/types/spinner.moon create mode 100644 LIB/types/text.lua create mode 100644 LIB/types/text.moon create mode 100644 main.lua create mode 100644 prototypes/examples/app-bar.lua create mode 100644 prototypes/examples/badge.lua create mode 100644 prototypes/examples/bottom-navigation.lua create mode 100644 prototypes/examples/buttons.lua create mode 100644 prototypes/examples/chip.lua create mode 100644 prototypes/examples/divider.lua create mode 100644 prototypes/scp-clicker/main.lua create mode 100644 prototypes/scp-clicker/moon/all.lua create mode 100644 prototypes/scp-clicker/moon/all.moon create mode 100644 prototypes/scp-clicker/moon/init.lua create mode 100644 prototypes/scp-clicker/moon/init.moon create mode 100644 prototypes/scp-clicker/moonscript/base.lua create mode 100644 prototypes/scp-clicker/moonscript/base.moon create mode 100644 prototypes/scp-clicker/moonscript/cmd/args.lua create mode 100644 prototypes/scp-clicker/moonscript/cmd/args.moon create mode 100644 prototypes/scp-clicker/moonscript/cmd/coverage.lua create mode 100644 prototypes/scp-clicker/moonscript/cmd/coverage.moon create mode 100644 prototypes/scp-clicker/moonscript/cmd/lint.lua create mode 100644 prototypes/scp-clicker/moonscript/cmd/lint.moon create mode 100644 prototypes/scp-clicker/moonscript/cmd/moonc.lua create mode 100644 prototypes/scp-clicker/moonscript/cmd/moonc.moon create mode 100644 prototypes/scp-clicker/moonscript/cmd/watchers.lua create mode 100644 prototypes/scp-clicker/moonscript/cmd/watchers.moon create mode 100644 prototypes/scp-clicker/moonscript/compile.lua create mode 100644 prototypes/scp-clicker/moonscript/compile.moon create mode 100644 prototypes/scp-clicker/moonscript/compile/statement.lua create mode 100644 prototypes/scp-clicker/moonscript/compile/statement.moon create mode 100644 prototypes/scp-clicker/moonscript/compile/value.lua create mode 100644 prototypes/scp-clicker/moonscript/compile/value.moon create mode 100644 prototypes/scp-clicker/moonscript/data.lua create mode 100644 prototypes/scp-clicker/moonscript/data.moon create mode 100644 prototypes/scp-clicker/moonscript/dump.lua create mode 100644 prototypes/scp-clicker/moonscript/dump.moon create mode 100644 prototypes/scp-clicker/moonscript/errors.lua create mode 100644 prototypes/scp-clicker/moonscript/errors.moon create mode 100644 prototypes/scp-clicker/moonscript/init.lua create mode 100644 prototypes/scp-clicker/moonscript/init.moon create mode 100644 prototypes/scp-clicker/moonscript/line_tables.lua create mode 100644 prototypes/scp-clicker/moonscript/line_tables.moon create mode 100644 prototypes/scp-clicker/moonscript/parse.lua create mode 100644 prototypes/scp-clicker/moonscript/parse.moon create mode 100644 prototypes/scp-clicker/moonscript/parse/env.lua create mode 100644 prototypes/scp-clicker/moonscript/parse/env.moon create mode 100644 prototypes/scp-clicker/moonscript/parse/literals.lua create mode 100644 prototypes/scp-clicker/moonscript/parse/literals.moon create mode 100644 prototypes/scp-clicker/moonscript/parse/util.lua create mode 100644 prototypes/scp-clicker/moonscript/parse/util.moon create mode 100644 prototypes/scp-clicker/moonscript/transform.lua create mode 100644 prototypes/scp-clicker/moonscript/transform.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/accumulator.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/accumulator.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/class.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/class.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/comprehension.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/comprehension.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/destructure.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/destructure.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/names.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/names.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/statement.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/statement.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/statements.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/statements.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/transformer.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/transformer.moon create mode 100644 prototypes/scp-clicker/moonscript/transform/value.lua create mode 100644 prototypes/scp-clicker/moonscript/transform/value.moon create mode 100644 prototypes/scp-clicker/moonscript/types.lua create mode 100644 prototypes/scp-clicker/moonscript/types.moon create mode 100644 prototypes/scp-clicker/moonscript/util.lua create mode 100644 prototypes/scp-clicker/moonscript/util.moon create mode 100644 prototypes/scp-clicker/moonscript/version.lua create mode 100644 prototypes/scp-clicker/moonscript/version.moon create mode 100644 prototypes/scp-clicker/slab/init.lua create mode 100644 prototypes/scp-clicker/slab/init.moon create mode 100644 prototypes/scp-clicker/slab/slab.lua create mode 100644 prototypes/scp-clicker/slab/slab.moon create mode 100644 prototypes/scp-clicker/slab/types/generic.lua create mode 100644 prototypes/scp-clicker/slab/types/generic.moon create mode 100644 prototypes/scp-clicker/slab/types/menu.lua create mode 100644 prototypes/scp-clicker/slab/types/menu.moon create mode 100644 prototypes/scp-clicker/slab/types/spinner.lua create mode 100644 prototypes/scp-clicker/slab/types/spinner.moon create mode 100644 prototypes/scp-clicker/slab/types/text.lua create mode 100644 prototypes/scp-clicker/slab/types/text.moon create mode 100644 prototypes/slab.lua diff --git a/LIB/init.lua b/LIB/init.lua new file mode 100644 index 0000000..479c007 --- /dev/null +++ b/LIB/init.lua @@ -0,0 +1,8 @@ +local path = ... +if path:sub(-4) == "init" then + path = path:sub(1, -6) + if not (path) then + path = "." + end +end +return require(tostring(path) .. "/slab") diff --git a/LIB/init.moon b/LIB/init.moon new file mode 100644 index 0000000..70729f0 --- /dev/null +++ b/LIB/init.moon @@ -0,0 +1,7 @@ +path = ... + +if path\sub(-4) == "init" + path = path\sub 1, -6 + path = "." unless path + +return require "#{path}/slab" diff --git a/LIB/slab.lua b/LIB/slab.lua new file mode 100644 index 0000000..d0aaf62 --- /dev/null +++ b/LIB/slab.lua @@ -0,0 +1,180 @@ +local path = (...):sub(1, -(("slab"):len() + 2)) +local graphics +graphics = love.graphics +local slab = { + _VERSION = "0.1.0", + _DESCRIPTION = "GUI library for LÖVE.", + _URL = nil, + _LICENSE = "The MIT License (MIT)", + _AUTHOR = "Paul Liverman III" +} +slab.ui = { + x = 0, + y = 0, + w = graphics.getWidth(), + h = graphics.getHeight() +} +slab.hovered = false +slab.focused = false +slab.types = { + "menu", + "spinner", + "text" +} +slab.generic = require(tostring(path) .. "/types/generic") +local _list_0 = slab.types +for _index_0 = 1, #_list_0 do + local t = _list_0[_index_0] + slab[t] = require(tostring(path) .. "/types/" .. tostring(t)) +end +slab.make = function(element, parent) + if element == nil then + element = { } + end + if parent == nil then + parent = slab.ui + end + local _list_1 = slab.types + for _index_0 = 1, #_list_1 do + local t = _list_1[_index_0] + if element[t] then + return slab[t](element, parent) + end + end + return slab.generic(element, parent) +end +slab.update = function(dt, element) + if element == nil then + element = slab.ui + end + if element.update then + element:update(dt) + end + for _index_0 = 1, #element do + local child = element[_index_0] + slab.update(dt, child) + end +end +slab.draw = function(element) + if element == nil then + element = slab.ui + end + if element == slab.ui then + graphics.push("all") + graphics.origin() + end + local drawChildren + if element.visible and element.draw then + drawChildren = element:draw() + end + if drawChildren ~= false then + for _index_0 = 1, #element do + local child = element[_index_0] + slab.draw(child) + end + end + if element == slab.ui then + return graphics.pop() + end +end +slab.mousemoved = function(x, y, dx, dy, element) + if element == nil then + element = slab.ui + end + local previous + if element == slab.ui then + previous = slab.hovered + end + if element.visible and element.hoverable and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + slab.hovered = element + for _index_0 = 1, #element do + local child = element[_index_0] + slab.mousemoved(x, y, dx, dy, child) + end + end + if element == slab.ui then + if slab.hovered ~= previous then + if previous and previous.hovered then + previous:hovered(false) + end + if slab.hovered.hovered then + slab.hovered:hovered(true) + end + end + if slab.focused and slab.focused.mousemoved then + return slab.focused:mousemoved(x - slab.focused.x, y - slab.focused.y, dx, dy) + end + end +end +slab.mousepressed = function(x, y, btn, element) + if element == nil then + element = screen.ui + end + if element == screen.ui then + if btn == "wd" then + return slab.wheelmoved(0, -1) + elseif btn == "wu" then + return slab.wheelmoved(0, 1) + end + end + local handled = false + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + for i = #element, 1, -1 do + handled = slab.mousepressed(x, y, btn, element[i]) + if handled ~= false then + return handled + end + end + if element.mousepressed then + handled = element:mousepressed(btn, x, y) + end + end + return handled +end +slab.mousereleased = function(x, y, btn, element) + local clickHandled, mousereleaseHandled = false, false + if element then + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + for i = #element, 1, -1 do + clickHandled, mousereleaseHandled = slab.mousereleased(x, y, btn, element[i]) + if clickHandled ~= false or mousereleaseHandled ~= false then + return clickHandled, mousereleaseHandled + end + end + if element.clicked then + clickHandled = element:clicked(btn, x - element.x, y - element.y) + end + if element.mousereleased then + mousereleaseHandled = element:mousereleased(btn, x - element.x, y - element.y) + end + if clickHandled then + slab.focused = element + end + end + else + do + element = slab.focused + if element then + if element.mousereleased then + mousereleaseHandled = element:mousereleased(btn, x - element.x, y - element.y) + end + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + if element.clicked then + clickHandled = element:clicked(btn, x - element.x, y - element.y) + end + end + if clickHandled ~= false or mousereleaseHandled ~= false then + return clickHandled, mousereleaseHandled + end + return slab.mousereleased(x, y, btn, slab.ui) + end + end + end +end +slab.wheelmoved = function(x, y) + if slab.hovered and slab.hovered.wheelmoved then + return slab.hovered:wheelmoved(x, y) + end + return false +end +return slab diff --git a/LIB/slab.moon b/LIB/slab.moon new file mode 100644 index 0000000..7bbda5c --- /dev/null +++ b/LIB/slab.moon @@ -0,0 +1,108 @@ +path = (...)\sub 1, -("slab"\len! + 2) + +import graphics from love + +slab = { + _VERSION: "0.1.0" + _DESCRIPTION: "GUI library for LÖVE." + _URL: nil + _LICENSE: "The MIT License (MIT)" + _AUTHOR: "Paul Liverman III" +} + +slab.ui = { x: 0, y: 0, w: graphics.getWidth!, h: graphics.getHeight! } +slab.hovered = false +slab.focused = false +slab.types = { "menu", "spinner", "text" } +slab.generic = require "#{path}/types/generic" +for t in *slab.types + slab[t] = require "#{path}/types/#{t}" + -- slab[t]\load slab if slab[t].load + +slab.make = (element={}, parent=slab.ui) -> + for t in *slab.types + return slab[t](element, parent) if element[t] + return slab.generic element, parent + +slab.update = (dt, element=slab.ui) -> + element\update dt if element.update + for child in *element + slab.update dt, child + +slab.draw = (element=slab.ui) -> + if element == slab.ui + graphics.push "all" + graphics.origin! + + local drawChildren + drawChildren = element\draw! if element.visible and element.draw + if drawChildren != false + for child in *element + slab.draw child + + if element == slab.ui + graphics.pop! + +slab.mousemoved = (x, y, dx, dy, element=slab.ui) -> + local previous + if element == slab.ui + previous = slab.hovered + + if element.visible and element.hoverable and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + slab.hovered = element + for child in *element + slab.mousemoved x, y, dx, dy, child + + if element == slab.ui + if slab.hovered != previous + previous\hovered false if previous and previous.hovered + slab.hovered\hovered true if slab.hovered.hovered + if slab.focused and slab.focused.mousemoved + return slab.focused\mousemoved x - slab.focused.x, y - slab.focused.y, dx, dy + +slab.mousepressed = (x, y, btn, element=screen.ui) -> + if element == screen.ui + if btn == "wd" + return slab.wheelmoved 0, -1 + elseif btn == "wu" + return slab.wheelmoved 0, 1 + + handled = false + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + for i = #element, 1, -1 + handled = slab.mousepressed x, y, btn, element[i] + return handled if handled != false + + if element.mousepressed + handled = element\mousepressed btn, x, y + + return handled + +slab.mousereleased = (x, y, btn, element) -> + clickHandled, mousereleaseHandled = false, false + if element + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + for i = #element, 1, -1 + clickHandled, mousereleaseHandled = slab.mousereleased x, y, btn, element[i] + if clickHandled != false or mousereleaseHandled != false + return clickHandled, mousereleaseHandled + + clickHandled = element\clicked btn, x - element.x, y - element.y if element.clicked + mousereleaseHandled = element\mousereleased btn, x - element.x, y - element.y if element.mousereleased + slab.focused = element if clickHandled + + elseif element = slab.focused + mousereleaseHandled = element\mousereleased btn, x - element.x, y - element.y if element.mousereleased + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + clickHandled = element\clicked btn, x - element.x, y - element.y if element.clicked + + if clickHandled != false or mousereleaseHandled != false + return clickHandled, mousereleaseHandled + + slab.mousereleased x, y, btn, slab.ui + +slab.wheelmoved = (x, y) -> + return slab.hovered\wheelmoved x, y if slab.hovered and slab.hovered.wheelmoved + return false + +return slab diff --git a/LIB/types/generic.lua b/LIB/types/generic.lua new file mode 100644 index 0000000..e5d6495 --- /dev/null +++ b/LIB/types/generic.lua @@ -0,0 +1,122 @@ +local path = (...):sub(1, -(("generic"):len() + 2)) +local floor +floor = math.floor +local generic +do + local _class_0 + local _base_0 = { + draw = function(self) + graphics.setColor(self.color) + if self.background then + return graphics.draw(self.background, self.x, self.y, 0, self.w / self.backgroundWidth, self.h / self.backgroundHeight) + else + return graphics.rectangle("fill", self.x, self.y, self.w, self.h) + end + end, + _align = function(self) + local _exp_0 = self.align + if "top-left" == _exp_0 or "left" == _exp_0 or "bottom-left" == _exp_0 then + self.x = self.parent.x + elseif "top" == _exp_0 or "center" == _exp_0 or "bottom" == _exp_0 then + self.x = self.parent.x + self.parent.w / 2 - self.w / 2 + elseif "top-right" == _exp_0 or "right" == _exp_0 or "bottom-right" == _exp_0 then + self.x = self.parent.x + self.parent.w - self.w + end + local _exp_1 = self.align + if "top-left" == _exp_1 or "top" == _exp_1 or "top-right" == _exp_1 then + self.y = self.parent.y + elseif "left" == _exp_1 or "center" == _exp_1 or "right" == _exp_1 then + self.y = self.parent.y + self.parent.h / 2 - self.h / 2 + elseif "bottom-left" == _exp_1 or "bottom" == _exp_1 or "bottom-right" == _exp_1 then + self.y = self.parent.y + self.parent.h - self.h + end + for _index_0 = 1, #self do + local child = self[_index_0] + child:_align() + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + if not (parent) then + error("No parent element!") + end + self.parent = parent + for k, v in pairs(element) do + self[k] = v + end + if self.visible == nil then + self.visible = true + end + if self.hovered then + self.hoverable = true + end + if self.hoverable == nil then + self.hoverable = false + end + if self.width then + if self.width <= 1 then + self.w = floor(parent.w * self.width) + else + self.w = floor(self.width) + end + else + self.width = 0 + self.w = 0 + end + if self.height then + if self.height <= 1 then + self.h = floor(parent.h * self.height) + else + self.h = floor(self.height) + end + else + self.height = 0 + self.h = 0 + end + if "table" == type(self.background) then + if not (self.color) then + self.color = self.background + end + self.background = nil + else + if not (self.color) then + self.color = { + 255, + 255, + 255, + 255 + } + end + end + if self.background then + self.backgroundWidth = self.background:getWidth() + self.backgroundHeight = self.background:getHeight() + self.aspectRatio = self.backgroundWidth / self.backgroundHeight + end + if #self > 0 then + local slab = require(tostring(path:sub(1, -7)) .. "/slab") + for i = 1, #self do + self[i] = slab.make(self[i], self) + end + end + return self:_align() + end, + __base = _base_0, + __name = "generic" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + generic = _class_0 + return _class_0 +end diff --git a/LIB/types/generic.moon b/LIB/types/generic.moon new file mode 100644 index 0000000..bb723dc --- /dev/null +++ b/LIB/types/generic.moon @@ -0,0 +1,88 @@ +path = (...)\sub 1, -("generic"\len! + 2) + +import floor from math + +-- local slab +class generic + -- @load: (lib) => + -- print "LOAD", self, lib + -- slab = lib + + new: (element={}, parent) => + error "No parent element!" unless parent + @parent = parent + for k,v in pairs element + @[k] = v + + @visible = true if @visible == nil + @hoverable = true if @hovered + @hoverable = false if @hoverable == nil + + if @width + if @width <= 1 + @w = floor parent.w * @width + else + @w = floor(@width) + else + @width = 0 + @w = 0 + + if @height + if @height <= 1 + @h = floor parent.h * @height + else + @h = floor(@height) + else + @height = 0 + @h = 0 + + if "table" == type @background + @color = @background unless @color + @background = nil + else + @color = {255, 255, 255, 255} unless @color + + if @background + @backgroundWidth = @background\getWidth! + @backgroundHeight = @background\getHeight! + @aspectRatio = @backgroundWidth / @backgroundHeight + + if #@ > 0 + slab = require "#{path\sub 1, -7}/slab" + for i = 1, #@ + @[i] = slab.make(@[i], @) + + @_align! + + draw: => + graphics.setColor @color + if @background + graphics.draw @background, @x, @y, 0, @w / @backgroundWidth, @h / @backgroundHeight + else + graphics.rectangle "fill", @x, @y, @w, @h + + _align: => + switch @align + when "top-left", "left", "bottom-left" + -- align left + @x = @parent.x + when "top", "center", "bottom" + -- align center + @x = @parent.x + @parent.w / 2 - @w / 2 + when "top-right", "right", "bottom-right" + -- align right + @x = @parent.x + @parent.w - @w + + switch @align + when "top-left", "top", "top-right" + -- align top + @y = @parent.y + when "left", "center", "right" + -- align center + @y = @parent.y + @parent.h / 2 - @h / 2 + when "bottom-left", "bottom", "bottom-right" + -- align bottom + @y = @parent.y + @parent.h - @h + + for child in *@ + child\_align! diff --git a/LIB/types/menu.lua b/LIB/types/menu.lua new file mode 100644 index 0000000..6c5fee9 --- /dev/null +++ b/LIB/types/menu.lua @@ -0,0 +1,72 @@ +local path = (...):sub(1, -(("menu"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local insert +insert = table.insert +local menu +do + local _class_0 + local _parent_0 = generic + local _base_0 = { } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + _class_0.__parent.__init(self, element, parent) + for _index_0 = 1, #self do + local child = self[_index_0] + if self.menu.width then + if not (child.width) then + child.width = self.menu.width + end + end + if self.menu.height then + if not (child.height) then + child.height = self.menu.height + end + end + if self.menu.align then + if not (child.align) then + child.align = self.menu.align + end + end + end + if #self.menu > 0 then + local slab = require(tostring(path:sub(1, -7)) .. "/slab") + local _list_0 = self.menu + for _index_0 = 1, #_list_0 do + local child = _list_0[_index_0] + insert(self, slab.make(child, self)) + end + end + end, + __base = _base_0, + __name = "menu", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + menu = _class_0 + return _class_0 +end diff --git a/LIB/types/menu.moon b/LIB/types/menu.moon new file mode 100644 index 0000000..038e04f --- /dev/null +++ b/LIB/types/menu.moon @@ -0,0 +1,21 @@ +path = (...)\sub 1, -("menu"\len! + 2) +generic = require "#{path}/generic" + +import insert from table + +class menu extends generic + new: (element={}, parent) => + super element, parent + + for child in *@ + if @menu.width + child.width = @menu.width unless child.width + if @menu.height + child.height = @menu.height unless child.height + if @menu.align + child.align = @menu.align unless child.align + + if #@menu > 0 + slab = require "#{path\sub 1, -7}/slab" + for child in *@menu + insert(@, slab.make child, @) diff --git a/LIB/types/spinner.lua b/LIB/types/spinner.lua new file mode 100644 index 0000000..3b69cd1 --- /dev/null +++ b/LIB/types/spinner.lua @@ -0,0 +1,61 @@ +local path = (...):sub(1, -(("spinner"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local graphics +graphics = love.graphics +local pi, min +do + local _obj_0 = math + pi, min = _obj_0.pi, _obj_0.min +end +local tau = pi * 2 +local half_pi = pi / 2 +local spinner +do + local _class_0 + local _parent_0 = generic + local _base_0 = { + draw = function(self) + return graphics.arc("line", self.x + self.w / 2, self.y + self.h / 2, min(self.w, self.h), self.offset, self.offset + self.value * tau) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + _class_0.__parent.__init(self, element, parent) + if not (self.offset) then + self.offset = 0 + end + self.value = 0.1 + end, + __base = _base_0, + __name = "spinner", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + spinner = _class_0 + return _class_0 +end diff --git a/LIB/types/spinner.moon b/LIB/types/spinner.moon new file mode 100644 index 0000000..698e02f --- /dev/null +++ b/LIB/types/spinner.moon @@ -0,0 +1,21 @@ +path = (...)\sub 1, -("spinner"\len! + 2) +generic = require "#{path}/generic" + +import graphics from love +import pi, min from math +tau = pi * 2 +half_pi = pi / 2 + +class spinner extends generic + new: (element={}, parent) => + super element, parent + + -- @offset = -half_pi + @offset = 0 unless @offset + @value = 0.1 -- temporary + + -- slab = require "#{path\sub 1, -7}/slab" + + draw: => + -- TODO should be setting color! + graphics.arc "line", @x + @w / 2, @y + @h / 2, min(@w, @h), @offset, @offset + @value * tau diff --git a/LIB/types/text.lua b/LIB/types/text.lua new file mode 100644 index 0000000..37166ed --- /dev/null +++ b/LIB/types/text.lua @@ -0,0 +1,48 @@ +local path = (...):sub(1, -(("text"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local graphics +graphics = love.graphics +local text +do + local _class_0 + local _parent_0 = generic + local _base_0 = { + draw = function(self) end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + return _class_0.__parent.__init(self, element, parent) + end, + __base = _base_0, + __name = "text", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + text = _class_0 + return _class_0 +end diff --git a/LIB/types/text.moon b/LIB/types/text.moon new file mode 100644 index 0000000..952093b --- /dev/null +++ b/LIB/types/text.moon @@ -0,0 +1,15 @@ +path = (...)\sub 1, -("text"\len! + 2) +generic = require "#{path}/generic" + +import graphics from love + +class text extends generic + new: (element={}, parent) => + super element, parent + + -- @font = graphics.newFont 14 + + -- slab = require "#{path\sub 1, -7}/slab" + + draw: => + -- TODO diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..57a6039 --- /dev/null +++ b/main.lua @@ -0,0 +1,4 @@ +for k in pairs(love.handlers) do + print(k) +end +return print(type(love.graphics.newImage("prototypes/scp-clicker/gfx/banknote.png"))) diff --git a/main.moon b/main.moon index e458fea..8f4338a 100644 --- a/main.moon +++ b/main.moon @@ -1,2 +1,4 @@ for k in pairs love.handlers print k + +print type love.graphics.newImage "prototypes/scp-clicker/gfx/banknote.png" diff --git a/prototypes/examples/app-bar.lua b/prototypes/examples/app-bar.lua new file mode 100644 index 0000000..e575a9d --- /dev/null +++ b/prototypes/examples/app-bar.lua @@ -0,0 +1,48 @@ +local _ = { + toolbar = { + width = 1, + padding = 5, + { + dropdown = { + toggle = "click", + background = hamburger, + background = { + hamburger, + close_x + }, + { } + } + }, + { + text = "Title", + clicked = function(btn, x, y) end + }, + { + align = "right", + dropdown = { + toggle = "click", + background = down_arrow, + { } + } + } + } +} +return { + toolbar = { + padding = 5, + { + background = close_x + }, + { + textinput = { + name = "title", + value = "Title" + } + }, + { + align = "right", + text = "SAVE", + clicked = function(btn, x, y) end + } + } +} diff --git a/prototypes/examples/badge.lua b/prototypes/examples/badge.lua new file mode 100644 index 0000000..0987aa2 --- /dev/null +++ b/prototypes/examples/badge.lua @@ -0,0 +1,18 @@ +return { + background = some_image, + badge = { + text = 2, + background = { + 255, + 0, + 0, + 255 + }, + color = { + 255, + 255, + 255, + 255 + } + } +} diff --git a/prototypes/examples/bottom-navigation.lua b/prototypes/examples/bottom-navigation.lua new file mode 100644 index 0000000..e460fc8 --- /dev/null +++ b/prototypes/examples/bottom-navigation.lua @@ -0,0 +1,17 @@ +return { + toolbar = { + width = 0.5, + padding = 3, + { + background = recents_icon + }, + { + align = "center", + background = favorites_icon + }, + { + align = "right", + background = nearby_icon + } + } +} diff --git a/prototypes/examples/buttons.lua b/prototypes/examples/buttons.lua new file mode 100644 index 0000000..859eab1 --- /dev/null +++ b/prototypes/examples/buttons.lua @@ -0,0 +1,42 @@ +local _ = { + text = "click me" +} +_ = { + padding = 3, + text = "button", + background = { + 0, + 255, + 255, + 255 + }, + color = { + 255, + 255, + 255, + 255 + } +} +_ = { + padding = 8, + text = "+", + background = { + 0, + 255, + 255, + 255 + }, + color = { + 255, + 255, + 255, + 255 + }, + round = true, + round = 5 +} +return { + background = some_image, + clicked = function(btn, x, y) end, + hovered = function(x, y) end +} diff --git a/prototypes/examples/chip.lua b/prototypes/examples/chip.lua new file mode 100644 index 0000000..32a37f7 --- /dev/null +++ b/prototypes/examples/chip.lua @@ -0,0 +1,19 @@ +return { + rounded = 5, + background = some_color, + padding = { + right = 5 + }, + { + rounded = true, + background = an_image + }, + { + text = "Colored Chip" + }, + { + width = 0.8, + rounded = true, + background = an_x_image + } +} diff --git a/prototypes/examples/divider.lua b/prototypes/examples/divider.lua new file mode 100644 index 0000000..4e7fa8f --- /dev/null +++ b/prototypes/examples/divider.lua @@ -0,0 +1,8 @@ +local _ = { + text = "some text" +} +return { + newline = true, + height = 1.0001, + background = dark_grey +} diff --git a/prototypes/scp-clicker/main.lua b/prototypes/scp-clicker/main.lua new file mode 100644 index 0000000..898e77c --- /dev/null +++ b/prototypes/scp-clicker/main.lua @@ -0,0 +1,72 @@ +local graphics +graphics = love.graphics +local slab = require("slab") +local clickers = { + width = 1 / 3, + height = 1, + align = "left", + menu = { + height = 1 / 3, + align = "center", + { + name = "cash_clicker", + clicked = function(self, btn, x, y) end, + spinner = { + value = 0 + }, + { + size = 0.8, + align = "center", + background = graphics.newImage("gfx/banknote.png") + }, + { + align = "bottom-left", + text = "Lv. 0" + } + }, + { + name = "danger_clicker", + clicked = function(self, btn, x, y) end, + spinner = { + value = 0 + }, + { + size = 0.8, + align = "center", + background = graphics.newImage("gfx/hazard-sign.png") + }, + { + align = "bottom-left", + text = "Lv. 0" + } + }, + { + name = "research_clicker", + clicked = function(self, btn, x, y) end, + spinner = { + value = 0 + }, + { + size = 0.8, + align = "center", + background = graphics.newImage("gfx/fizzing-flask.png") + }, + { + align = "bottom-left", + text = "Lv. 0" + } + } + } +} +slab.make(clickers) +love.update = function(dt) + return slab.update(dt) +end +love.draw = function() + return slab.draw() +end +love.keypressed = function(key) + if key == "escape" then + return love.event.quit() + end +end diff --git a/prototypes/scp-clicker/main.moon b/prototypes/scp-clicker/main.moon index b2fcf26..6cdfaa5 100644 --- a/prototypes/scp-clicker/main.moon +++ b/prototypes/scp-clicker/main.moon @@ -1,104 +1,72 @@ import graphics from love -slab = { - types: { "menu", "spinner" } - screen: { x: 0, y: 0, width: graphics.getWidth!, height: graphics.getHeight! } -} - -slab.generic = class - new: (element={}, parent=slab.screen) => - for k,v in pairs element - @[k] = v - - if @width - if @width <= 1 - @w = parent.w * @width - else - @w = @width - - if @height - if @height <= 1 - @h = parent.h * @height - else - @h = @height - -slab.menu = class - new: (parent=slab.screen) => - for child in *@ - if @menu.width - child.width = @menu.width - if @menu.height - child.height = @menu.height - if @menu.align - child.align = @menu.align - - -- TODO menus are a vertical structure - -slab.make = (element={}, parent=slab.screen) -> - element = slab.generic element, parent - for t in *slab.types - element = slab[t](parent) if element[t] - return element - --- b = slab.make sample_value: true --- print(b.sample_value) +slab = require "slab" clickers = { width: 1/3 height: 1 - menu: { -- block elements default to minimum space required + align: "left" -- if not set, will not align! + menu: { -- block elements default to minimum space required (but we defined it) height: 1/3 -- defined here applies to children align: "center" -- defined here means the children are aligned this way { name: "cash_clicker" - clicked: (btn, x, y) -> + clicked: (btn, x, y) => -- stuff spinner: { value: 0 -- a defined value means it is set instead of just spinning constantly ? - { - width: 0.8 - align: "center" - background: graphics.newImage "gfx/banknote.png" - } - { - align: "bottom-left" - text: "Lv. 0" - } + } + { + size: 0.8 + align: "center" + background: graphics.newImage "gfx/banknote.png" + } + { + align: "bottom-left" + text: "Lv. 0" } } { name: "danger_clicker" - clicked: (btn, x, y) -> + clicked: (btn, x, y) => -- stuff - spinner: { - value: 0 - { - width: 0.8 - align: "center" - background: graphics.newImage "gfx/hazard-sign.png" - } - { - align: "bottom-left" - text: "Lv. 0" - } + spinner: { value: 0 } + { + size: 0.8 + align: "center" + background: graphics.newImage "gfx/hazard-sign.png" + } + { + align: "bottom-left" + text: "Lv. 0" } } { name: "research_clicker" - clicked: (btn, x, y) -> + clicked: (btn, x, y) => -- stuff - spinner: { - value: 0 - { - width: 0.8 - align: "center" - background: graphics.newImage "gfx/fizzing-flask.png" - } - { - align: "bottom-left" - text: "Lv. 0" - } + spinner: { value: 0 } + { + size: 0.8 + align: "center" + background: graphics.newImage "gfx/fizzing-flask.png" + } + { + align: "bottom-left" + text: "Lv. 0" } } } } + +slab.make clickers + +love.update = (dt) -> + slab.update dt + +love.draw = -> + slab.draw! + +love.keypressed = (key) -> + if key == "escape" + love.event.quit! diff --git a/prototypes/scp-clicker/moon/all.lua b/prototypes/scp-clicker/moon/all.lua new file mode 100644 index 0000000..945576d --- /dev/null +++ b/prototypes/scp-clicker/moon/all.lua @@ -0,0 +1,5 @@ +local moon = require("moon") +for k, v in pairs(moon) do + _G[k] = v +end +return moon diff --git a/prototypes/scp-clicker/moon/all.moon b/prototypes/scp-clicker/moon/all.moon new file mode 100644 index 0000000..cb773c7 --- /dev/null +++ b/prototypes/scp-clicker/moon/all.moon @@ -0,0 +1,6 @@ +-- install moon into global scope +moon = require "moon" +for k,v in pairs moon + _G[k] = v +moon + diff --git a/prototypes/scp-clicker/moon/init.lua b/prototypes/scp-clicker/moon/init.lua new file mode 100644 index 0000000..d4c2490 --- /dev/null +++ b/prototypes/scp-clicker/moon/init.lua @@ -0,0 +1,175 @@ +local lua = { + debug = debug, + type = type +} +local getfenv, setfenv, dump +do + local _obj_0 = require("moonscript.util") + getfenv, setfenv, dump = _obj_0.getfenv, _obj_0.setfenv, _obj_0.dump +end +local p, is_object, type, debug, run_with_scope, bind_methods, defaultbl, extend, copy, mixin, mixin_object, mixin_table, fold +p = function(...) + return print(dump(...)) +end +is_object = function(value) + return lua.type(value) == "table" and value.__class +end +type = function(value) + local base_type = lua.type(value) + if base_type == "table" then + local cls = value.__class + if cls then + return cls + end + end + return base_type +end +debug = setmetatable({ + upvalue = function(fn, k, v) + local upvalues = { } + local i = 1 + while true do + local name = lua.debug.getupvalue(fn, i) + if name == nil then + break + end + upvalues[name] = i + i = i + 1 + end + if not upvalues[k] then + error("Failed to find upvalue: " .. tostring(k)) + end + if not v then + local _, value = lua.debug.getupvalue(fn, upvalues[k]) + return value + else + return lua.debug.setupvalue(fn, upvalues[k], v) + end + end +}, { + __index = lua.debug +}) +run_with_scope = function(fn, scope, ...) + local old_env = getfenv(fn) + local env = setmetatable({ }, { + __index = function(self, name) + local val = scope[name] + if val ~= nil then + return val + else + return old_env[name] + end + end + }) + setfenv(fn, env) + return fn(...) +end +bind_methods = function(obj) + return setmetatable({ }, { + __index = function(self, name) + local val = obj[name] + if val and lua.type(val) == "function" then + local bound + bound = function(...) + return val(obj, ...) + end + self[name] = bound + return bound + else + return val + end + end + }) +end +defaultbl = function(t, fn) + if not fn then + fn = t + t = { } + end + return setmetatable(t, { + __index = function(self, name) + local val = fn(self, name) + rawset(self, name, val) + return val + end + }) +end +extend = function(...) + local tbls = { + ... + } + if #tbls < 2 then + return + end + for i = 1, #tbls - 1 do + local a = tbls[i] + local b = tbls[i + 1] + setmetatable(a, { + __index = b + }) + end + return tbls[1] +end +copy = function(self) + local _tbl_0 = { } + for key, val in pairs(self) do + _tbl_0[key] = val + end + return _tbl_0 +end +mixin = function(self, cls, ...) + for key, val in pairs(cls.__base) do + if not key:match("^__") then + self[key] = val + end + end + return cls.__init(self, ...) +end +mixin_object = function(self, object, methods) + for _index_0 = 1, #methods do + local name = methods[_index_0] + self[name] = function(parent, ...) + return object[name](object, ...) + end + end +end +mixin_table = function(self, tbl, keys) + if keys then + for _index_0 = 1, #keys do + local key = keys[_index_0] + self[key] = tbl[key] + end + else + for key, val in pairs(tbl) do + self[key] = val + end + end +end +fold = function(items, fn) + local len = #items + if len > 1 then + local accum = fn(items[1], items[2]) + for i = 3, len do + accum = fn(accum, items[i]) + end + return accum + else + return items[1] + end +end +return { + dump = dump, + p = p, + is_object = is_object, + type = type, + debug = debug, + run_with_scope = run_with_scope, + bind_methods = bind_methods, + defaultbl = defaultbl, + extend = extend, + copy = copy, + mixin = mixin, + mixin_object = mixin_object, + mixin_table = mixin_table, + fold = fold +} diff --git a/prototypes/scp-clicker/moon/init.moon b/prototypes/scp-clicker/moon/init.moon new file mode 100644 index 0000000..658d886 --- /dev/null +++ b/prototypes/scp-clicker/moon/init.moon @@ -0,0 +1,133 @@ + +lua = { :debug, :type } +import getfenv, setfenv, dump from require "moonscript.util" + +local * + +p = (...) -> + print dump ... + +is_object = (value) -> -- is a moonscript object + lua.type(value) == "table" and value.__class + +type = (value) -> -- class aware type + base_type = lua.type value + if base_type == "table" + cls = value.__class + return cls if cls + base_type + +debug = setmetatable { + upvalue: (fn, k, v) -> + upvalues = {} + i = 1 + while true + name = lua.debug.getupvalue(fn, i) + break if name == nil + upvalues[name] = i + i += 1 + + if not upvalues[k] + error "Failed to find upvalue: " .. tostring k + + if not v + _, value = lua.debug.getupvalue fn, upvalues[k] + value + else + lua.debug.setupvalue fn, upvalues[k], v +}, __index: lua.debug + +-- run a function with scope injected before its function environment +run_with_scope = (fn, scope, ...) -> + old_env = getfenv fn + env = setmetatable {}, { + __index: (name) => + val = scope[name] + if val != nil + val + else + old_env[name] + } + setfenv fn, env + fn ... + +-- wrap obj such that calls to methods do not need a reference to self +bind_methods = (obj) -> + setmetatable {}, { + __index: (name) => + val = obj[name] + if val and lua.type(val) == "function" + bound = (...) -> val obj, ... + self[name] = bound + bound + else + val + } + +-- use a function to provide default values to table +-- optionally specify a starting table +-- fibanocci table: +-- t = defaultbl {[0]: 0, [1]: 1}, (i) -> self[i - 1] + self[i - 2] +defaultbl = (t, fn) -> + if not fn + fn = t + t = {} + setmetatable t, { + __index: (name) => + val = fn self, name + rawset self, name, val + val + } + +-- chain together tables by __index metatables +extend = (...) -> + tbls = {...} + return if #tbls < 2 + + for i = 1, #tbls - 1 + a = tbls[i] + b = tbls[i + 1] + + setmetatable a, __index: b + + tbls[1] + +-- shallow copy +copy = => + {key,val for key,val in pairs self} + +-- mixin class properties into self, call new +mixin = (cls, ...) => + for key, val in pairs cls.__base + self[key] = val if not key\match"^__" + cls.__init self, ... + +-- mixin methods from an object into self +mixin_object = (object, methods) => + for name in *methods + self[name] = (parent, ...) -> + object[name](object, ...) + +-- mixin table values into self +mixin_table = (tbl, keys) => + if keys + for key in *keys + self[key] = tbl[key] + else + for key, val in pairs tbl + self[key] = val + +fold = (items, fn)-> + len = #items + if len > 1 + accum = fn items[1], items[2] + for i=3,len + accum = fn accum, items[i] + accum + else + items[1] + +{ + :dump, :p, :is_object, :type, :debug, :run_with_scope, :bind_methods, + :defaultbl, :extend, :copy, :mixin, :mixin_object, :mixin_table, :fold +} diff --git a/prototypes/scp-clicker/moonscript/base.lua b/prototypes/scp-clicker/moonscript/base.lua new file mode 100644 index 0000000..6bc0d49 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/base.lua @@ -0,0 +1,153 @@ +local compile = require("moonscript.compile") +local parse = require("moonscript.parse") +local concat, insert, remove +do + local _obj_0 = table + concat, insert, remove = _obj_0.concat, _obj_0.insert, _obj_0.remove +end +local split, dump, get_options, unpack +do + local _obj_0 = require("moonscript.util") + split, dump, get_options, unpack = _obj_0.split, _obj_0.dump, _obj_0.get_options, _obj_0.unpack +end +local lua = { + loadstring = loadstring, + load = load +} +local dirsep, line_tables, create_moonpath, to_lua, moon_loader, loadstring, loadfile, dofile, insert_loader, remove_loader +dirsep = "/" +line_tables = require("moonscript.line_tables") +create_moonpath = function(package_path) + local moonpaths + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = split(package_path, ";") + for _index_0 = 1, #_list_0 do + local _continue_0 = false + repeat + local path = _list_0[_index_0] + local prefix = path:match("^(.-)%.lua$") + if not (prefix) then + _continue_0 = true + break + end + local _value_0 = prefix .. ".moon" + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + moonpaths = _accum_0 + end + return concat(moonpaths, ";") +end +to_lua = function(text, options) + if options == nil then + options = { } + end + if "string" ~= type(text) then + local t = type(text) + return nil, "expecting string (got " .. t .. ")" + end + local tree, err = parse.string(text) + if not tree then + return nil, err + end + local code, ltable, pos = compile.tree(tree, options) + if not code then + return nil, compile.format_error(ltable, pos, text) + end + return code, ltable +end +moon_loader = function(name) + local name_path = name:gsub("%.", dirsep) + local file, file_path + for path in package.moonpath:gmatch("[^;]+") do + file_path = path:gsub("?", name_path) + file = io.open(file_path) + if file then + break + end + end + if file then + local text = file:read("*a") + file:close() + local res, err = loadstring(text, "@" .. tostring(file_path)) + if not res then + error(file_path .. ": " .. err) + end + return res + end + return nil, "Could not find moon file" +end +loadstring = function(...) + local options, str, chunk_name, mode, env = get_options(...) + chunk_name = chunk_name or "=(moonscript.loadstring)" + local code, ltable_or_err = to_lua(str, options) + if not (code) then + return nil, ltable_or_err + end + if chunk_name then + line_tables[chunk_name] = ltable_or_err + end + return (lua.loadstring or lua.load)(code, chunk_name, unpack({ + mode, + env + })) +end +loadfile = function(fname, ...) + local file, err = io.open(fname) + if not (file) then + return nil, err + end + local text = assert(file:read("*a")) + file:close() + return loadstring(text, "@" .. tostring(fname), ...) +end +dofile = function(...) + local f = assert(loadfile(...)) + return f() +end +insert_loader = function(pos) + if pos == nil then + pos = 2 + end + if not package.moonpath then + package.moonpath = create_moonpath(package.path) + end + local loaders = package.loaders or package.searchers + for _index_0 = 1, #loaders do + local loader = loaders[_index_0] + if loader == moon_loader then + return false + end + end + insert(loaders, pos, moon_loader) + return true +end +remove_loader = function() + local loaders = package.loaders or package.searchers + for i, loader in ipairs(loaders) do + if loader == moon_loader then + remove(loaders, i) + return true + end + end + return false +end +return { + _NAME = "moonscript", + insert_loader = insert_loader, + remove_loader = remove_loader, + to_lua = to_lua, + moon_loader = moon_loader, + dirsep = dirsep, + dofile = dofile, + loadfile = loadfile, + loadstring = loadstring, + create_moonpath = create_moonpath +} diff --git a/prototypes/scp-clicker/moonscript/base.moon b/prototypes/scp-clicker/moonscript/base.moon new file mode 100644 index 0000000..3a4502d --- /dev/null +++ b/prototypes/scp-clicker/moonscript/base.moon @@ -0,0 +1,108 @@ +compile = require "moonscript.compile" +parse = require "moonscript.parse" + +import concat, insert, remove from table +import split, dump, get_options, unpack from require "moonscript.util" + +lua = :loadstring, :load + +local * + +dirsep = "/" +line_tables = require "moonscript.line_tables" + +-- create moon path package from lua package path +create_moonpath = (package_path) -> + moonpaths = for path in *split package_path, ";" + prefix = path\match "^(.-)%.lua$" + continue unless prefix + prefix .. ".moon" + concat moonpaths, ";" + +to_lua = (text, options={}) -> + if "string" != type text + t = type text + return nil, "expecting string (got ".. t ..")" + + tree, err = parse.string text + if not tree + return nil, err + + code, ltable, pos = compile.tree tree, options + if not code + return nil, compile.format_error(ltable, pos, text) + + code, ltable + +moon_loader = (name) -> + name_path = name\gsub "%.", dirsep + + local file, file_path + for path in package.moonpath\gmatch "[^;]+" + file_path = path\gsub "?", name_path + file = io.open file_path + break if file + + if file + text = file\read "*a" + file\close! + res, err = loadstring text, "@#{file_path}" + if not res + error file_path .. ": " .. err + + return res + + return nil, "Could not find moon file" + + +loadstring = (...) -> + options, str, chunk_name, mode, env = get_options ... + chunk_name or= "=(moonscript.loadstring)" + + code, ltable_or_err = to_lua str, options + unless code + return nil, ltable_or_err + + line_tables[chunk_name] = ltable_or_err if chunk_name + -- the unpack prevents us from passing nil + (lua.loadstring or lua.load) code, chunk_name, unpack { mode, env } + +loadfile = (fname, ...) -> + file, err = io.open fname + return nil, err unless file + text = assert file\read "*a" + file\close! + loadstring text, "@#{fname}", ... + +-- throws errros +dofile = (...) -> + f = assert loadfile ... + f! + +insert_loader = (pos=2) -> + if not package.moonpath + package.moonpath = create_moonpath package.path + + loaders = package.loaders or package.searchers + for loader in *loaders + return false if loader == moon_loader + + insert loaders, pos, moon_loader + true + +remove_loader = -> + loaders = package.loaders or package.searchers + + for i, loader in ipairs loaders + if loader == moon_loader + remove loaders, i + return true + + false + +{ + _NAME: "moonscript" + :insert_loader, :remove_loader, :to_lua, :moon_loader, :dirsep, + :dofile, :loadfile, :loadstring, :create_moonpath +} + diff --git a/prototypes/scp-clicker/moonscript/cmd/args.lua b/prototypes/scp-clicker/moonscript/cmd/args.lua new file mode 100644 index 0000000..68b06fd --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/args.lua @@ -0,0 +1,69 @@ +local unpack +unpack = require("moonscript.util").unpack +local parse_spec +parse_spec = function(spec) + local flags, words + if type(spec) == "table" then + flags, words = unpack(spec), spec + else + flags, words = spec, { } + end + assert("no flags for arguments") + local out = { } + for part in flags:gmatch("%w:?") do + if part:match(":$") then + out[part:sub(1, 1)] = { + value = true + } + else + out[part] = { } + end + end + return out +end +local parse_arguments +parse_arguments = function(spec, args) + spec = parse_spec(spec) + local out = { } + local remaining = { } + local last_flag = nil + for _index_0 = 1, #args do + local _continue_0 = false + repeat + local arg = args[_index_0] + local group = { } + if last_flag then + out[last_flag] = arg + _continue_0 = true + break + end + do + local flag = arg:match("-(%w+)") + if flag then + do + local short_name = spec[flag] + if short_name then + out[short_name] = true + else + for char in flag:gmatch(".") do + out[char] = true + end + end + end + _continue_0 = true + break + end + end + table.insert(remaining, arg) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return out, remaining +end +return { + parse_arguments = parse_arguments, + parse_spec = parse_spec +} diff --git a/prototypes/scp-clicker/moonscript/cmd/args.moon b/prototypes/scp-clicker/moonscript/cmd/args.moon new file mode 100644 index 0000000..ac65c0e --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/args.moon @@ -0,0 +1,47 @@ +import unpack from require "moonscript.util" +parse_spec = (spec) -> + flags, words = if type(spec) == "table" + unpack(spec), spec + else + spec, {} + + assert "no flags for arguments" + + out = {} + for part in flags\gmatch "%w:?" + if part\match ":$" + out[part\sub 1,1] = { value: true } + else + out[part] = {} + + out + +parse_arguments = (spec, args) -> + spec = parse_spec spec + + out = {} + + remaining = {} + last_flag = nil + + for arg in *args + group = {} + if last_flag + out[last_flag] = arg + continue + + if flag = arg\match "-(%w+)" + if short_name = spec[flag] + out[short_name] = true + else + for char in flag\gmatch "." + out[char] = true + continue + + table.insert remaining, arg + + out, remaining + + + +{ :parse_arguments, :parse_spec } diff --git a/prototypes/scp-clicker/moonscript/cmd/coverage.lua b/prototypes/scp-clicker/moonscript/cmd/coverage.lua new file mode 100644 index 0000000..bed561f --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/coverage.lua @@ -0,0 +1,144 @@ +local log +log = function(str) + if str == nil then + str = "" + end + return io.stderr:write(str .. "\n") +end +local create_counter +create_counter = function() + return setmetatable({ }, { + __index = function(self, name) + do + local tbl = setmetatable({ }, { + __index = function(self) + return 0 + end + }) + self[name] = tbl + return tbl + end + end + }) +end +local position_to_lines +position_to_lines = function(file_content, positions) + local lines = { } + local current_pos = 0 + local line_no = 1 + for char in file_content:gmatch(".") do + do + local count = rawget(positions, current_pos) + if count then + lines[line_no] = count + end + end + if char == "\n" then + line_no = line_no + 1 + end + current_pos = current_pos + 1 + end + return lines +end +local format_file +format_file = function(fname, positions) + fname = fname:gsub("^@", "") + local file = assert(io.open(fname)) + local content = file:read("*a") + file:close() + local lines = position_to_lines(content, positions) + log("------| @" .. tostring(fname)) + local line_no = 1 + for line in (content .. "\n"):gmatch("(.-)\n") do + local foramtted_no = ("% 5d"):format(line_no) + local sym = lines[line_no] and "*" or " " + log(tostring(sym) .. tostring(foramtted_no) .. "| " .. tostring(line)) + line_no = line_no + 1 + end + return log() +end +local CodeCoverage +do + local _class_0 + local _base_0 = { + reset = function(self) + self.line_counts = create_counter() + end, + start = function(self) + return debug.sethook((function() + local _base_1 = self + local _fn_0 = _base_1.process_line + return function(...) + return _fn_0(_base_1, ...) + end + end)(), "l") + end, + stop = function(self) + return debug.sethook() + end, + print_results = function(self) + return self:format_results() + end, + process_line = function(self, _, line_no) + local debug_data = debug.getinfo(2, "S") + local source = debug_data.source + self.line_counts[source][line_no] = self.line_counts[source][line_no] + 1 + end, + format_results = function(self) + local line_table = require("moonscript.line_tables") + local positions = create_counter() + for file, lines in pairs(self.line_counts) do + local _continue_0 = false + repeat + local file_table = line_table[file] + if not (file_table) then + _continue_0 = true + break + end + for line, count in pairs(lines) do + local _continue_1 = false + repeat + local position = file_table[line] + if not (position) then + _continue_1 = true + break + end + positions[file][position] = positions[file][position] + count + _continue_1 = true + until true + if not _continue_1 then + break + end + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + for file, ps in pairs(positions) do + format_file(file, ps) + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + return self:reset() + end, + __base = _base_0, + __name = "CodeCoverage" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + CodeCoverage = _class_0 +end +return { + CodeCoverage = CodeCoverage +} diff --git a/prototypes/scp-clicker/moonscript/cmd/coverage.moon b/prototypes/scp-clicker/moonscript/cmd/coverage.moon new file mode 100644 index 0000000..281c12a --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/coverage.moon @@ -0,0 +1,82 @@ + +log = (str="") -> + io.stderr\write str .. "\n" + +create_counter = -> + setmetatable {}, __index: (name) => + with tbl = setmetatable {}, __index: => 0 + @[name] = tbl + +position_to_lines = (file_content, positions) -> + lines = {} + current_pos = 0 + line_no = 1 + for char in file_content\gmatch "." + if count = rawget positions, current_pos + lines[line_no] = count + + if char == "\n" + line_no += 1 + + current_pos += 1 + + lines + +format_file = (fname, positions) -> + -- sources have @ in front of file names + fname = fname\gsub "^@", "" + + file = assert io.open fname + content = file\read "*a" + file\close! + + lines = position_to_lines content, positions + log "------| @#{fname}" + line_no = 1 + for line in (content .. "\n")\gmatch "(.-)\n" + foramtted_no = "% 5d"\format(line_no) + sym = lines[line_no] and "*" or " " + log "#{sym}#{foramtted_no}| #{line}" + line_no += 1 + + log! + + +class CodeCoverage + new: => + @reset! + + reset: => + @line_counts = create_counter! + + start: => + debug.sethook @\process_line, "l" + + stop: => + debug.sethook! + + print_results: => + @format_results! + + process_line: (_, line_no) => + debug_data = debug.getinfo 2, "S" + source = debug_data.source + @line_counts[source][line_no] += 1 + + format_results: => + line_table = require "moonscript.line_tables" + positions = create_counter! + + for file, lines in pairs @line_counts + file_table = line_table[file] + continue unless file_table + + for line, count in pairs lines + position = file_table[line] + continue unless position + positions[file][position] += count + + for file, ps in pairs positions + format_file file, ps + +{ :CodeCoverage } diff --git a/prototypes/scp-clicker/moonscript/cmd/lint.lua b/prototypes/scp-clicker/moonscript/cmd/lint.lua new file mode 100644 index 0000000..8f036ff --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/lint.lua @@ -0,0 +1,323 @@ +local insert +insert = table.insert +local Set +Set = require("moonscript.data").Set +local Block +Block = require("moonscript.compile").Block +local mtype +mtype = require("moonscript.util").moon.type +local default_whitelist = Set({ + '_G', + '_VERSION', + 'assert', + 'bit32', + 'collectgarbage', + 'coroutine', + 'debug', + 'dofile', + 'error', + 'getfenv', + 'getmetatable', + 'io', + 'ipairs', + 'load', + 'loadfile', + 'loadstring', + 'math', + 'module', + 'next', + 'os', + 'package', + 'pairs', + 'pcall', + 'print', + 'rawequal', + 'rawget', + 'rawlen', + 'rawset', + 'require', + 'select', + 'setfenv', + 'setmetatable', + 'string', + 'table', + 'tonumber', + 'tostring', + 'type', + 'unpack', + 'xpcall', + "nil", + "true", + "false" +}) +local LinterBlock +do + local _class_0 + local _parent_0 = Block + local _base_0 = { + lint_mark_used = function(self, name) + if self.lint_unused_names and self.lint_unused_names[name] then + self.lint_unused_names[name] = false + return + end + if self.parent then + return self.parent:lint_mark_used(name) + end + end, + lint_check_unused = function(self) + if not (self.lint_unused_names and next(self.lint_unused_names)) then + return + end + local names_by_position = { } + for name, pos in pairs(self.lint_unused_names) do + local _continue_0 = false + repeat + if not (pos) then + _continue_0 = true + break + end + names_by_position[pos] = names_by_position[pos] or { } + insert(names_by_position[pos], name) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + local tuples + do + local _accum_0 = { } + local _len_0 = 1 + for pos, names in pairs(names_by_position) do + _accum_0[_len_0] = { + pos, + names + } + _len_0 = _len_0 + 1 + end + tuples = _accum_0 + end + table.sort(tuples, function(a, b) + return a[1] < b[1] + end) + for _index_0 = 1, #tuples do + local _des_0 = tuples[_index_0] + local pos, names + pos, names = _des_0[1], _des_0[2] + insert(self:get_root_block().lint_errors, { + "assigned but unused " .. tostring(table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_1 = 1, #names do + local n = names[_index_1] + _accum_0[_len_0] = "`" .. tostring(n) .. "`" + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ")), + pos + }) + end + end, + render = function(self, ...) + self:lint_check_unused() + return _class_0.__parent.__base.render(self, ...) + end, + block = function(self, ...) + do + local _with_0 = _class_0.__parent.__base.block(self, ...) + _with_0.block = self.block + _with_0.render = self.render + _with_0.get_root_block = self.get_root_block + _with_0.lint_check_unused = self.lint_check_unused + _with_0.lint_mark_used = self.lint_mark_used + _with_0.value_compilers = self.value_compilers + _with_0.statement_compilers = self.statement_compilers + return _with_0 + end + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, whitelist_globals, ...) + if whitelist_globals == nil then + whitelist_globals = default_whitelist + end + _class_0.__parent.__init(self, ...) + self.get_root_block = function() + return self + end + self.lint_errors = { } + local vc = self.value_compilers + self.value_compilers = setmetatable({ + ref = function(block, val) + local name = val[2] + if not (block:has_name(name) or whitelist_globals[name] or name:match("%.")) then + insert(self.lint_errors, { + "accessing global `" .. tostring(name) .. "`", + val[-1] + }) + end + block:lint_mark_used(name) + return vc.ref(block, val) + end + }, { + __index = vc + }) + local sc = self.statement_compilers + self.statement_compilers = setmetatable({ + assign = function(block, node) + local names = node[2] + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + if type(name) == "table" and name[1] == "temp_name" then + _continue_0 = true + break + end + local real_name, is_local = block:extract_assign_name(name) + if not (is_local or real_name and not block:has_name(real_name, true)) then + _continue_0 = true + break + end + if real_name == "_" then + _continue_0 = true + break + end + block.lint_unused_names = block.lint_unused_names or { } + block.lint_unused_names[real_name] = node[-1] or 0 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return sc.assign(block, node) + end + }, { + __index = sc + }) + end, + __base = _base_0, + __name = "LinterBlock", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + LinterBlock = _class_0 +end +local format_lint +format_lint = function(errors, code, header) + if not (next(errors)) then + return + end + local pos_to_line, get_line + do + local _obj_0 = require("moonscript.util") + pos_to_line, get_line = _obj_0.pos_to_line, _obj_0.get_line + end + local formatted + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #errors do + local _des_0 = errors[_index_0] + local msg, pos + msg, pos = _des_0[1], _des_0[2] + if pos then + local line = pos_to_line(code, pos) + msg = "line " .. tostring(line) .. ": " .. tostring(msg) + local line_text = "> " .. get_line(code, line) + local sep_len = math.max(#msg, #line_text) + _accum_0[_len_0] = table.concat({ + msg, + ("="):rep(sep_len), + line_text + }, "\n") + else + _accum_0[_len_0] = msg + end + _len_0 = _len_0 + 1 + end + formatted = _accum_0 + end + if header then + table.insert(formatted, 1, header) + end + return table.concat(formatted, "\n\n") +end +local whitelist_for_file +do + local lint_config + whitelist_for_file = function(fname) + if not (lint_config) then + lint_config = { } + pcall(function() + lint_config = require("lint_config") + end) + end + if not (lint_config.whitelist_globals) then + return default_whitelist + end + local final_list = { } + for pattern, list in pairs(lint_config.whitelist_globals) do + if fname:match(pattern) then + for _index_0 = 1, #list do + local item = list[_index_0] + insert(final_list, item) + end + end + end + return setmetatable(Set(final_list), { + __index = default_whitelist + }) + end +end +local lint_code +lint_code = function(code, name, whitelist_globals) + if name == nil then + name = "string input" + end + local parse = require("moonscript.parse") + local tree, err = parse.string(code) + if not (tree) then + return nil, err + end + local scope = LinterBlock(whitelist_globals) + scope:stms(tree) + scope:lint_check_unused() + return format_lint(scope.lint_errors, code, name) +end +local lint_file +lint_file = function(fname) + local f, err = io.open(fname) + if not (f) then + return nil, err + end + return lint_code(f:read("*a"), fname, whitelist_for_file(fname)) +end +return { + lint_code = lint_code, + lint_file = lint_file +} diff --git a/prototypes/scp-clicker/moonscript/cmd/lint.moon b/prototypes/scp-clicker/moonscript/cmd/lint.moon new file mode 100644 index 0000000..103c25e --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/lint.moon @@ -0,0 +1,204 @@ + +import insert from table +import Set from require "moonscript.data" +import Block from require "moonscript.compile" + +{type: mtype} = require("moonscript.util").moon + +-- globals allowed to be referenced +default_whitelist = Set { + '_G' + '_VERSION' + 'assert' + 'bit32' + 'collectgarbage' + 'coroutine' + 'debug' + 'dofile' + 'error' + 'getfenv' + 'getmetatable' + 'io' + 'ipairs' + 'load' + 'loadfile' + 'loadstring' + 'math' + 'module' + 'next' + 'os' + 'package' + 'pairs' + 'pcall' + 'print' + 'rawequal' + 'rawget' + 'rawlen' + 'rawset' + 'require' + 'select' + 'setfenv' + 'setmetatable' + 'string' + 'table' + 'tonumber' + 'tostring' + 'type' + 'unpack' + 'xpcall' + + "nil" + "true" + "false" +} + +class LinterBlock extends Block + new: (whitelist_globals=default_whitelist, ...) => + super ... + @get_root_block = -> @ + + @lint_errors = {} + + vc = @value_compilers + @value_compilers = setmetatable { + ref: (block, val) -> + name = val[2] + unless block\has_name(name) or whitelist_globals[name] or name\match "%." + insert @lint_errors, { + "accessing global `#{name}`" + val[-1] + } + + block\lint_mark_used name + vc.ref block, val + }, __index: vc + + sc = @statement_compilers + @statement_compilers = setmetatable { + assign: (block, node) -> + names = node[2] + -- extract the names to be declared + for name in *names + -- don't include autogenerated names + if type(name) == "table" and name[1] == "temp_name" + continue + + real_name, is_local = block\extract_assign_name name + -- already defined in some other scope + unless is_local or real_name and not block\has_name real_name, true + continue + + continue if real_name == "_" + + block.lint_unused_names or= {} + block.lint_unused_names[real_name] = node[-1] or 0 + + sc.assign block, node + }, __index: sc + + lint_mark_used: (name) => + if @lint_unused_names and @lint_unused_names[name] + @lint_unused_names[name] = false + return + + if @parent + @parent\lint_mark_used name + + lint_check_unused: => + return unless @lint_unused_names and next @lint_unused_names + + names_by_position = {} + for name, pos in pairs @lint_unused_names + continue unless pos + names_by_position[pos] or= {} + insert names_by_position[pos], name + + tuples = [{pos, names} for pos,names in pairs names_by_position] + table.sort tuples, (a,b) -> a[1] < b[1] + + for {pos, names} in *tuples + insert @get_root_block!.lint_errors, { + "assigned but unused #{table.concat ["`#{n}`" for n in *names], ", "}" + pos + } + + render: (...) => + @lint_check_unused! + super ... + + block: (...) => + + with super ... + .block = @block + .render = @render + .get_root_block = @get_root_block + .lint_check_unused = @lint_check_unused + .lint_mark_used = @lint_mark_used + .value_compilers = @value_compilers + .statement_compilers = @statement_compilers + +format_lint = (errors, code, header) -> + return unless next errors + + import pos_to_line, get_line from require "moonscript.util" + formatted = for {msg, pos} in *errors + if pos + line = pos_to_line code, pos + msg = "line #{line}: #{msg}" + line_text = "> " .. get_line code, line + + sep_len = math.max #msg, #line_text + table.concat { + msg + "="\rep sep_len + line_text + }, "\n" + + else + msg + + table.insert formatted, 1, header if header + table.concat formatted, "\n\n" + + +-- { +-- whitelist_globals: { +-- ["some_file_pattern"]: { +-- "some_var", "another_var" +-- } +-- } +-- } +whitelist_for_file = do + local lint_config + (fname) -> + unless lint_config + lint_config = {} + pcall -> lint_config = require "lint_config" + + return default_whitelist unless lint_config.whitelist_globals + final_list = {} + for pattern, list in pairs lint_config.whitelist_globals + if fname\match(pattern) + for item in *list + insert final_list, item + + setmetatable Set(final_list), __index: default_whitelist + +lint_code = (code, name="string input", whitelist_globals) -> + parse = require "moonscript.parse" + tree, err = parse.string code + return nil, err unless tree + + scope = LinterBlock whitelist_globals + scope\stms tree + scope\lint_check_unused! + + format_lint scope.lint_errors, code, name + +lint_file = (fname) -> + f, err = io.open fname + return nil, err unless f + lint_code f\read("*a"), fname, whitelist_for_file fname + + +{ :lint_code, :lint_file } diff --git a/prototypes/scp-clicker/moonscript/cmd/moonc.lua b/prototypes/scp-clicker/moonscript/cmd/moonc.lua new file mode 100644 index 0000000..09ac3c7 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/moonc.lua @@ -0,0 +1,197 @@ +local lfs = require("lfs") +local split +split = require("moonscript.util").split +local dirsep, dirsep_chars, mkdir, normalize_dir, parse_dir, parse_file, convert_path, format_time, gettime, compile_file_text, write_file, compile_and_write, is_abs_path, path_to_target +dirsep = package.config:sub(1, 1) +if dirsep == "\\" then + dirsep_chars = "\\/" +else + dirsep_chars = dirsep +end +mkdir = function(path) + local chunks = split(path, dirsep) + local accum + for _index_0 = 1, #chunks do + local dir = chunks[_index_0] + accum = accum and tostring(accum) .. tostring(dirsep) .. tostring(dir) or dir + lfs.mkdir(accum) + end + return lfs.attributes(path, "mode") +end +normalize_dir = function(path) + return path:match("^(.-)[" .. tostring(dirsep_chars) .. "]*$") .. dirsep +end +parse_dir = function(path) + return (path:match("^(.-)[^" .. tostring(dirsep_chars) .. "]*$")) +end +parse_file = function(path) + return (path:match("^.-([^" .. tostring(dirsep_chars) .. "]*)$")) +end +convert_path = function(path) + local new_path = path:gsub("%.moon$", ".lua") + if new_path == path then + new_path = path .. ".lua" + end + return new_path +end +format_time = function(time) + return ("%.3fms"):format(time * 1000) +end +do + local socket + gettime = function() + if socket == nil then + pcall(function() + socket = require("socket") + end) + if not (socket) then + socket = false + end + end + if socket then + return socket.gettime() + else + return nil, "LuaSocket needed for benchmark" + end + end +end +compile_file_text = function(text, opts) + if opts == nil then + opts = { } + end + local parse = require("moonscript.parse") + local compile = require("moonscript.compile") + local parse_time + if opts.benchmark then + parse_time = assert(gettime()) + end + local tree, err = parse.string(text) + if not (tree) then + return nil, err + end + if parse_time then + parse_time = gettime() - parse_time + end + if opts.show_parse_tree then + local dump = require("moonscript.dump") + dump.tree(tree) + return true + end + local compile_time + if opts.benchmark then + compile_time = gettime() + end + do + local mod = opts.transform_module + if mod then + tree = assert(require(mod)(tree)) + end + end + local code, posmap_or_err, err_pos = compile.tree(tree) + if not (code) then + return nil, compile.format_error(posmap_or_err, err_pos, text) + end + if compile_time then + compile_time = gettime() - compile_time + end + if opts.show_posmap then + local debug_posmap + debug_posmap = require("moonscript.util").debug_posmap + print("Pos", "Lua", ">>", "Moon") + print(debug_posmap(posmap_or_err, text, code)) + return true + end + if opts.benchmark then + print(table.concat({ + opts.fname or "stdin", + "Parse time \t" .. format_time(parse_time), + "Compile time\t" .. format_time(compile_time), + "" + }, "\n")) + return true + end + return code +end +write_file = function(fname, code) + mkdir(parse_dir(fname)) + local f, err = io.open(fname, "w") + if not (f) then + return nil, err + end + assert(f:write(code)) + assert(f:write("\n")) + f:close() + return "build" +end +compile_and_write = function(src, dest, opts) + if opts == nil then + opts = { } + end + local f = io.open(src) + if not (f) then + return nil, "Can't find file" + end + local text = assert(f:read("*a")) + f:close() + local code, err = compile_file_text(text, opts) + if not code then + return nil, err + end + if code == true then + return true + end + if opts.print then + print(code) + return true + end + return write_file(dest, code) +end +is_abs_path = function(path) + local first = path:sub(1, 1) + if dirsep == "\\" then + return first == "/" or first == "\\" or path:sub(2, 1) == ":" + else + return first == dirsep + end +end +path_to_target = function(path, target_dir, base_dir) + if target_dir == nil then + target_dir = nil + end + if base_dir == nil then + base_dir = nil + end + local target = convert_path(path) + if target_dir then + target_dir = normalize_dir(target_dir) + end + if base_dir and target_dir then + local head = base_dir:match("^(.-)[^" .. tostring(dirsep_chars) .. "]*[" .. tostring(dirsep_chars) .. "]?$") + if head then + local start, stop = target:find(head, 1, true) + if start == 1 then + target = target:sub(stop + 1) + end + end + end + if target_dir then + if is_abs_path(target) then + target = parse_file(target) + end + target = target_dir .. target + end + return target +end +return { + dirsep = dirsep, + mkdir = mkdir, + normalize_dir = normalize_dir, + parse_dir = parse_dir, + parse_file = parse_file, + convert_path = convert_path, + gettime = gettime, + format_time = format_time, + path_to_target = path_to_target, + compile_file_text = compile_file_text, + compile_and_write = compile_and_write +} diff --git a/prototypes/scp-clicker/moonscript/cmd/moonc.moon b/prototypes/scp-clicker/moonscript/cmd/moonc.moon new file mode 100644 index 0000000..ba59b06 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/moonc.moon @@ -0,0 +1,195 @@ +-- assorted utilities for moonc command line tool + +lfs = require "lfs" + +import split from require "moonscript.util" + +local * + +dirsep = package.config\sub 1,1 +dirsep_chars = if dirsep == "\\" + "\\/" -- windows +else + dirsep + +-- similar to mkdir -p +mkdir = (path) -> + chunks = split path, dirsep + + local accum + for dir in *chunks + accum = accum and "#{accum}#{dirsep}#{dir}" or dir + lfs.mkdir accum + + lfs.attributes path, "mode" + +-- strips excess / and ensures path ends with / +normalize_dir = (path) -> + path\match("^(.-)[#{dirsep_chars}]*$") .. dirsep + +-- parse the directory out of a path +parse_dir = (path) -> + (path\match "^(.-)[^#{dirsep_chars}]*$") + +-- parse the filename out of a path +parse_file = (path) -> + (path\match "^.-([^#{dirsep_chars}]*)$") + +-- converts .moon to a .lua path for calcuating compile target +convert_path = (path) -> + new_path = path\gsub "%.moon$", ".lua" + if new_path == path + new_path = path .. ".lua" + new_path + +format_time = (time) -> + "%.3fms"\format time*1000 + +gettime = do + local socket + -> + if socket == nil + pcall -> + socket = require "socket" + + unless socket + socket = false + + if socket + socket.gettime() + else + nil, "LuaSocket needed for benchmark" + +-- compiles file to lua, returns lua code +-- returns nil, error on error +-- returns true if some option handled the output instead +compile_file_text = (text, opts={}) -> + parse = require "moonscript.parse" + compile = require "moonscript.compile" + + parse_time = if opts.benchmark + assert gettime! + + tree, err = parse.string text + return nil, err unless tree + + if parse_time + parse_time = gettime! - parse_time + + if opts.show_parse_tree + dump = require "moonscript.dump" + dump.tree tree + return true + + compile_time = if opts.benchmark + gettime! + + if mod = opts.transform_module + tree = assert require(mod) tree + + code, posmap_or_err, err_pos = compile.tree tree + + unless code + return nil, compile.format_error posmap_or_err, err_pos, text + + if compile_time + compile_time = gettime() - compile_time + + if opts.show_posmap + import debug_posmap from require "moonscript.util" + print "Pos", "Lua", ">>", "Moon" + print debug_posmap posmap_or_err, text, code + return true + + if opts.benchmark + print table.concat { + opts.fname or "stdin", + "Parse time \t" .. format_time(parse_time), + "Compile time\t" .. format_time(compile_time), + "" + }, "\n" + return true + + code + +write_file = (fname, code) -> + mkdir parse_dir fname + f, err = io.open fname, "w" + unless f + return nil, err + + assert f\write code + assert f\write "\n" + f\close! + "build" + +compile_and_write = (src, dest, opts={}) -> + f = io.open src + unless f + return nil, "Can't find file" + + text = assert f\read("*a") + f\close! + + code, err = compile_file_text text, opts + + if not code + return nil, err + + if code == true + return true + + if opts.print + print code + return true + + write_file dest, code + +is_abs_path = (path) -> + first = path\sub 1, 1 + if dirsep == "\\" + first == "/" or first == "\\" or path\sub(2,1) == ":" + else + first == dirsep + + +-- calcuate where a path should be compiled to +-- target_dir: the directory to place the file (optional, from -t flag) +-- base_dir: the directory where the file came from when globbing recursively +path_to_target = (path, target_dir=nil, base_dir=nil) -> + target = convert_path path + + if target_dir + target_dir = normalize_dir target_dir + + if base_dir and target_dir + -- one directory back + head = base_dir\match("^(.-)[^#{dirsep_chars}]*[#{dirsep_chars}]?$") + + if head + start, stop = target\find head, 1, true + if start == 1 + target = target\sub(stop + 1) + + if target_dir + if is_abs_path target + target = parse_file target + + target = target_dir .. target + + target + +{ + :dirsep + :mkdir + :normalize_dir + :parse_dir + :parse_file + :convert_path + :gettime + :format_time + :path_to_target + + :compile_file_text + :compile_and_write +} diff --git a/prototypes/scp-clicker/moonscript/cmd/watchers.lua b/prototypes/scp-clicker/moonscript/cmd/watchers.lua new file mode 100644 index 0000000..7e266b8 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/watchers.lua @@ -0,0 +1,268 @@ +local remove_dupes +remove_dupes = function(list, key_fn) + local seen = { } + return (function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #list do + local _continue_0 = false + repeat + local item = list[_index_0] + local key + if key_fn then + key = key_fn(item) + else + key = item + end + if seen[key] then + _continue_0 = true + break + end + seen[key] = true + local _value_0 = item + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return _accum_0 + end)() +end +local plural +plural = function(count, word) + return tostring(count) .. " " .. tostring(word) .. tostring(count == 1 and "" or "s") +end +local Watcher +do + local _class_0 + local _base_0 = { + start_msg = "Starting watch loop (Ctrl-C to exit)", + print_start = function(self, mode, misc) + return io.stderr:write(tostring(self.start_msg) .. " with " .. tostring(mode) .. " [" .. tostring(misc) .. "]\n") + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, file_list) + self.file_list = file_list + end, + __base = _base_0, + __name = "Watcher" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Watcher = _class_0 +end +local InotifyWacher +do + local _class_0 + local _parent_0 = Watcher + local _base_0 = { + get_dirs = function(self) + local parse_dir + parse_dir = require("moonscript.cmd.moonc").parse_dir + local dirs + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.file_list + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local file_path + file_path = _des_0[1] + local dir = parse_dir(file_path) + if dir == "" then + dir = "./" + end + local _value_0 = dir + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + dirs = _accum_0 + end + return remove_dupes(dirs) + end, + each_update = function(self) + return coroutine.wrap(function() + local dirs = self:get_dirs() + self:print_start("inotify", plural(#dirs, "dir")) + local wd_table = { } + local inotify = require("inotify") + local handle = inotify.init() + for _index_0 = 1, #dirs do + local dir = dirs[_index_0] + local wd = handle:addwatch(dir, inotify.IN_CLOSE_WRITE, inotify.IN_MOVED_TO) + wd_table[wd] = dir + end + while true do + local events = handle:read() + if not (events) then + break + end + for _index_0 = 1, #events do + local _continue_0 = false + repeat + local ev = events[_index_0] + local fname = ev.name + if not (fname:match("%.moon$")) then + _continue_0 = true + break + end + local dir = wd_table[ev.wd] + if dir ~= "./" then + fname = dir .. fname + end + coroutine.yield(fname) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + end + end) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "InotifyWacher", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.available = function(self) + return pcall(function() + return require("inotify") + end) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + InotifyWacher = _class_0 +end +local SleepWatcher +do + local _class_0 + local _parent_0 = Watcher + local _base_0 = { + polling_rate = 1.0, + get_sleep_func = function(self) + local sleep + pcall(function() + sleep = require("socket").sleep + end) + sleep = sleep or require("moonscript")._sleep + if not (sleep) then + error("Missing sleep function; install LuaSocket") + end + return sleep + end, + each_update = function(self) + return coroutine.wrap(function() + local lfs = require("lfs") + local sleep = self:get_sleep_func() + self:print_start("polling", plural(#self.file_list, "files")) + local mod_time = { } + while true do + local _list_0 = self.file_list + for _index_0 = 1, #_list_0 do + local _continue_0 = false + repeat + local _des_0 = _list_0[_index_0] + local file + file = _des_0[1] + local time = lfs.attributes(file, "modification") + if not (time) then + mod_time[file] = nil + _continue_0 = true + break + end + if not (mod_time[file]) then + mod_time[file] = time + _continue_0 = true + break + end + if time > mod_time[file] then + mod_time[file] = time + coroutine.yield(file) + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + sleep(self.polling_rate) + end + end) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "SleepWatcher", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + SleepWatcher = _class_0 +end +return { + Watcher = Watcher, + SleepWatcher = SleepWatcher, + InotifyWacher = InotifyWacher +} diff --git a/prototypes/scp-clicker/moonscript/cmd/watchers.moon b/prototypes/scp-clicker/moonscript/cmd/watchers.moon new file mode 100644 index 0000000..c147fc5 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/cmd/watchers.moon @@ -0,0 +1,103 @@ +remove_dupes = (list, key_fn) -> + seen = {} + return for item in *list + key = if key_fn then key_fn item else item + continue if seen[key] + seen[key] = true + item + +plural = (count, word) -> + "#{count} #{word}#{count == 1 and "" or "s"}" + +-- files is a list of tuples, {source, target} +class Watcher + start_msg: "Starting watch loop (Ctrl-C to exit)" + new: (@file_list) => + + print_start: (mode, misc) => + io.stderr\write "#{@start_msg} with #{mode} [#{misc}]\n" + +class InotifyWacher extends Watcher + @available: => + pcall -> require "inotify" + + get_dirs: => + import parse_dir from require "moonscript.cmd.moonc" + dirs = for {file_path} in *@file_list + dir = parse_dir file_path + dir = "./" if dir == "" + dir + + remove_dupes dirs + + -- creates an iterator that yields a file every time it's updated + -- TODO: detect when new files are added to directories + each_update: => + coroutine.wrap -> + dirs = @get_dirs! + + @print_start "inotify", plural #dirs, "dir" + + wd_table = {} + + inotify = require "inotify" + handle = inotify.init! + + for dir in *dirs + wd = handle\addwatch dir, inotify.IN_CLOSE_WRITE, inotify.IN_MOVED_TO + wd_table[wd] = dir + + while true + events = handle\read! + break unless events -- error? + + for ev in *events + fname = ev.name + continue unless fname\match "%.moon$" + dir = wd_table[ev.wd] + fname = dir .. fname if dir != "./" + + -- TODO: check to make sure the file was in the original set + coroutine.yield fname + +class SleepWatcher extends Watcher + polling_rate: 1.0 + + -- the windows mooonscript binaries provide their own sleep function + get_sleep_func: => + local sleep + + pcall -> + sleep = require("socket").sleep + + -- TODO: this is also loading moonloader, which isn't intentional + sleep or= require("moonscript")._sleep + error "Missing sleep function; install LuaSocket" unless sleep + sleep + + each_update: => + coroutine.wrap -> + lfs = require "lfs" + sleep = @get_sleep_func! + + @print_start "polling", plural #@file_list, "files" + mod_time = {} + + while true + for {file} in *@file_list + time = lfs.attributes file, "modification" + unless time -- file no longer exists + mod_time[file] = nil + continue + + unless mod_time[file] -- file time scanned + mod_time[file] = time + continue + + if time > mod_time[file] + mod_time[file] = time + coroutine.yield file + + sleep @polling_rate + +{:Watcher, :SleepWatcher, :InotifyWacher} diff --git a/prototypes/scp-clicker/moonscript/compile.lua b/prototypes/scp-clicker/moonscript/compile.lua new file mode 100644 index 0000000..0715b86 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile.lua @@ -0,0 +1,748 @@ +local util = require("moonscript.util") +local dump = require("moonscript.dump") +local transform = require("moonscript.transform") +local NameProxy, LocalName +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName +end +local Set +Set = require("moonscript.data").Set +local ntype, value_can_be_statement +do + local _obj_0 = require("moonscript.types") + ntype, value_can_be_statement = _obj_0.ntype, _obj_0.value_can_be_statement +end +local statement_compilers = require("moonscript.compile.statement") +local value_compilers = require("moonscript.compile.value") +local concat, insert +do + local _obj_0 = table + concat, insert = _obj_0.concat, _obj_0.insert +end +local pos_to_line, get_closest_line, trim, unpack +pos_to_line, get_closest_line, trim, unpack = util.pos_to_line, util.get_closest_line, util.trim, util.unpack +local mtype = util.moon.type +local indent_char = " " +local Line, DelayedLine, Lines, Block, RootBlock +do + local _class_0 + local _base_0 = { + mark_pos = function(self, pos, line) + if line == nil then + line = #self + end + if not (self.posmap[line]) then + self.posmap[line] = pos + end + end, + add = function(self, item) + local _exp_0 = mtype(item) + if Line == _exp_0 then + item:render(self) + elseif Block == _exp_0 then + item:render(self) + else + self[#self + 1] = item + end + return self + end, + flatten_posmap = function(self, line_no, out) + if line_no == nil then + line_no = 0 + end + if out == nil then + out = { } + end + local posmap = self.posmap + for i, l in ipairs(self) do + local _exp_0 = mtype(l) + if "string" == _exp_0 or DelayedLine == _exp_0 then + line_no = line_no + 1 + out[line_no] = posmap[i] + for _ in l:gmatch("\n") do + line_no = line_no + 1 + end + out[line_no] = posmap[i] + elseif Lines == _exp_0 then + local _ + _, line_no = l:flatten_posmap(line_no, out) + else + error("Unknown item in Lines: " .. tostring(l)) + end + end + return out, line_no + end, + flatten = function(self, indent, buffer) + if indent == nil then + indent = nil + end + if buffer == nil then + buffer = { } + end + for i = 1, #self do + local l = self[i] + local t = mtype(l) + if t == DelayedLine then + l = l:render() + t = "string" + end + local _exp_0 = t + if "string" == _exp_0 then + if indent then + insert(buffer, indent) + end + insert(buffer, l) + if "string" == type(self[i + 1]) then + local lc = l:sub(-1) + if (lc == ")" or lc == "]") and self[i + 1]:sub(1, 1) == "(" then + insert(buffer, ";") + end + end + insert(buffer, "\n") + elseif Lines == _exp_0 then + l:flatten(indent and indent .. indent_char or indent_char, buffer) + else + error("Unknown item in Lines: " .. tostring(l)) + end + end + return buffer + end, + __tostring = function(self) + local strip + strip = function(t) + if "table" == type(t) then + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #t do + local v = t[_index_0] + _accum_0[_len_0] = strip(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + else + return t + end + end + return "Lines<" .. tostring(util.dump(strip(self)):sub(1, -2)) .. ">" + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.posmap = { } + end, + __base = _base_0, + __name = "Lines" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Lines = _class_0 +end +do + local _class_0 + local _base_0 = { + pos = nil, + append_list = function(self, items, delim) + for i = 1, #items do + self:append(items[i]) + if i < #items then + insert(self, delim) + end + end + return nil + end, + append = function(self, first, ...) + if Line == mtype(first) then + if not (self.pos) then + self.pos = first.pos + end + for _index_0 = 1, #first do + local value = first[_index_0] + self:append(value) + end + else + insert(self, first) + end + if ... then + return self:append(...) + end + end, + render = function(self, buffer) + local current = { } + local add_current + add_current = function() + buffer:add(concat(current)) + return buffer:mark_pos(self.pos) + end + for _index_0 = 1, #self do + local chunk = self[_index_0] + local _exp_0 = mtype(chunk) + if Block == _exp_0 then + local _list_0 = chunk:render(Lines()) + for _index_1 = 1, #_list_0 do + local block_chunk = _list_0[_index_1] + if "string" == type(block_chunk) then + insert(current, block_chunk) + else + add_current() + buffer:add(block_chunk) + current = { } + end + end + else + insert(current, chunk) + end + end + if current[1] then + add_current() + end + return buffer + end, + __tostring = function(self) + return "Line<" .. tostring(util.dump(self):sub(1, -2)) .. ">" + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "Line" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Line = _class_0 +end +do + local _class_0 + local _base_0 = { + prepare = function() end, + render = function(self) + self:prepare() + return concat(self) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, fn) + self.prepare = fn + end, + __base = _base_0, + __name = "DelayedLine" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DelayedLine = _class_0 +end +do + local _class_0 + local _base_0 = { + header = "do", + footer = "end", + export_all = false, + export_proper = false, + value_compilers = value_compilers, + statement_compilers = statement_compilers, + __tostring = function(self) + local h + if "string" == type(self.header) then + h = self.header + else + h = unpack(self.header:render({ })) + end + return "Block<" .. tostring(h) .. "> <- " .. tostring(self.parent) + end, + set = function(self, name, value) + self._state[name] = value + end, + get = function(self, name) + return self._state[name] + end, + get_current = function(self, name) + return rawget(self._state, name) + end, + listen = function(self, name, fn) + self._listeners[name] = fn + end, + unlisten = function(self, name) + self._listeners[name] = nil + end, + send = function(self, name, ...) + do + local fn = self._listeners[name] + if fn then + return fn(self, ...) + end + end + end, + extract_assign_name = function(self, node) + local is_local = false + local real_name + local _exp_0 = mtype(node) + if LocalName == _exp_0 then + is_local = true + real_name = node:get_name(self) + elseif NameProxy == _exp_0 then + real_name = node:get_name(self) + elseif "table" == _exp_0 then + real_name = node[1] == "ref" and node[2] + elseif "string" == _exp_0 then + real_name = node + end + return real_name, is_local + end, + declare = function(self, names) + local undeclared + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + local real_name, is_local = self:extract_assign_name(name) + if not (is_local or real_name and not self:has_name(real_name, true)) then + _continue_0 = true + break + end + self:put_name(real_name) + if self:name_exported(real_name) then + _continue_0 = true + break + end + local _value_0 = real_name + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + undeclared = _accum_0 + end + return undeclared + end, + whitelist_names = function(self, names) + self._name_whitelist = Set(names) + end, + name_exported = function(self, name) + if self.export_all then + return true + end + if self.export_proper and name:match("^%u") then + return true + end + end, + put_name = function(self, name, ...) + local value = ... + if select("#", ...) == 0 then + value = true + end + if NameProxy == mtype(name) then + name = name:get_name(self) + end + self._names[name] = value + end, + has_name = function(self, name, skip_exports) + if not skip_exports and self:name_exported(name) then + return true + end + local yes = self._names[name] + if yes == nil and self.parent then + if not self._name_whitelist or self._name_whitelist[name] then + return self.parent:has_name(name, true) + end + else + return yes + end + end, + is_local = function(self, node) + local t = mtype(node) + if t == "string" then + return self:has_name(node, false) + end + if t == NameProxy or t == LocalName then + return true + end + if t == "table" then + if node[1] == "ref" or (node[1] == "chain" and #node == 2) then + return self:is_local(node[2]) + end + end + return false + end, + free_name = function(self, prefix, dont_put) + prefix = prefix or "moon" + local searching = true + local name, i = nil, 0 + while searching do + name = concat({ + "", + prefix, + i + }, "_") + i = i + 1 + searching = self:has_name(name, true) + end + if not dont_put then + self:put_name(name) + end + return name + end, + init_free_var = function(self, prefix, value) + local name = self:free_name(prefix, true) + self:stm({ + "assign", + { + name + }, + { + value + } + }) + return name + end, + add = function(self, item, pos) + do + local _with_0 = self._lines + _with_0:add(item) + if pos then + _with_0:mark_pos(pos) + end + end + return item + end, + render = function(self, buffer) + buffer:add(self.header) + buffer:mark_pos(self.pos) + if self.next then + buffer:add(self._lines) + self.next:render(buffer) + else + if #self._lines == 0 and "string" == type(buffer[#buffer]) then + buffer[#buffer] = buffer[#buffer] .. (" " .. (unpack(Lines():add(self.footer)))) + else + buffer:add(self._lines) + buffer:add(self.footer) + buffer:mark_pos(self.pos) + end + end + return buffer + end, + block = function(self, header, footer) + return Block(self, header, footer) + end, + line = function(self, ...) + do + local _with_0 = Line() + _with_0:append(...) + return _with_0 + end + end, + is_stm = function(self, node) + return self.statement_compilers[ntype(node)] ~= nil + end, + is_value = function(self, node) + local t = ntype(node) + return self.value_compilers[t] ~= nil or t == "value" + end, + name = function(self, node, ...) + if type(node) == "string" then + return node + else + return self:value(node, ...) + end + end, + value = function(self, node, ...) + node = self.transform.value(node) + local action + if type(node) ~= "table" then + action = "raw_value" + else + action = node[1] + end + local fn = self.value_compilers[action] + if not (fn) then + error({ + "compile-error", + "Failed to find value compiler for: " .. dump.value(node), + node[-1] + }) + end + local out = fn(self, node, ...) + if type(node) == "table" and node[-1] then + if type(out) == "string" then + do + local _with_0 = Line() + _with_0:append(out) + out = _with_0 + end + end + out.pos = node[-1] + end + return out + end, + values = function(self, values, delim) + delim = delim or ', ' + do + local _with_0 = Line() + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #values do + local v = values[_index_0] + _accum_0[_len_0] = self:value(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), delim) + return _with_0 + end + end, + stm = function(self, node, ...) + if not node then + return + end + node = self.transform.statement(node) + local result + do + local fn = self.statement_compilers[ntype(node)] + if fn then + result = fn(self, node, ...) + else + if value_can_be_statement(node) then + result = self:value(node) + else + result = self:stm({ + "assign", + { + "_" + }, + { + node + } + }) + end + end + end + if result then + if type(node) == "table" and type(result) == "table" and node[-1] then + result.pos = node[-1] + end + self:add(result) + end + return nil + end, + stms = function(self, stms, ret) + if ret then + error("deprecated stms call, use transformer") + end + local current_stms, current_stm_i + current_stms, current_stm_i = self.current_stms, self.current_stm_i + self.current_stms = stms + for i = 1, #stms do + self.current_stm_i = i + self:stm(stms[i]) + end + self.current_stms = current_stms + self.current_stm_i = current_stm_i + return nil + end, + splice = function(self, fn) + local lines = { + "lines", + self._lines + } + self._lines = Lines() + return self:stms(fn(lines)) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, parent, header, footer) + self.parent, self.header, self.footer = parent, header, footer + self._lines = Lines() + self._names = { } + self._state = { } + self._listeners = { } + do + self.transform = { + value = transform.Value:bind(self), + statement = transform.Statement:bind(self) + } + end + if self.parent then + self.root = self.parent.root + self.indent = self.parent.indent + 1 + setmetatable(self._state, { + __index = self.parent._state + }) + return setmetatable(self._listeners, { + __index = self.parent._listeners + }) + else + self.indent = 0 + end + end, + __base = _base_0, + __name = "Block" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Block = _class_0 +end +do + local _class_0 + local _parent_0 = Block + local _base_0 = { + __tostring = function(self) + return "RootBlock<>" + end, + root_stms = function(self, stms) + if not (self.options.implicitly_return_root == false) then + stms = transform.Statement.transformers.root_stms(self, stms) + end + return self:stms(stms) + end, + render = function(self) + local buffer = self._lines:flatten() + if buffer[#buffer] == "\n" then + buffer[#buffer] = nil + end + return table.concat(buffer) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, options) + self.options = options + self.root = self + return _class_0.__parent.__init(self) + end, + __base = _base_0, + __name = "RootBlock", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + RootBlock = _class_0 +end +local format_error +format_error = function(msg, pos, file_str) + local line_message + if pos then + local line = pos_to_line(file_str, pos) + local line_str + line_str, line = get_closest_line(file_str, line) + line_str = line_str or "" + line_message = (" [%d] >> %s"):format(line, trim(line_str)) + end + return concat({ + "Compile error: " .. msg, + line_message + }, "\n") +end +local value +value = function(value) + local out = nil + do + local _with_0 = RootBlock() + _with_0:add(_with_0:value(value)) + out = _with_0:render() + end + return out +end +local tree +tree = function(tree, options) + if options == nil then + options = { } + end + assert(tree, "missing tree") + local scope = (options.scope or RootBlock)(options) + local runner = coroutine.create(function() + return scope:root_stms(tree) + end) + local success, err = coroutine.resume(runner) + if not (success) then + local error_msg, error_pos + if type(err) == "table" then + local _exp_0 = err[1] + if "user-error" == _exp_0 or "compile-error" == _exp_0 then + error_msg, error_pos = unpack(err, 2) + else + error_msg, error_pos = error("Unknown error thrown", util.dump(error_msg)) + end + else + error_msg, error_pos = concat({ + err, + debug.traceback(runner) + }, "\n") + end + return nil, error_msg, error_pos or scope.last_pos + end + local lua_code = scope:render() + local posmap = scope._lines:flatten_posmap() + return lua_code, posmap +end +do + local data = require("moonscript.data") + for name, cls in pairs({ + Line = Line, + Lines = Lines, + DelayedLine = DelayedLine + }) do + data[name] = cls + end +end +return { + tree = tree, + value = value, + format_error = format_error, + Block = Block, + RootBlock = RootBlock +} diff --git a/prototypes/scp-clicker/moonscript/compile.moon b/prototypes/scp-clicker/moonscript/compile.moon new file mode 100644 index 0000000..a2b17fe --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile.moon @@ -0,0 +1,485 @@ + +util = require "moonscript.util" +dump = require "moonscript.dump" +transform = require "moonscript.transform" + +import NameProxy, LocalName from require "moonscript.transform.names" +import Set from require "moonscript.data" +import ntype, value_can_be_statement from require "moonscript.types" + +statement_compilers = require "moonscript.compile.statement" +value_compilers = require "moonscript.compile.value" + +import concat, insert from table +import pos_to_line, get_closest_line, trim, unpack from util + +mtype = util.moon.type + +indent_char = " " + +local Line, DelayedLine, Lines, Block, RootBlock + +-- a buffer for building up lines +class Lines + new: => + @posmap = {} + + mark_pos: (pos, line=#@) => + @posmap[line] = pos unless @posmap[line] + + -- append a line or lines to the buffer + add: (item) => + switch mtype item + when Line + item\render self + when Block + item\render self + else -- also captures DelayedLine + @[#@ + 1] = item + @ + + flatten_posmap: (line_no=0, out={}) => + posmap = @posmap + for i, l in ipairs @ + switch mtype l + when "string", DelayedLine + line_no += 1 + out[line_no] = posmap[i] + + line_no += 1 for _ in l\gmatch"\n" + out[line_no] = posmap[i] + when Lines + _, line_no = l\flatten_posmap line_no, out + else + error "Unknown item in Lines: #{l}" + + out, line_no + + flatten: (indent=nil, buffer={}) => + for i = 1, #@ + l = @[i] + t = mtype l + + if t == DelayedLine + l = l\render! + t = "string" + + switch t + when "string" + insert buffer, indent if indent + insert buffer, l + + -- insert breaks between ambiguous statements + if "string" == type @[i + 1] + lc = l\sub(-1) + if (lc == ")" or lc == "]") and @[i + 1]\sub(1,1) == "(" + insert buffer, ";" + + insert buffer, "\n" + when Lines + l\flatten indent and indent .. indent_char or indent_char, buffer + else + error "Unknown item in Lines: #{l}" + buffer + + __tostring: => + -- strip non-array elements + strip = (t) -> + if "table" == type t + [strip v for v in *t] + else + t + + "Lines<#{util.dump(strip @)\sub 1, -2}>" + +-- Buffer for building up a line +-- A plain old table holding either strings or Block objects. +-- Adding a line to a line will cause that line to be merged in. +class Line + pos: nil + + append_list: (items, delim) => + for i = 1,#items + @append items[i] + if i < #items then insert self, delim + nil + + append: (first, ...) => + if Line == mtype first + -- print "appending line to line", first.pos, first + @pos = first.pos unless @pos -- bubble pos if there isn't one + @append value for value in *first + else + insert self, first + + if ... + @append ... + + -- todo: try to remove concats from here + render: (buffer) => + current = {} + + add_current = -> + buffer\add concat current + buffer\mark_pos @pos + + for chunk in *@ + switch mtype chunk + when Block + for block_chunk in *chunk\render Lines! + if "string" == type block_chunk + insert current, block_chunk + else + add_current! + buffer\add block_chunk + current = {} + else + insert current, chunk + + if current[1] + add_current! + + buffer + + __tostring: => + "Line<#{util.dump(@)\sub 1, -2}>" + +class DelayedLine + new: (fn) => + @prepare = fn + + prepare: -> + + render: => + @prepare! + concat @ + +class Block + header: "do" + footer: "end" + + export_all: false + export_proper: false + + value_compilers: value_compilers + statement_compilers: statement_compilers + + __tostring: => + h = if "string" == type @header + @header + else + unpack @header\render {} + + "Block<#{h}> <- " .. tostring @parent + + new: (@parent, @header, @footer) => + @_lines = Lines! + + @_names = {} + @_state = {} + @_listeners = {} + + with transform + @transform = { + value: .Value\bind self + statement: .Statement\bind self + } + + if @parent + @root = @parent.root + @indent = @parent.indent + 1 + setmetatable @_state, { __index: @parent._state } + setmetatable @_listeners, { __index: @parent._listeners } + else + @indent = 0 + + set: (name, value) => + @_state[name] = value + + get: (name) => + @_state[name] + + get_current: (name) => + rawget @_state, name + + listen: (name, fn) => + @_listeners[name] = fn + + unlisten: (name) => + @_listeners[name] = nil + + send: (name, ...) => + if fn = @_listeners[name] + fn self, ... + + extract_assign_name: (node) => + is_local = false + real_name = switch mtype node + when LocalName + is_local = true + node\get_name self + when NameProxy + node\get_name self + when "table" + node[1] == "ref" and node[2] + when "string" + -- TOOD: some legacy transfomers might use string for ref + node + + real_name, is_local + + declare: (names) => + undeclared = for name in *names + real_name, is_local = @extract_assign_name name + continue unless is_local or real_name and not @has_name real_name, true + -- this also puts exported names so they can be assigned a new value in + -- deeper scope + @put_name real_name + continue if @name_exported real_name + real_name + + undeclared + + whitelist_names: (names) => + @_name_whitelist = Set names + + name_exported: (name) => + return true if @export_all + return true if @export_proper and name\match"^%u" + + put_name: (name, ...) => + value = ... + value = true if select("#", ...) == 0 + + name = name\get_name self if NameProxy == mtype name + @_names[name] = value + + -- Check if a name is defined in the current or any enclosing scope + -- skip_exports: ignore names that have been exported using `export` + has_name: (name, skip_exports) => + return true if not skip_exports and @name_exported name + + yes = @_names[name] + if yes == nil and @parent + if not @_name_whitelist or @_name_whitelist[name] + @parent\has_name name, true + else + yes + + is_local: (node) => + t = mtype node + + return @has_name(node, false) if t == "string" + return true if t == NameProxy or t == LocalName + + if t == "table" + if node[1] == "ref" or (node[1] == "chain" and #node == 2) + return @is_local node[2] + + false + + free_name: (prefix, dont_put) => + prefix = prefix or "moon" + searching = true + name, i = nil, 0 + while searching + name = concat {"", prefix, i}, "_" + i = i + 1 + searching = @has_name name, true + + @put_name name if not dont_put + name + + init_free_var: (prefix, value) => + name = @free_name prefix, true + @stm {"assign", {name}, {value}} + name + + -- add something to the line buffer + add: (item, pos) => + with @_lines + \add item + \mark_pos pos if pos + item + + -- todo: pass in buffer as argument + render: (buffer) => + buffer\add @header + buffer\mark_pos @pos + + if @next + buffer\add @_lines + @next\render buffer + else + -- join an empty block into a single line + if #@_lines == 0 and "string" == type buffer[#buffer] + buffer[#buffer] ..= " " .. (unpack Lines!\add @footer) + else + buffer\add @_lines + buffer\add @footer + buffer\mark_pos @pos + + buffer + + block: (header, footer) => + Block self, header, footer + + line: (...) => + with Line! + \append ... + + is_stm: (node) => + @statement_compilers[ntype node] != nil + + is_value: (node) => + t = ntype node + @value_compilers[t] != nil or t == "value" + + -- compile name for assign + name: (node, ...) => + if type(node) == "string" + node + else + @value node, ... + + value: (node, ...) => + node = @transform.value node + action = if type(node) != "table" + "raw_value" + else + node[1] + + fn = @value_compilers[action] + unless fn + error { + "compile-error" + "Failed to find value compiler for: " .. dump.value node + node[-1] + } + + out = fn self, node, ... + + -- store the pos, creating a line if necessary + if type(node) == "table" and node[-1] + if type(out) == "string" + out = with Line! do \append out + out.pos = node[-1] + + out + + values: (values, delim) => + delim = delim or ', ' + with Line! + \append_list [@value v for v in *values], delim + + stm: (node, ...) => + return if not node -- skip blank statements + node = @transform.statement node + + result = if fn = @statement_compilers[ntype(node)] + fn @, node, ... + else + if value_can_be_statement node + @value node + else + -- coerce value into statement + @stm {"assign", {"_"}, {node}} + + if result + if type(node) == "table" and type(result) == "table" and node[-1] + result.pos = node[-1] + @add result + + nil + + stms: (stms, ret) => + error "deprecated stms call, use transformer" if ret + {:current_stms, :current_stm_i} = @ + + @current_stms = stms + for i=1,#stms + @current_stm_i = i + @stm stms[i] + + @current_stms = current_stms + @current_stm_i = current_stm_i + + nil + + -- takes the existing set of lines and replaces them with the result of + -- calling fn on them + splice: (fn) => + lines = {"lines", @_lines} + @_lines = Lines! + @stms fn lines + +class RootBlock extends Block + new: (@options) => + @root = self + super! + + __tostring: => "RootBlock<>" + + root_stms: (stms) => + unless @options.implicitly_return_root == false + stms = transform.Statement.transformers.root_stms self, stms + @stms stms + + render: => + -- print @_lines + buffer = @_lines\flatten! + buffer[#buffer] = nil if buffer[#buffer] == "\n" + table.concat buffer + +format_error = (msg, pos, file_str) -> + line_message = if pos + line = pos_to_line file_str, pos + line_str, line = get_closest_line file_str, line + line_str = line_str or "" + (" [%d] >> %s")\format line, trim line_str + + concat { + "Compile error: "..msg + line_message + }, "\n" + +value = (value) -> + out = nil + with RootBlock! + \add \value value + out = \render! + out + +tree = (tree, options={}) -> + assert tree, "missing tree" + + scope = (options.scope or RootBlock) options + + runner = coroutine.create -> + scope\root_stms tree + + success, err = coroutine.resume runner + + unless success + error_msg, error_pos = if type(err) == "table" + switch err[1] + when "user-error", "compile-error" + unpack err, 2 + else + -- unknown error, bubble it + error "Unknown error thrown", util.dump error_msg + else + concat {err, debug.traceback runner}, "\n" + + return nil, error_msg, error_pos or scope.last_pos + + lua_code = scope\render! + posmap = scope._lines\flatten_posmap! + lua_code, posmap + +-- mmmm +with data = require "moonscript.data" + for name, cls in pairs {:Line, :Lines, :DelayedLine} + data[name] = cls + +{ :tree, :value, :format_error, :Block, :RootBlock } diff --git a/prototypes/scp-clicker/moonscript/compile/statement.lua b/prototypes/scp-clicker/moonscript/compile/statement.lua new file mode 100644 index 0000000..7f1c906 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile/statement.lua @@ -0,0 +1,240 @@ +local ntype +ntype = require("moonscript.types").ntype +local concat, insert +do + local _obj_0 = table + concat, insert = _obj_0.concat, _obj_0.insert +end +local unpack +unpack = require("moonscript.util").unpack +return { + raw = function(self, node) + return self:add(node[2]) + end, + lines = function(self, node) + local _list_0 = node[2] + for _index_0 = 1, #_list_0 do + local line = _list_0[_index_0] + self:add(line) + end + end, + declare = function(self, node) + local names = node[2] + local undeclared = self:declare(names) + if #undeclared > 0 then + do + local _with_0 = self:line("local ") + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #undeclared do + local name = undeclared[_index_0] + _accum_0[_len_0] = self:name(name) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + return _with_0 + end + end + end, + declare_with_shadows = function(self, node) + local names = node[2] + self:declare(names) + do + local _with_0 = self:line("local ") + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + _accum_0[_len_0] = self:name(name) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + return _with_0 + end + end, + assign = function(self, node) + local names, values = unpack(node, 2) + local undeclared = self:declare(names) + local declare = "local " .. concat(undeclared, ", ") + local has_fndef = false + local i = 1 + while i <= #values do + if ntype(values[i]) == "fndef" then + has_fndef = true + end + i = i + 1 + end + do + local _with_0 = self:line() + if #undeclared == #names and not has_fndef then + _with_0:append(declare) + else + if #undeclared > 0 then + self:add(declare, node[-1]) + end + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + _accum_0[_len_0] = self:value(name) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + end + _with_0:append(" = ") + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #values do + local v = values[_index_0] + _accum_0[_len_0] = self:value(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + return _with_0 + end + end, + ["return"] = function(self, node) + return self:line("return ", (function() + if node[2] ~= "" then + return self:value(node[2]) + end + end)()) + end, + ["break"] = function(self, node) + return "break" + end, + ["if"] = function(self, node) + local cond, block = node[2], node[3] + local root + do + local _with_0 = self:block(self:line("if ", self:value(cond), " then")) + _with_0:stms(block) + root = _with_0 + end + local current = root + local add_clause + add_clause = function(clause) + local type = clause[1] + local i = 2 + local next + if type == "else" then + next = self:block("else") + else + i = i + 1 + next = self:block(self:line("elseif ", self:value(clause[2]), " then")) + end + next:stms(clause[i]) + current.next = next + current = next + end + for _index_0 = 4, #node do + local cond = node[_index_0] + add_clause(cond) + end + return root + end, + ["repeat"] = function(self, node) + local cond, block = unpack(node, 2) + do + local _with_0 = self:block("repeat", self:line("until ", self:value(cond))) + _with_0:stms(block) + return _with_0 + end + end, + ["while"] = function(self, node) + local cond, block = unpack(node, 2) + do + local _with_0 = self:block(self:line("while ", self:value(cond), " do")) + _with_0:stms(block) + return _with_0 + end + end, + ["for"] = function(self, node) + local name, bounds, block = unpack(node, 2) + local loop = self:line("for ", self:name(name), " = ", self:value({ + "explist", + unpack(bounds) + }), " do") + do + local _with_0 = self:block(loop) + _with_0:declare({ + name + }) + _with_0:stms(block) + return _with_0 + end + end, + foreach = function(self, node) + local names, exps, block = unpack(node, 2) + local loop + do + local _with_0 = self:line() + _with_0:append("for ") + loop = _with_0 + end + do + local _with_0 = self:block(loop) + loop:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + _accum_0[_len_0] = _with_0:name(name, false) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + loop:append(" in ") + loop:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #exps do + local exp = exps[_index_0] + _accum_0[_len_0] = self:value(exp) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ",") + loop:append(" do") + _with_0:declare(names) + _with_0:stms(block) + return _with_0 + end + end, + export = function(self, node) + local names = unpack(node, 2) + if type(names) == "string" then + if names == "*" then + self.export_all = true + elseif names == "^" then + self.export_proper = true + end + else + self:declare(names) + end + return nil + end, + run = function(self, code) + code:call(self) + return nil + end, + group = function(self, node) + return self:stms(node[2]) + end, + ["do"] = function(self, node) + do + local _with_0 = self:block() + _with_0:stms(node[2]) + return _with_0 + end + end, + noop = function(self) end +} diff --git a/prototypes/scp-clicker/moonscript/compile/statement.moon b/prototypes/scp-clicker/moonscript/compile/statement.moon new file mode 100644 index 0000000..25f53fd --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile/statement.moon @@ -0,0 +1,137 @@ + +import ntype from require "moonscript.types" +import concat, insert from table + +import unpack from require "moonscript.util" + +{ + raw: (node) => @add node[2] + + lines: (node) => + for line in *node[2] + @add line + + declare: (node) => + names = node[2] + undeclared = @declare names + if #undeclared > 0 + with @line "local " + \append_list [@name name for name in *undeclared], ", " + + -- this overrides the existing names with new locals, used for local keyword + declare_with_shadows: (node) => + names = node[2] + @declare names + with @line "local " + \append_list [@name name for name in *names], ", " + + assign: (node) => + names, values = unpack node, 2 + + undeclared = @declare names + declare = "local " .. concat(undeclared, ", ") + + has_fndef = false + i = 1 + while i <= #values + if ntype(values[i]) == "fndef" + has_fndef = true + i = i +1 + + with @line! + if #undeclared == #names and not has_fndef + \append declare + else + @add declare, node[-1] if #undeclared > 0 + \append_list [@value name for name in *names], ", " + + \append " = " + \append_list [@value v for v in *values], ", " + + return: (node) => + @line "return ", if node[2] != "" then @value node[2] + + break: (node) => + "break" + + if: (node) => + cond, block = node[2], node[3] + root = with @block @line "if ", @value(cond), " then" + \stms block + + current = root + add_clause = (clause)-> + type = clause[1] + i = 2 + next = if type == "else" + @block "else" + else + i += 1 + @block @line "elseif ", @value(clause[2]), " then" + + next\stms clause[i] + + current.next = next + current = next + + add_clause cond for cond in *node[4,] + root + + repeat: (node) => + cond, block = unpack node, 2 + with @block "repeat", @line "until ", @value cond + \stms block + + while: (node) => + cond, block = unpack node, 2 + with @block @line "while ", @value(cond), " do" + \stms block + + for: (node) => + name, bounds, block = unpack node, 2 + loop = @line "for ", @name(name), " = ", @value({"explist", unpack bounds}), " do" + with @block loop + \declare {name} + \stms block + + -- for x in y ... + -- {"foreach", {names...}, {exp...}, body} + foreach: (node) => + names, exps, block = unpack node, 2 + + loop = with @line! + \append "for " + + with @block loop + loop\append_list [\name name, false for name in *names], ", " + loop\append " in " + loop\append_list [@value exp for exp in *exps], "," + loop\append " do" + + \declare names + \stms block + + export: (node) => + names = unpack node, 2 + if type(names) == "string" + if names == "*" + @export_all = true + elseif names == "^" + @export_proper = true + else + @declare names + nil + + run: (code) => + code\call self + nil + + group: (node) => + @stms node[2] + + do: (node) => + with @block! + \stms node[2] + + noop: => -- nothing! +} diff --git a/prototypes/scp-clicker/moonscript/compile/value.lua b/prototypes/scp-clicker/moonscript/compile/value.lua new file mode 100644 index 0000000..ce20dad --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile/value.lua @@ -0,0 +1,325 @@ +local util = require("moonscript.util") +local data = require("moonscript.data") +local ntype +ntype = require("moonscript.types").ntype +local user_error +user_error = require("moonscript.errors").user_error +local concat, insert +do + local _obj_0 = table + concat, insert = _obj_0.concat, _obj_0.insert +end +local unpack +unpack = util.unpack +local table_delim = "," +local string_chars = { + ["\r"] = "\\r", + ["\n"] = "\\n" +} +return { + scoped = function(self, node) + local _, before, value, after + _, before, value, after = node[1], node[2], node[3], node[4] + _ = before and before:call(self) + do + local _with_0 = self:value(value) + _ = after and after:call(self) + return _with_0 + end + end, + exp = function(self, node) + local _comp + _comp = function(i, value) + if i % 2 == 1 and value == "!=" then + value = "~=" + end + return self:value(value) + end + do + local _with_0 = self:line() + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for i, v in ipairs(node) do + if i > 1 then + _accum_0[_len_0] = _comp(i, v) + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end)(), " ") + return _with_0 + end + end, + explist = function(self, node) + do + local _with_0 = self:line() + _with_0:append_list((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 2, #node do + local v = node[_index_0] + _accum_0[_len_0] = self:value(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") + return _with_0 + end + end, + parens = function(self, node) + return self:line("(", self:value(node[2]), ")") + end, + string = function(self, node) + local delim, inner = unpack(node, 2) + local end_delim = delim:gsub("%[", "]") + if delim == "'" or delim == '"' then + inner = inner:gsub("[\r\n]", string_chars) + end + return delim .. inner .. end_delim + end, + chain = function(self, node) + local callee = node[2] + local callee_type = ntype(callee) + local item_offset = 3 + if callee_type == "dot" or callee_type == "colon" or callee_type == "index" then + callee = self:get("scope_var") + if not (callee) then + user_error("Short-dot syntax must be called within a with block") + end + item_offset = 2 + end + if callee_type == "ref" and callee[2] == "super" or callee == "super" then + do + local sup = self:get("super") + if sup then + return self:value(sup(self, node)) + end + end + end + local chain_item + chain_item = function(node) + local t, arg = unpack(node) + if t == "call" then + return "(", self:values(arg), ")" + elseif t == "index" then + return "[", self:value(arg), "]" + elseif t == "dot" then + return ".", tostring(arg) + elseif t == "colon" then + return ":", tostring(arg) + elseif t == "colon_stub" then + return user_error("Uncalled colon stub") + else + return error("Unknown chain action: " .. tostring(t)) + end + end + if (callee_type == "self" or callee_type == "self_class") and node[3] and ntype(node[3]) == "call" then + callee[1] = callee_type .. "_colon" + end + local callee_value = self:value(callee) + if ntype(callee) == "exp" then + callee_value = self:line("(", callee_value, ")") + end + local actions + do + local _with_0 = self:line() + for _index_0 = item_offset, #node do + local action = node[_index_0] + _with_0:append(chain_item(action)) + end + actions = _with_0 + end + return self:line(callee_value, actions) + end, + fndef = function(self, node) + local args, whitelist, arrow, block = unpack(node, 2) + local default_args = { } + local self_args = { } + local arg_names + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #args do + local arg = args[_index_0] + local name, default_value = unpack(arg) + if type(name) == "string" then + name = name + else + if name[1] == "self" or name[1] == "self_class" then + insert(self_args, name) + end + name = name[2] + end + if default_value then + insert(default_args, arg) + end + local _value_0 = name + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + arg_names = _accum_0 + end + if arrow == "fat" then + insert(arg_names, 1, "self") + end + do + local _with_0 = self:block() + if #whitelist > 0 then + _with_0:whitelist_names(whitelist) + end + for _index_0 = 1, #arg_names do + local name = arg_names[_index_0] + _with_0:put_name(name) + end + for _index_0 = 1, #default_args do + local default = default_args[_index_0] + local name, value = unpack(default) + if type(name) == "table" then + name = name[2] + end + _with_0:stm({ + 'if', + { + 'exp', + { + "ref", + name + }, + '==', + 'nil' + }, + { + { + 'assign', + { + name + }, + { + value + } + } + } + }) + end + local self_arg_values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self_args do + local arg = self_args[_index_0] + _accum_0[_len_0] = arg[2] + _len_0 = _len_0 + 1 + end + self_arg_values = _accum_0 + end + if #self_args > 0 then + _with_0:stm({ + "assign", + self_args, + self_arg_values + }) + end + _with_0:stms(block) + if #args > #arg_names then + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #args do + local arg = args[_index_0] + _accum_0[_len_0] = arg[1] + _len_0 = _len_0 + 1 + end + arg_names = _accum_0 + end + end + _with_0.header = "function(" .. concat(arg_names, ", ") .. ")" + return _with_0 + end + end, + table = function(self, node) + local items = unpack(node, 2) + do + local _with_0 = self:block("{", "}") + local format_line + format_line = function(tuple) + if #tuple == 2 then + local key, value = unpack(tuple) + if ntype(key) == "key_literal" and data.lua_keywords[key[2]] then + key = { + "string", + '"', + key[2] + } + end + local assign + if ntype(key) == "key_literal" then + assign = key[2] + else + assign = self:line("[", _with_0:value(key), "]") + end + local out = self:line(assign, " = ", _with_0:value(value)) + return out + else + return self:line(_with_0:value(tuple[1])) + end + end + if items then + local count = #items + for i, tuple in ipairs(items) do + local line = format_line(tuple) + if not (count == i) then + line:append(table_delim) + end + _with_0:add(line) + end + end + return _with_0 + end + end, + minus = function(self, node) + return self:line("-", self:value(node[2])) + end, + temp_name = function(self, node, ...) + return node:get_name(self, ...) + end, + number = function(self, node) + return node[2] + end, + bitnot = function(self, node) + return self:line("~", self:value(node[2])) + end, + length = function(self, node) + return self:line("#", self:value(node[2])) + end, + ["not"] = function(self, node) + return self:line("not ", self:value(node[2])) + end, + self = function(self, node) + return "self." .. self:name(node[2]) + end, + self_class = function(self, node) + return "self.__class." .. self:name(node[2]) + end, + self_colon = function(self, node) + return "self:" .. self:name(node[2]) + end, + self_class_colon = function(self, node) + return "self.__class:" .. self:name(node[2]) + end, + ref = function(self, value) + do + local sup = value[2] == "super" and self:get("super") + if sup then + return self:value(sup(self)) + end + end + return tostring(value[2]) + end, + raw_value = function(self, value) + if value == "..." then + self:send("varargs") + end + return tostring(value) + end +} diff --git a/prototypes/scp-clicker/moonscript/compile/value.moon b/prototypes/scp-clicker/moonscript/compile/value.moon new file mode 100644 index 0000000..7cfdda4 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/compile/value.moon @@ -0,0 +1,210 @@ + +util = require "moonscript.util" +data = require "moonscript.data" + +import ntype from require "moonscript.types" +import user_error from require "moonscript.errors" +import concat, insert from table +import unpack from util + +table_delim = "," + +string_chars = { + "\r": "\\r" + "\n": "\\n" +} + +{ + scoped: (node) => + {_, before, value, after} = node + before and before\call @ + with @value value + after and after\call @ + + -- list of values separated by binary operators + exp: (node) => + _comp = (i, value) -> + if i % 2 == 1 and value == "!=" + value = "~=" + @value value + + with @line! + \append_list [_comp i,v for i,v in ipairs node when i > 1], " " + + -- list of expressions separated by commas + explist: (node) => + with @line! + \append_list [@value v for v in *node[2,]], ", " + + parens: (node) => + @line "(", @value(node[2]), ")" + + string: (node) => + delim, inner = unpack node, 2 + end_delim = delim\gsub "%[", "]" + if delim == "'" or delim == '"' + inner = inner\gsub "[\r\n]", string_chars + + delim..inner..end_delim + + chain: (node) => + callee = node[2] + callee_type = ntype callee + item_offset = 3 + + if callee_type == "dot" or callee_type == "colon" or callee_type == "index" + callee = @get "scope_var" + unless callee + user_error "Short-dot syntax must be called within a with block" + item_offset = 2 + + -- TODO: don't use string literals as ref + if callee_type == "ref" and callee[2] == "super" or callee == "super" + if sup = @get "super" + return @value sup self, node + + chain_item = (node) -> + t, arg = unpack node + if t == "call" + -- print arg, util.dump arg + "(", @values(arg), ")" + elseif t == "index" + "[", @value(arg), "]" + elseif t == "dot" + ".", tostring arg + elseif t == "colon" + ":", tostring arg + elseif t == "colon_stub" + user_error "Uncalled colon stub" + else + error "Unknown chain action: #{t}" + + if (callee_type == "self" or callee_type == "self_class") and node[3] and ntype(node[3]) == "call" + callee[1] = callee_type.."_colon" + + callee_value = @value callee + callee_value = @line "(", callee_value, ")" if ntype(callee) == "exp" + + actions = with @line! + \append chain_item action for action in *node[item_offset,] + + @line callee_value, actions + + fndef: (node) => + args, whitelist, arrow, block = unpack node, 2 + + default_args = {} + self_args = {} + arg_names = for arg in *args + name, default_value = unpack arg + name = if type(name) == "string" + name + else + if name[1] == "self" or name[1] == "self_class" + insert self_args, name + name[2] + insert default_args, arg if default_value + name + + if arrow == "fat" + insert arg_names, 1, "self" + + with @block! + if #whitelist > 0 + \whitelist_names whitelist + + \put_name name for name in *arg_names + + for default in *default_args + name, value = unpack default + name = name[2] if type(name) == "table" + \stm { + 'if', {'exp', {"ref", name}, '==', 'nil'}, { + {'assign', {name}, {value}} + } + } + + self_arg_values = [arg[2] for arg in *self_args] + \stm {"assign", self_args, self_arg_values} if #self_args > 0 + + \stms block + + -- inject more args if the block manipulated arguments + -- only varargs bubbling does this currently + if #args > #arg_names -- will only work for simple adjustments + arg_names = [arg[1] for arg in *args] + + .header = "function("..concat(arg_names, ", ")..")" + + table: (node) => + items = unpack node, 2 + with @block "{", "}" + format_line = (tuple) -> + if #tuple == 2 + key, value = unpack tuple + + -- escape keys that are lua keywords + if ntype(key) == "key_literal" and data.lua_keywords[key[2]] + key = {"string", '"', key[2]} + + assign = if ntype(key) == "key_literal" + key[2] + else + @line "[", \value(key), "]" + + out = @line assign, " = ", \value(value) + out + else + @line \value tuple[1] + + if items + count = #items + for i, tuple in ipairs items + line = format_line tuple + line\append table_delim unless count == i + \add line + + minus: (node) => + @line "-", @value node[2] + + temp_name: (node, ...) => + node\get_name self, ... + + number: (node) => + node[2] + + bitnot: (node) => + @line "~", @value node[2] + + length: (node) => + @line "#", @value node[2] + + not: (node) => + @line "not ", @value node[2] + + self: (node) => + "self."..@name node[2] + + self_class: (node) => + "self.__class."..@name node[2] + + self_colon: (node) => + "self:"..@name node[2] + + self_class_colon: (node) => + "self.__class:"..@name node[2] + + -- a variable reference + ref: (value) => + if sup = value[2] == "super" and @get "super" + return @value sup @ + + tostring value[2] + + -- catch all pure string values + raw_value: (value) => + if value == "..." + @send "varargs" + + tostring value +} diff --git a/prototypes/scp-clicker/moonscript/data.lua b/prototypes/scp-clicker/moonscript/data.lua new file mode 100644 index 0000000..f33f708 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/data.lua @@ -0,0 +1,83 @@ +local concat, remove, insert +do + local _obj_0 = table + concat, remove, insert = _obj_0.concat, _obj_0.remove, _obj_0.insert +end +local Set +Set = function(items) + local _tbl_0 = { } + for _index_0 = 1, #items do + local k = items[_index_0] + _tbl_0[k] = true + end + return _tbl_0 +end +local Stack +do + local _class_0 + local _base_0 = { + __tostring = function(self) + return "" + end, + pop = function(self) + return remove(self) + end, + push = function(self, value, ...) + insert(self, value) + if ... then + return self:push(...) + else + return value + end + end, + top = function(self) + return self[#self] + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, ...) + self:push(...) + return nil + end, + __base = _base_0, + __name = "Stack" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Stack = _class_0 +end +local lua_keywords = Set({ + 'and', + 'break', + 'do', + 'else', + 'elseif', + 'end', + 'false', + 'for', + 'function', + 'if', + 'in', + 'local', + 'nil', + 'not', + 'or', + 'repeat', + 'return', + 'then', + 'true', + 'until', + 'while' +}) +return { + Set = Set, + Stack = Stack, + lua_keywords = lua_keywords +} diff --git a/prototypes/scp-clicker/moonscript/data.moon b/prototypes/scp-clicker/moonscript/data.moon new file mode 100644 index 0000000..4bb9b4f --- /dev/null +++ b/prototypes/scp-clicker/moonscript/data.moon @@ -0,0 +1,37 @@ +-- data structures & static data + +import concat, remove, insert from table + +Set = (items) -> {k,true for k in *items} + +class Stack + __tostring: => "" + + new: (...) => + @push ... + nil + + pop: => + remove @ + + push: (value, ...) => + insert @, value + if ... + @push ... + else + value + + top: => + self[#self] + + +lua_keywords = Set { + 'and', 'break', 'do', 'else', 'elseif', + 'end', 'false', 'for', 'function', 'if', + 'in', 'local', 'nil', 'not', 'or', + 'repeat', 'return', 'then', 'true', + 'until', 'while' +} + +{ :Set, :Stack, :lua_keywords } + diff --git a/prototypes/scp-clicker/moonscript/dump.lua b/prototypes/scp-clicker/moonscript/dump.lua new file mode 100644 index 0000000..1496ed4 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/dump.lua @@ -0,0 +1,41 @@ +local flat_value +flat_value = function(op, depth) + if depth == nil then + depth = 1 + end + if type(op) == "string" then + return '"' .. op .. '"' + end + if type(op) ~= "table" then + return tostring(op) + end + local items + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #op do + local item = op[_index_0] + _accum_0[_len_0] = flat_value(item, depth + 1) + _len_0 = _len_0 + 1 + end + items = _accum_0 + end + local pos = op[-1] + return "{" .. (pos and "[" .. pos .. "] " or "") .. table.concat(items, ", ") .. "}" +end +local value +value = function(op) + return flat_value(op) +end +local tree +tree = function(block) + local _list_0 = block + for _index_0 = 1, #_list_0 do + local value = _list_0[_index_0] + print(flat_value(value)) + end +end +return { + value = value, + tree = tree +} diff --git a/prototypes/scp-clicker/moonscript/dump.moon b/prototypes/scp-clicker/moonscript/dump.moon new file mode 100644 index 0000000..a6373da --- /dev/null +++ b/prototypes/scp-clicker/moonscript/dump.moon @@ -0,0 +1,18 @@ + +flat_value = (op, depth=1) -> + return '"'..op..'"' if type(op) == "string" + return tostring(op) if type(op) != "table" + + items = [flat_value item, depth + 1 for item in *op] + pos = op[-1] + + "{"..(pos and "["..pos.."] " or "")..table.concat(items, ", ").."}" + +value = (op) -> + flat_value op + +tree = (block) -> + print flat_value value for value in *block + +{ :value, :tree } + diff --git a/prototypes/scp-clicker/moonscript/errors.lua b/prototypes/scp-clicker/moonscript/errors.lua new file mode 100644 index 0000000..d85c316 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/errors.lua @@ -0,0 +1,117 @@ +local util = require("moonscript.util") +local lpeg = require("lpeg") +local concat, insert +do + local _obj_0 = table + concat, insert = _obj_0.concat, _obj_0.insert +end +local split, pos_to_line +split, pos_to_line = util.split, util.pos_to_line +local user_error +user_error = function(...) + return error({ + "user-error", + ... + }) +end +local lookup_line +lookup_line = function(fname, pos, cache) + if not cache[fname] then + do + local _with_0 = assert(io.open(fname)) + cache[fname] = _with_0:read("*a") + _with_0:close() + end + end + return pos_to_line(cache[fname], pos) +end +local reverse_line_number +reverse_line_number = function(fname, line_table, line_num, cache) + for i = line_num, 0, -1 do + if line_table[i] then + return lookup_line(fname, line_table[i], cache) + end + end + return "unknown" +end +local truncate_traceback +truncate_traceback = function(traceback, chunk_func) + if chunk_func == nil then + chunk_func = "moonscript_chunk" + end + traceback = split(traceback, "\n") + local stop = #traceback + while stop > 1 do + if traceback[stop]:match(chunk_func) then + break + end + stop = stop - 1 + end + do + local _accum_0 = { } + local _len_0 = 1 + local _max_0 = stop + for _index_0 = 1, _max_0 < 0 and #traceback + _max_0 or _max_0 do + local t = traceback[_index_0] + _accum_0[_len_0] = t + _len_0 = _len_0 + 1 + end + traceback = _accum_0 + end + local rep = "function '" .. chunk_func .. "'" + traceback[#traceback] = traceback[#traceback]:gsub(rep, "main chunk") + return concat(traceback, "\n") +end +local rewrite_traceback +rewrite_traceback = function(text, err) + local line_tables = require("moonscript.line_tables") + local V, S, Ct, C + V, S, Ct, C = lpeg.V, lpeg.S, lpeg.Ct, lpeg.C + local header_text = "stack traceback:" + local Header, Line = V("Header"), V("Line") + local Break = lpeg.S("\n") + local g = lpeg.P({ + Header, + Header = header_text * Break * Ct(Line ^ 1), + Line = "\t" * C((1 - Break) ^ 0) * (Break + -1) + }) + local cache = { } + local rewrite_single + rewrite_single = function(trace) + local fname, line, msg = trace:match('^(.-):(%d+): (.*)$') + local tbl = line_tables["@" .. tostring(fname)] + if fname and tbl then + return concat({ + fname, + ":", + reverse_line_number(fname, tbl, line, cache), + ": ", + "(", + line, + ") ", + msg + }) + else + return trace + end + end + err = rewrite_single(err) + local match = g:match(text) + if not (match) then + return nil + end + for i, trace in ipairs(match) do + match[i] = rewrite_single(trace) + end + return concat({ + "moon: " .. err, + header_text, + "\t" .. concat(match, "\n\t") + }, "\n") +end +return { + rewrite_traceback = rewrite_traceback, + truncate_traceback = truncate_traceback, + user_error = user_error, + reverse_line_number = reverse_line_number +} diff --git a/prototypes/scp-clicker/moonscript/errors.moon b/prototypes/scp-clicker/moonscript/errors.moon new file mode 100644 index 0000000..b04c567 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/errors.moon @@ -0,0 +1,88 @@ + +util = require "moonscript.util" + +lpeg = require "lpeg" + +import concat, insert from table +import split, pos_to_line from util + +user_error = (...) -> + error {"user-error", ...} + +-- find the line number of `pos` chars into fname +lookup_line = (fname, pos, cache) -> + if not cache[fname] + with assert io.open(fname) + cache[fname] = \read "*a" + \close! + pos_to_line cache[fname], pos + +-- reverse the line number in fname using line_table +reverse_line_number = (fname, line_table, line_num, cache) -> + for i = line_num,0,-1 + if line_table[i] + return lookup_line fname, line_table[i], cache + "unknown" + + +-- strip traceback lines up to chunk func +-- replace reference to chunk_func with "main chunk" +truncate_traceback = (traceback, chunk_func="moonscript_chunk") -> + traceback = split traceback, "\n" + stop = #traceback + while stop > 1 + break if traceback[stop]\match chunk_func + stop -= 1 + + traceback = [t for t in *traceback[1,stop]] + + rep = "function '" .. chunk_func .. "'" + traceback[#traceback] = traceback[#traceback]\gsub rep, "main chunk" + + concat traceback, "\n" + +rewrite_traceback = (text, err) -> + line_tables = require "moonscript.line_tables" + import V, S, Ct, C from lpeg + header_text = "stack traceback:" + + Header, Line = V("Header"), V("Line") + Break = lpeg.S "\n" + g = lpeg.P { + Header + Header: header_text * Break * Ct(Line^1) + Line: "\t" * C((1 - Break)^0) * (Break + -1) + } + + cache = {} -- loaded file cache + rewrite_single = (trace) -> + fname, line, msg = trace\match '^(.-):(%d+): (.*)$' + tbl = line_tables["@#{fname}"] + if fname and tbl + concat { + fname, ":" + reverse_line_number(fname, tbl, line, cache) + ": " + "(", line, ") " + msg + } + else + trace + + err = rewrite_single err + match = g\match text + + return nil unless match + + for i, trace in ipairs match + match[i] = rewrite_single trace + + concat { + "moon: " .. err + header_text + "\t" .. concat match, "\n\t" + }, "\n" + + +{ :rewrite_traceback, :truncate_traceback, :user_error, :reverse_line_number } + diff --git a/prototypes/scp-clicker/moonscript/init.lua b/prototypes/scp-clicker/moonscript/init.lua new file mode 100644 index 0000000..6de5f12 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/init.lua @@ -0,0 +1,5 @@ +do + local _with_0 = require("moonscript.base") + _with_0.insert_loader() + return _with_0 +end diff --git a/prototypes/scp-clicker/moonscript/init.moon b/prototypes/scp-clicker/moonscript/init.moon new file mode 100644 index 0000000..396686d --- /dev/null +++ b/prototypes/scp-clicker/moonscript/init.moon @@ -0,0 +1,2 @@ +with require "moonscript.base" + .insert_loader! diff --git a/prototypes/scp-clicker/moonscript/line_tables.lua b/prototypes/scp-clicker/moonscript/line_tables.lua new file mode 100644 index 0000000..61388ec --- /dev/null +++ b/prototypes/scp-clicker/moonscript/line_tables.lua @@ -0,0 +1 @@ +return { } diff --git a/prototypes/scp-clicker/moonscript/line_tables.moon b/prototypes/scp-clicker/moonscript/line_tables.moon new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/line_tables.moon @@ -0,0 +1 @@ +{} diff --git a/prototypes/scp-clicker/moonscript/parse.lua b/prototypes/scp-clicker/moonscript/parse.lua new file mode 100644 index 0000000..da6731e --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse.lua @@ -0,0 +1,245 @@ +local debug_grammar = false +local lpeg = require("lpeg") +lpeg.setmaxstack(10000) +local err_msg = "Failed to parse:%s\n [%d] >> %s" +local Stack +Stack = require("moonscript.data").Stack +local trim, pos_to_line, get_line +do + local _obj_0 = require("moonscript.util") + trim, pos_to_line, get_line = _obj_0.trim, _obj_0.pos_to_line, _obj_0.get_line +end +local unpack +unpack = require("moonscript.util").unpack +local wrap_env +wrap_env = require("moonscript.parse.env").wrap_env +local R, S, V, P, C, Ct, Cmt, Cg, Cb, Cc +R, S, V, P, C, Ct, Cmt, Cg, Cb, Cc = lpeg.R, lpeg.S, lpeg.V, lpeg.P, lpeg.C, lpeg.Ct, lpeg.Cmt, lpeg.Cg, lpeg.Cb, lpeg.Cc +local White, Break, Stop, Comment, Space, SomeSpace, SpaceBreak, EmptyLine, AlphaNum, Num, Shebang, L, _Name +do + local _obj_0 = require("moonscript.parse.literals") + White, Break, Stop, Comment, Space, SomeSpace, SpaceBreak, EmptyLine, AlphaNum, Num, Shebang, L, _Name = _obj_0.White, _obj_0.Break, _obj_0.Stop, _obj_0.Comment, _obj_0.Space, _obj_0.SomeSpace, _obj_0.SpaceBreak, _obj_0.EmptyLine, _obj_0.AlphaNum, _obj_0.Num, _obj_0.Shebang, _obj_0.L, _obj_0.Name +end +local SpaceName = Space * _Name +Num = Space * (Num / function(v) + return { + "number", + v + } +end) +local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign, got +do + local _obj_0 = require("moonscript.parse.util") + Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign, got = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign, _obj_0.got +end +local build_grammar = wrap_env(debug_grammar, function(root) + local _indent = Stack(0) + local _do_stack = Stack(0) + local state = { + last_pos = 0 + } + local check_indent + check_indent = function(str, pos, indent) + state.last_pos = pos + return _indent:top() == indent + end + local advance_indent + advance_indent = function(str, pos, indent) + local top = _indent:top() + if top ~= -1 and indent > top then + _indent:push(indent) + return true + end + end + local push_indent + push_indent = function(str, pos, indent) + _indent:push(indent) + return true + end + local pop_indent + pop_indent = function() + assert(_indent:pop(), "unexpected outdent") + return true + end + local check_do + check_do = function(str, pos, do_node) + local top = _do_stack:top() + if top == nil or top then + return true, do_node + end + return false + end + local disable_do + disable_do = function() + _do_stack:push(false) + return true + end + local pop_do + pop_do = function() + assert(_do_stack:pop() ~= nil, "unexpected do pop") + return true + end + local DisableDo = Cmt("", disable_do) + local PopDo = Cmt("", pop_do) + local keywords = { } + local key + key = function(chars) + keywords[chars] = true + return Space * chars * -AlphaNum + end + local op + op = function(chars) + local patt = Space * C(chars) + if chars:match("^%w*$") then + keywords[chars] = true + patt = patt * -AlphaNum + end + return patt + end + local Name = Cmt(SpaceName, function(str, pos, name) + if keywords[name] then + return false + end + return true + end) / trim + local SelfName = Space * "@" * ("@" * (_Name / mark("self_class") + Cc("self.__class")) + _Name / mark("self") + Cc("self")) + local KeyName = SelfName + Space * _Name / mark("key_literal") + local VarArg = Space * P("...") / trim + local g = P({ + root or File, + File = Shebang ^ -1 * (Block + Ct("")), + Block = Ct(Line * (Break ^ 1 * Line) ^ 0), + CheckIndent = Cmt(Indent, check_indent), + Line = (CheckIndent * Statement + Space * L(Stop)), + Statement = pos(Import + While + With + For + ForEach + Switch + Return + Local + Export + BreakLoop + Ct(ExpList) * (Update + Assign) ^ -1 / format_assign) * Space * ((key("if") * Exp * (key("else") * Exp) ^ -1 * Space / mark("if") + key("unless") * Exp / mark("unless") + CompInner / mark("comprehension")) * Space) ^ -1 / wrap_decorator, + Body = Space ^ -1 * Break * EmptyLine ^ 0 * InBlock + Ct(Statement), + Advance = L(Cmt(Indent, advance_indent)), + PushIndent = Cmt(Indent, push_indent), + PreventIndent = Cmt(Cc(-1), push_indent), + PopIndent = Cmt("", pop_indent), + InBlock = Advance * Block * PopIndent, + Local = key("local") * ((op("*") + op("^")) / mark("declare_glob") + Ct(NameList) / mark("declare_with_shadows")), + Import = key("import") * Ct(ImportNameList) * SpaceBreak ^ 0 * key("from") * Exp / mark("import"), + ImportName = (sym("\\") * Ct(Cc("colon") * Name) + Name), + ImportNameList = SpaceBreak ^ 0 * ImportName * ((SpaceBreak ^ 1 + sym(",") * SpaceBreak ^ 0) * ImportName) ^ 0, + BreakLoop = Ct(key("break") / trim) + Ct(key("continue") / trim), + Return = key("return") * (ExpListLow / mark("explist") + C("")) / mark("return"), + WithExp = Ct(ExpList) * Assign ^ -1 / format_assign, + With = key("with") * DisableDo * ensure(WithExp, PopDo) * key("do") ^ -1 * Body / mark("with"), + Switch = key("switch") * DisableDo * ensure(Exp, PopDo) * key("do") ^ -1 * Space ^ -1 * Break * SwitchBlock / mark("switch"), + SwitchBlock = EmptyLine ^ 0 * Advance * Ct(SwitchCase * (Break ^ 1 * SwitchCase) ^ 0 * (Break ^ 1 * SwitchElse) ^ -1) * PopIndent, + SwitchCase = key("when") * Ct(ExpList) * key("then") ^ -1 * Body / mark("case"), + SwitchElse = key("else") * Body / mark("else"), + IfCond = Exp * Assign ^ -1 / format_single_assign, + IfElse = (Break * EmptyLine ^ 0 * CheckIndent) ^ -1 * key("else") * Body / mark("else"), + IfElseIf = (Break * EmptyLine ^ 0 * CheckIndent) ^ -1 * key("elseif") * pos(IfCond) * key("then") ^ -1 * Body / mark("elseif"), + If = key("if") * IfCond * key("then") ^ -1 * Body * IfElseIf ^ 0 * IfElse ^ -1 / mark("if"), + Unless = key("unless") * IfCond * key("then") ^ -1 * Body * IfElseIf ^ 0 * IfElse ^ -1 / mark("unless"), + While = key("while") * DisableDo * ensure(Exp, PopDo) * key("do") ^ -1 * Body / mark("while"), + For = key("for") * DisableDo * ensure(Name * sym("=") * Ct(Exp * sym(",") * Exp * (sym(",") * Exp) ^ -1), PopDo) * key("do") ^ -1 * Body / mark("for"), + ForEach = key("for") * Ct(AssignableNameList) * key("in") * DisableDo * ensure(Ct(sym("*") * Exp / mark("unpack") + ExpList), PopDo) * key("do") ^ -1 * Body / mark("foreach"), + Do = key("do") * Body / mark("do"), + Comprehension = sym("[") * Exp * CompInner * sym("]") / mark("comprehension"), + TblComprehension = sym("{") * Ct(Exp * (sym(",") * Exp) ^ -1) * CompInner * sym("}") / mark("tblcomprehension"), + CompInner = Ct((CompForEach + CompFor) * CompClause ^ 0), + CompForEach = key("for") * Ct(AssignableNameList) * key("in") * (sym("*") * Exp / mark("unpack") + Exp) / mark("foreach"), + CompFor = key("for" * Name * sym("=") * Ct(Exp * sym(",") * Exp * (sym(",") * Exp) ^ -1) / mark("for")), + CompClause = CompFor + CompForEach + key("when") * Exp / mark("when"), + Assign = sym("=") * (Ct(With + If + Switch) + Ct(TableBlock + ExpListLow)) / mark("assign"), + Update = ((sym("..=") + sym("+=") + sym("-=") + sym("*=") + sym("/=") + sym("%=") + sym("or=") + sym("and=") + sym("&=") + sym("|=") + sym(">>=") + sym("<<=")) / trim) * Exp / mark("update"), + CharOperators = Space * C(S("+-*/%^><|&")), + WordOperators = op("or") + op("and") + op("<=") + op(">=") + op("~=") + op("!=") + op("==") + op("..") + op("<<") + op(">>") + op("//"), + BinaryOperator = (WordOperators + CharOperators) * SpaceBreak ^ 0, + Assignable = Cmt(Chain, check_assignable) + Name + SelfName, + Exp = Ct(Value * (BinaryOperator * Value) ^ 0) / flatten_or_mark("exp"), + SimpleValue = If + Unless + Switch + With + ClassDecl + ForEach + For + While + Cmt(Do, check_do) + sym("-") * -SomeSpace * Exp / mark("minus") + sym("#") * Exp / mark("length") + sym("~") * Exp / mark("bitnot") + key("not") * Exp / mark("not") + TblComprehension + TableLit + Comprehension + FunLit + Num, + ChainValue = (Chain + Callable) * Ct(InvokeArgs ^ -1) / join_chain, + Value = pos(SimpleValue + Ct(KeyValueList) / mark("table") + ChainValue + String), + SliceValue = Exp, + String = Space * DoubleString + Space * SingleString + LuaString, + SingleString = simple_string("'"), + DoubleString = simple_string('"', true), + LuaString = Cg(LuaStringOpen, "string_open") * Cb("string_open") * Break ^ -1 * C((1 - Cmt(C(LuaStringClose) * Cb("string_open"), check_lua_string)) ^ 0) * LuaStringClose / mark("string"), + LuaStringOpen = sym("[") * P("=") ^ 0 * "[" / trim, + LuaStringClose = "]" * P("=") ^ 0 * "]", + Callable = pos(Name / mark("ref")) + SelfName + VarArg + Parens / mark("parens"), + Parens = sym("(") * SpaceBreak ^ 0 * Exp * SpaceBreak ^ 0 * sym(")"), + FnArgs = symx("(") * SpaceBreak ^ 0 * Ct(FnArgsExpList ^ -1) * SpaceBreak ^ 0 * sym(")") + sym("!") * -P("=") * Ct(""), + FnArgsExpList = Exp * ((Break + sym(",")) * White * Exp) ^ 0, + Chain = (Callable + String + -S(".\\")) * ChainItems / mark("chain") + Space * (DotChainItem * ChainItems ^ -1 + ColonChain) / mark("chain"), + ChainItems = ChainItem ^ 1 * ColonChain ^ -1 + ColonChain, + ChainItem = Invoke + DotChainItem + Slice + symx("[") * Exp / mark("index") * sym("]"), + DotChainItem = symx(".") * _Name / mark("dot"), + ColonChainItem = symx("\\") * _Name / mark("colon"), + ColonChain = ColonChainItem * (Invoke * ChainItems ^ -1) ^ -1, + Slice = symx("[") * (SliceValue + Cc(1)) * sym(",") * (SliceValue + Cc("")) * (sym(",") * SliceValue) ^ -1 * sym("]") / mark("slice"), + Invoke = FnArgs / mark("call") + SingleString / wrap_func_arg + DoubleString / wrap_func_arg + L(P("[")) * LuaString / wrap_func_arg, + TableValue = KeyValue + Ct(Exp), + TableLit = sym("{") * Ct(TableValueList ^ -1 * sym(",") ^ -1 * (SpaceBreak * TableLitLine * (sym(",") ^ -1 * SpaceBreak * TableLitLine) ^ 0 * sym(",") ^ -1) ^ -1) * White * sym("}") / mark("table"), + TableValueList = TableValue * (sym(",") * TableValue) ^ 0, + TableLitLine = PushIndent * ((TableValueList * PopIndent) + (PopIndent * Cut)) + Space, + TableBlockInner = Ct(KeyValueLine * (SpaceBreak ^ 1 * KeyValueLine) ^ 0), + TableBlock = SpaceBreak ^ 1 * Advance * ensure(TableBlockInner, PopIndent) / mark("table"), + ClassDecl = key("class") * -P(":") * (Assignable + Cc(nil)) * (key("extends") * PreventIndent * ensure(Exp, PopIndent) + C("")) ^ -1 * (ClassBlock + Ct("")) / mark("class"), + ClassBlock = SpaceBreak ^ 1 * Advance * Ct(ClassLine * (SpaceBreak ^ 1 * ClassLine) ^ 0) * PopIndent, + ClassLine = CheckIndent * ((KeyValueList / mark("props") + Statement / mark("stm") + Exp / mark("stm")) * sym(",") ^ -1), + Export = key("export") * (Cc("class") * ClassDecl + op("*") + op("^") + Ct(NameList) * (sym("=") * Ct(ExpListLow)) ^ -1) / mark("export"), + KeyValue = (sym(":") * -SomeSpace * Name * lpeg.Cp()) / self_assign + Ct((KeyName + sym("[") * Exp * sym("]") + Space * DoubleString + Space * SingleString) * symx(":") * (Exp + TableBlock + SpaceBreak ^ 1 * Exp)), + KeyValueList = KeyValue * (sym(",") * KeyValue) ^ 0, + KeyValueLine = CheckIndent * KeyValueList * sym(",") ^ -1, + FnArgsDef = sym("(") * White * Ct(FnArgDefList ^ -1) * (key("using") * Ct(NameList + Space * "nil") + Ct("")) * White * sym(")") + Ct("") * Ct(""), + FnArgDefList = FnArgDef * ((sym(",") + Break) * White * FnArgDef) ^ 0 * ((sym(",") + Break) * White * Ct(VarArg)) ^ 0 + Ct(VarArg), + FnArgDef = Ct((Name + SelfName) * (sym("=") * Exp) ^ -1), + FunLit = FnArgsDef * (sym("->") * Cc("slim") + sym("=>") * Cc("fat")) * (Body + Ct("")) / mark("fndef"), + NameList = Name * (sym(",") * Name) ^ 0, + NameOrDestructure = Name + TableLit, + AssignableNameList = NameOrDestructure * (sym(",") * NameOrDestructure) ^ 0, + ExpList = Exp * (sym(",") * Exp) ^ 0, + ExpListLow = Exp * ((sym(",") + sym(";")) * Exp) ^ 0, + InvokeArgs = -P("-") * (ExpList * (sym(",") * (TableBlock + SpaceBreak * Advance * ArgBlock * TableBlock ^ -1) + TableBlock) ^ -1 + TableBlock), + ArgBlock = ArgLine * (sym(",") * SpaceBreak * ArgLine) ^ 0 * PopIndent, + ArgLine = CheckIndent * ExpList + }) + return g, state +end) +local file_parser +file_parser = function() + local g, state = build_grammar() + local file_grammar = White * g * White * -1 + return { + match = function(self, str) + local tree + local _, err = xpcall((function() + tree = file_grammar:match(str) + end), function(err) + return debug.traceback(err, 2) + end) + if type(err) == "string" then + return nil, err + end + if not (tree) then + local msg + local err_pos = state.last_pos + if err then + local node + node, msg = unpack(err) + if msg then + msg = " " .. msg + end + err_pos = node[-1] + end + local line_no = pos_to_line(str, err_pos) + local line_str = get_line(str, line_no) or "" + return nil, err_msg:format(msg or "", line_no, trim(line_str)) + end + return tree + end + } +end +return { + extract_line = extract_line, + build_grammar = build_grammar, + string = function(str) + return file_parser():match(str) + end +} diff --git a/prototypes/scp-clicker/moonscript/parse.moon b/prototypes/scp-clicker/moonscript/parse.moon new file mode 100644 index 0000000..7fd5de6 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse.moon @@ -0,0 +1,361 @@ +debug_grammar = false +lpeg = require "lpeg" + +lpeg.setmaxstack 10000 -- whoa + +err_msg = "Failed to parse:%s\n [%d] >> %s" + +import Stack from require "moonscript.data" +import trim, pos_to_line, get_line from require "moonscript.util" +import unpack from require "moonscript.util" +import wrap_env from require "moonscript.parse.env" + +{ + :R, :S, :V, :P, :C, :Ct, :Cmt, :Cg, :Cb, :Cc +} = lpeg + +{ + :White, :Break, :Stop, :Comment, :Space, :SomeSpace, :SpaceBreak, :EmptyLine, + :AlphaNum, :Num, :Shebang, :L + Name: _Name +} = require "moonscript.parse.literals" + +SpaceName = Space * _Name +Num = Space * (Num / (v) -> {"number", v}) + +{ + :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, + :is_assignable, :check_assignable, :format_assign, :format_single_assign, + :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, + :wrap_decorator, :check_lua_string, :self_assign, :got + +} = require "moonscript.parse.util" + + +build_grammar = wrap_env debug_grammar, (root) -> + _indent = Stack 0 + _do_stack = Stack 0 + + state = { + -- last pos we saw, used to report error location + last_pos: 0 + } + + check_indent = (str, pos, indent) -> + state.last_pos = pos + _indent\top! == indent + + advance_indent = (str, pos, indent) -> + top = _indent\top! + if top != -1 and indent > top + _indent\push indent + true + + push_indent = (str, pos, indent) -> + _indent\push indent + true + + pop_indent = -> + assert _indent\pop!, "unexpected outdent" + true + + check_do = (str, pos, do_node) -> + top = _do_stack\top! + if top == nil or top + return true, do_node + false + + disable_do = -> + _do_stack\push false + true + + pop_do = -> + assert _do_stack\pop! != nil, "unexpected do pop" + true + + DisableDo = Cmt "", disable_do + PopDo = Cmt "", pop_do + + keywords = {} + key = (chars) -> + keywords[chars] = true + Space * chars * -AlphaNum + + op = (chars) -> + patt = Space * C chars + -- it's a word, treat like keyword + if chars\match "^%w*$" + keywords[chars] = true + patt *= -AlphaNum + + patt + + Name = Cmt(SpaceName, (str, pos, name) -> + return false if keywords[name] + true + ) / trim + + SelfName = Space * "@" * ( + "@" * (_Name / mark"self_class" + Cc"self.__class") + + _Name / mark"self" + + Cc"self" -- @ by itself + ) + + KeyName = SelfName + Space * _Name / mark"key_literal" + VarArg = Space * P"..." / trim + + g = P { + root or File + File: Shebang^-1 * (Block + Ct"") + Block: Ct(Line * (Break^1 * Line)^0) + CheckIndent: Cmt(Indent, check_indent), -- validates line is in correct indent + Line: (CheckIndent * Statement + Space * L(Stop)) + + Statement: pos( + Import + While + With + For + ForEach + Switch + Return + + Local + Export + BreakLoop + + Ct(ExpList) * (Update + Assign)^-1 / format_assign + ) * Space * (( + -- statement decorators + key"if" * Exp * (key"else" * Exp)^-1 * Space / mark"if" + + key"unless" * Exp / mark"unless" + + CompInner / mark"comprehension" + ) * Space)^-1 / wrap_decorator + + Body: Space^-1 * Break * EmptyLine^0 * InBlock + Ct(Statement) -- either a statement, or an indented block + + Advance: L Cmt(Indent, advance_indent) -- Advances the indent, gives back whitespace for CheckIndent + PushIndent: Cmt(Indent, push_indent) + PreventIndent: Cmt(Cc(-1), push_indent) + PopIndent: Cmt("", pop_indent) + InBlock: Advance * Block * PopIndent + + Local: key"local" * ((op"*" + op"^") / mark"declare_glob" + Ct(NameList) / mark"declare_with_shadows") + + Import: key"import" * Ct(ImportNameList) * SpaceBreak^0 * key"from" * Exp / mark"import" + ImportName: (sym"\\" * Ct(Cc"colon" * Name) + Name) + ImportNameList: SpaceBreak^0 * ImportName * ((SpaceBreak^1 + sym"," * SpaceBreak^0) * ImportName)^0 + + BreakLoop: Ct(key"break"/trim) + Ct(key"continue"/trim) + + Return: key"return" * (ExpListLow/mark"explist" + C"") / mark"return" + + WithExp: Ct(ExpList) * Assign^-1 / format_assign + With: key"with" * DisableDo * ensure(WithExp, PopDo) * key"do"^-1 * Body / mark"with" + + Switch: key"switch" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Space^-1 * Break * SwitchBlock / mark"switch" + + SwitchBlock: EmptyLine^0 * Advance * Ct(SwitchCase * (Break^1 * SwitchCase)^0 * (Break^1 * SwitchElse)^-1) * PopIndent + SwitchCase: key"when" * Ct(ExpList) * key"then"^-1 * Body / mark"case" + SwitchElse: key"else" * Body / mark"else" + + IfCond: Exp * Assign^-1 / format_single_assign + + IfElse: (Break * EmptyLine^0 * CheckIndent)^-1 * key"else" * Body / mark"else" + IfElseIf: (Break * EmptyLine^0 * CheckIndent)^-1 * key"elseif" * pos(IfCond) * key"then"^-1 * Body / mark"elseif" + + If: key"if" * IfCond * key"then"^-1 * Body * IfElseIf^0 * IfElse^-1 / mark"if" + Unless: key"unless" * IfCond * key"then"^-1 * Body * IfElseIf^0 * IfElse^-1 / mark"unless" + + While: key"while" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Body / mark"while" + + For: key"for" * DisableDo * ensure(Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1), PopDo) * + key"do"^-1 * Body / mark"for" + + ForEach: key"for" * Ct(AssignableNameList) * key"in" * DisableDo * ensure(Ct(sym"*" * Exp / mark"unpack" + ExpList), PopDo) * key"do"^-1 * Body / mark"foreach" + + Do: key"do" * Body / mark"do" + + Comprehension: sym"[" * Exp * CompInner * sym"]" / mark"comprehension" + + TblComprehension: sym"{" * Ct(Exp * (sym"," * Exp)^-1) * CompInner * sym"}" / mark"tblcomprehension" + + CompInner: Ct((CompForEach + CompFor) * CompClause^0) + CompForEach: key"for" * Ct(AssignableNameList) * key"in" * (sym"*" * Exp / mark"unpack" + Exp) / mark"foreach" + CompFor: key "for" * Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1) / mark"for" + CompClause: CompFor + CompForEach + key"when" * Exp / mark"when" + + Assign: sym"=" * (Ct(With + If + Switch) + Ct(TableBlock + ExpListLow)) / mark"assign" + Update: ((sym"..=" + sym"+=" + sym"-=" + sym"*=" + sym"/=" + sym"%=" + sym"or=" + sym"and=" + sym"&=" + sym"|=" + sym">>=" + sym"<<=") / trim) * Exp / mark"update" + + CharOperators: Space * C(S"+-*/%^><|&") + WordOperators: op"or" + op"and" + op"<=" + op">=" + op"~=" + op"!=" + op"==" + op".." + op"<<" + op">>" + op"//" + BinaryOperator: (WordOperators + CharOperators) * SpaceBreak^0 + + Assignable: Cmt(Chain, check_assignable) + Name + SelfName + Exp: Ct(Value * (BinaryOperator * Value)^0) / flatten_or_mark"exp" + + SimpleValue: + If + Unless + + Switch + + With + + ClassDecl + + ForEach + For + While + + Cmt(Do, check_do) + + sym"-" * -SomeSpace * Exp / mark"minus" + + sym"#" * Exp / mark"length" + + sym"~" * Exp / mark"bitnot" + + key"not" * Exp / mark"not" + + TblComprehension + + TableLit + + Comprehension + + FunLit + + Num + + -- a function call or an object access + ChainValue: (Chain + Callable) * Ct(InvokeArgs^-1) / join_chain + + Value: pos( + SimpleValue + + Ct(KeyValueList) / mark"table" + + ChainValue + + String) + + SliceValue: Exp + + String: Space * DoubleString + Space * SingleString + LuaString + SingleString: simple_string("'") + DoubleString: simple_string('"', true) + + LuaString: Cg(LuaStringOpen, "string_open") * Cb"string_open" * Break^-1 * + C((1 - Cmt(C(LuaStringClose) * Cb"string_open", check_lua_string))^0) * + LuaStringClose / mark"string" + + LuaStringOpen: sym"[" * P"="^0 * "[" / trim + LuaStringClose: "]" * P"="^0 * "]" + + Callable: pos(Name / mark"ref") + SelfName + VarArg + Parens / mark"parens" + Parens: sym"(" * SpaceBreak^0 * Exp * SpaceBreak^0 * sym")" + + FnArgs: symx"(" * SpaceBreak^0 * Ct(FnArgsExpList^-1) * SpaceBreak^0 * sym")" + sym"!" * -P"=" * Ct"" + FnArgsExpList: Exp * ((Break + sym",") * White * Exp)^0 + + Chain: (Callable + String + -S".\\") * ChainItems / mark"chain" + + Space * (DotChainItem * ChainItems^-1 + ColonChain) / mark"chain" + + ChainItems: ChainItem^1 * ColonChain^-1 + ColonChain + + ChainItem: + Invoke + + DotChainItem + + Slice + + symx"[" * Exp/mark"index" * sym"]" + + DotChainItem: symx"." * _Name/mark"dot" + ColonChainItem: symx"\\" * _Name / mark"colon" + ColonChain: ColonChainItem * (Invoke * ChainItems^-1)^-1 + + Slice: symx"[" * (SliceValue + Cc(1)) * sym"," * (SliceValue + Cc"") * + (sym"," * SliceValue)^-1 *sym"]" / mark"slice" + + Invoke: FnArgs / mark"call" + + SingleString / wrap_func_arg + + DoubleString / wrap_func_arg + + L(P"[") * LuaString / wrap_func_arg + + TableValue: KeyValue + Ct(Exp) + + TableLit: sym"{" * Ct( + TableValueList^-1 * sym","^-1 * + (SpaceBreak * TableLitLine * (sym","^-1 * SpaceBreak * TableLitLine)^0 * sym","^-1)^-1 + ) * White * sym"}" / mark"table" + + TableValueList: TableValue * (sym"," * TableValue)^0 + TableLitLine: PushIndent * ((TableValueList * PopIndent) + (PopIndent * Cut)) + Space + + -- the unbounded table + TableBlockInner: Ct(KeyValueLine * (SpaceBreak^1 * KeyValueLine)^0) + TableBlock: SpaceBreak^1 * Advance * ensure(TableBlockInner, PopIndent) / mark"table" + + ClassDecl: key"class" * -P":" * (Assignable + Cc(nil)) * (key"extends" * PreventIndent * ensure(Exp, PopIndent) + C"")^-1 * (ClassBlock + Ct("")) / mark"class" + + ClassBlock: SpaceBreak^1 * Advance * + Ct(ClassLine * (SpaceBreak^1 * ClassLine)^0) * PopIndent + ClassLine: CheckIndent * (( + KeyValueList / mark"props" + + Statement / mark"stm" + + Exp / mark"stm" + ) * sym","^-1) + + Export: key"export" * ( + Cc"class" * ClassDecl + + op"*" + op"^" + + Ct(NameList) * (sym"=" * Ct(ExpListLow))^-1) / mark"export" + + KeyValue: (sym":" * -SomeSpace * Name * lpeg.Cp!) / self_assign + + Ct( + (KeyName + sym"[" * Exp * sym"]" +Space * DoubleString + Space * SingleString) * + symx":" * + (Exp + TableBlock + SpaceBreak^1 * Exp) + ) + + KeyValueList: KeyValue * (sym"," * KeyValue)^0 + KeyValueLine: CheckIndent * KeyValueList * sym","^-1 + + FnArgsDef: sym"(" * White * Ct(FnArgDefList^-1) * + (key"using" * Ct(NameList + Space * "nil") + Ct"") * + White * sym")" + Ct"" * Ct"" + + FnArgDefList: FnArgDef * ((sym"," + Break) * White * FnArgDef)^0 * ((sym"," + Break) * White * Ct(VarArg))^0 + Ct(VarArg) + FnArgDef: Ct((Name + SelfName) * (sym"=" * Exp)^-1) + + FunLit: FnArgsDef * + (sym"->" * Cc"slim" + sym"=>" * Cc"fat") * + (Body + Ct"") / mark"fndef" + + NameList: Name * (sym"," * Name)^0 + NameOrDestructure: Name + TableLit + AssignableNameList: NameOrDestructure * (sym"," * NameOrDestructure)^0 + + ExpList: Exp * (sym"," * Exp)^0 + ExpListLow: Exp * ((sym"," + sym";") * Exp)^0 + + -- open args + InvokeArgs: -P"-" * (ExpList * (sym"," * (TableBlock + SpaceBreak * Advance * ArgBlock * TableBlock^-1) + TableBlock)^-1 + TableBlock) + ArgBlock: ArgLine * (sym"," * SpaceBreak * ArgLine)^0 * PopIndent + ArgLine: CheckIndent * ExpList + } + + g, state + +file_parser = -> + g, state = build_grammar! + file_grammar = White * g * White * -1 + + { + match: (str) => + local tree + _, err = xpcall (-> + tree = file_grammar\match str + ), (err) -> + debug.traceback err, 2 + + -- regular error, let it bubble up + if type(err) == "string" + return nil, err + + unless tree + local msg + err_pos = state.last_pos + + if err + node, msg = unpack err + msg = " " .. msg if msg + err_pos = node[-1] + + line_no = pos_to_line str, err_pos + line_str = get_line(str, line_no) or "" + return nil, err_msg\format msg or "", line_no, trim line_str + + tree + } + +{ + :extract_line + :build_grammar + + -- parse a string as a file + -- returns tree, or nil and error message + string: (str) -> file_parser!\match str +} + diff --git a/prototypes/scp-clicker/moonscript/parse/env.lua b/prototypes/scp-clicker/moonscript/parse/env.lua new file mode 100644 index 0000000..f805211 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/env.lua @@ -0,0 +1,70 @@ +local getfenv, setfenv +do + local _obj_0 = require("moonscript.util") + getfenv, setfenv = _obj_0.getfenv, _obj_0.setfenv +end +local wrap_env +wrap_env = function(debug, fn) + local V, Cmt + do + local _obj_0 = require("lpeg") + V, Cmt = _obj_0.V, _obj_0.Cmt + end + local env = getfenv(fn) + local wrap_name = V + if debug then + local indent = 0 + local indent_char = " " + local iprint + iprint = function(...) + local args = table.concat((function(...) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = { + ... + } + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + _accum_0[_len_0] = tostring(a) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(...), ", ") + return io.stderr:write(tostring(indent_char:rep(indent)) .. tostring(args) .. "\n") + end + wrap_name = function(name) + local v = V(name) + v = Cmt("", function(str, pos) + local rest = str:sub(pos, -1):match("^([^\n]*)") + iprint("* " .. tostring(name) .. " (" .. tostring(rest) .. ")") + indent = indent + 1 + return true + end) * Cmt(v, function(str, pos, ...) + iprint(name, true) + indent = indent - 1 + return true, ... + end) + Cmt("", function() + iprint(name, false) + indent = indent - 1 + return false + end) + return v + end + end + return setfenv(fn, setmetatable({ }, { + __index = function(self, name) + local value = env[name] + if value ~= nil then + return value + end + if name:match("^[A-Z][A-Za-z0-9]*$") then + local v = wrap_name(name) + return v + end + return error("unknown variable referenced: " .. tostring(name)) + end + })) +end +return { + wrap_env = wrap_env +} diff --git a/prototypes/scp-clicker/moonscript/parse/env.moon b/prototypes/scp-clicker/moonscript/parse/env.moon new file mode 100644 index 0000000..ea71c3d --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/env.moon @@ -0,0 +1,51 @@ + +import getfenv, setfenv from require "moonscript.util" + +-- all undefined Proper globals are automaticlly converted into lpeg.V +wrap_env = (debug, fn) -> + import V, Cmt from require "lpeg" + + env = getfenv fn + wrap_name = V + + if debug + indent = 0 + indent_char = " " + + iprint = (...) -> + args = table.concat [tostring a for a in *{...}], ", " + io.stderr\write "#{indent_char\rep(indent)}#{args}\n" + + wrap_name = (name) -> + v = V name + v = Cmt("", (str, pos) -> + rest = str\sub(pos, -1)\match "^([^\n]*)" + + iprint "* #{name} (#{rest})" + indent += 1 + true + ) * Cmt(v, (str, pos, ...) -> + iprint name, true + indent -= 1 + true, ... + ) + Cmt("", -> + iprint name, false + indent -= 1 + false + ) + + v + + setfenv fn, setmetatable {}, { + __index: (name) => + value = env[name] + return value if value != nil + + if name\match"^[A-Z][A-Za-z0-9]*$" + v = wrap_name name + return v + + error "unknown variable referenced: #{name}" + } + +{ :wrap_env } diff --git a/prototypes/scp-clicker/moonscript/parse/literals.lua b/prototypes/scp-clicker/moonscript/parse/literals.lua new file mode 100644 index 0000000..1ad5aeb --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/literals.lua @@ -0,0 +1,39 @@ +local safe_module +safe_module = require("moonscript.util").safe_module +local S, P, R, C +do + local _obj_0 = require("lpeg") + S, P, R, C = _obj_0.S, _obj_0.P, _obj_0.R, _obj_0.C +end +local lpeg = require("lpeg") +local L = lpeg.luversion and lpeg.L or function(v) + return #v +end +local White = S(" \t\r\n") ^ 0 +local plain_space = S(" \t") ^ 0 +local Break = P("\r") ^ -1 * P("\n") +local Stop = Break + -1 +local Comment = P("--") * (1 - S("\r\n")) ^ 0 * L(Stop) +local Space = plain_space * Comment ^ -1 +local SomeSpace = S(" \t") ^ 1 * Comment ^ -1 +local SpaceBreak = Space * Break +local EmptyLine = SpaceBreak +local AlphaNum = R("az", "AZ", "09", "__") +local Name = C(R("az", "AZ", "__") * AlphaNum ^ 0) +local Num = P("0x") * R("09", "af", "AF") ^ 1 * (S("uU") ^ -1 * S("lL") ^ 2) ^ -1 + R("09") ^ 1 * (S("uU") ^ -1 * S("lL") ^ 2) + (R("09") ^ 1 * (P(".") * R("09") ^ 1) ^ -1 + P(".") * R("09") ^ 1) * (S("eE") * P("-") ^ -1 * R("09") ^ 1) ^ -1 +local Shebang = P("#!") * P(1 - Stop) ^ 0 +return safe_module("moonscript.parse.literals", { + L = L, + White = White, + Break = Break, + Stop = Stop, + Comment = Comment, + Space = Space, + SomeSpace = SomeSpace, + SpaceBreak = SpaceBreak, + EmptyLine = EmptyLine, + AlphaNum = AlphaNum, + Name = Name, + Num = Num, + Shebang = Shebang +}) diff --git a/prototypes/scp-clicker/moonscript/parse/literals.moon b/prototypes/scp-clicker/moonscript/parse/literals.moon new file mode 100644 index 0000000..5a4980f --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/literals.moon @@ -0,0 +1,38 @@ +-- non-recursive parsers +import safe_module from require "moonscript.util" +import S, P, R, C from require "lpeg" + +lpeg = require "lpeg" +L = lpeg.luversion and lpeg.L or (v) -> #v + +White = S" \t\r\n"^0 +plain_space = S" \t"^0 + +Break = P"\r"^-1 * P"\n" +Stop = Break + -1 + +Comment = P"--" * (1 - S"\r\n")^0 * L(Stop) +Space = plain_space * Comment^-1 +SomeSpace = S" \t"^1 * Comment^-1 + +SpaceBreak = Space * Break +EmptyLine = SpaceBreak + +AlphaNum = R "az", "AZ", "09", "__" + +Name = C R("az", "AZ", "__") * AlphaNum^0 + +Num = P"0x" * R("09", "af", "AF")^1 * (S"uU"^-1 * S"lL"^2)^-1 + + R"09"^1 * (S"uU"^-1 * S"lL"^2) + + ( + R"09"^1 * (P"." * R"09"^1)^-1 + + P"." * R"09"^1 + ) * (S"eE" * P"-"^-1 * R"09"^1)^-1 + +Shebang = P"#!" * P(1 - Stop)^0 + +safe_module "moonscript.parse.literals", { + :L + :White, :Break, :Stop, :Comment, :Space, :SomeSpace, :SpaceBreak, :EmptyLine, + :AlphaNum, :Name, :Num, :Shebang +} diff --git a/prototypes/scp-clicker/moonscript/parse/util.lua b/prototypes/scp-clicker/moonscript/parse/util.lua new file mode 100644 index 0000000..2687384 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/util.lua @@ -0,0 +1,306 @@ +local unpack +unpack = require("moonscript.util").unpack +local P, C, S, Cp, Cmt, V +do + local _obj_0 = require("lpeg") + P, C, S, Cp, Cmt, V = _obj_0.P, _obj_0.C, _obj_0.S, _obj_0.Cp, _obj_0.Cmt, _obj_0.V +end +local ntype +ntype = require("moonscript.types").ntype +local Space +Space = require("moonscript.parse.literals").Space +local Indent = C(S("\t ") ^ 0) / function(str) + do + local sum = 0 + for v in str:gmatch("[\t ]") do + local _exp_0 = v + if " " == _exp_0 then + sum = sum + 1 + elseif "\t" == _exp_0 then + sum = sum + 4 + end + end + return sum + end +end +local Cut = P(function() + return false +end) +local ensure +ensure = function(patt, finally) + return patt * finally + finally * Cut +end +local extract_line +extract_line = function(str, start_pos) + str = str:sub(start_pos) + do + local m = str:match("^(.-)\n") + if m then + return m + end + end + return str:match("^.-$") +end +local show_line_position +show_line_position = function(str, pos, context) + if context == nil then + context = true + end + local lines = { + { } + } + for c in str:gmatch(".") do + lines[#lines] = lines[#lines] or { } + table.insert(lines[#lines], c) + if c == "\n" then + lines[#lines + 1] = { } + end + end + for i, line in ipairs(lines) do + lines[i] = table.concat(line) + end + local out + local remaining = pos - 1 + for k, line in ipairs(lines) do + if remaining < #line then + local left = line:sub(1, remaining) + local right = line:sub(remaining + 1) + out = { + tostring(left) .. "◉" .. tostring(right) + } + if context then + do + local before = lines[k - 1] + if before then + table.insert(out, 1, before) + end + end + do + local after = lines[k + 1] + if after then + table.insert(out, after) + end + end + end + break + else + remaining = remaining - #line + end + end + if not (out) then + return "-" + end + out = table.concat(out) + return (out:gsub("\n*$", "")) +end +local mark +mark = function(name) + return function(...) + return { + name, + ... + } + end +end +local pos +pos = function(patt) + return (Cp() * patt) / function(pos, value) + if type(value) == "table" then + value[-1] = pos + end + return value + end +end +local got +got = function(what, context) + if context == nil then + context = true + end + return Cmt("", function(str, pos) + print("++ got " .. tostring(what), "[" .. tostring(show_line_position(str, pos, context)) .. "]") + return true + end) +end +local flatten_or_mark +flatten_or_mark = function(name) + return function(tbl) + if #tbl == 1 then + return tbl[1] + end + table.insert(tbl, 1, name) + return tbl + end +end +local is_assignable +do + local chain_assignable = { + index = true, + dot = true, + slice = true + } + is_assignable = function(node) + if node == "..." then + return false + end + local _exp_0 = ntype(node) + if "ref" == _exp_0 or "self" == _exp_0 or "value" == _exp_0 or "self_class" == _exp_0 or "table" == _exp_0 then + return true + elseif "chain" == _exp_0 then + return chain_assignable[ntype(node[#node])] + else + return false + end + end +end +local check_assignable +check_assignable = function(str, pos, value) + if is_assignable(value) then + return true, value + else + return false + end +end +local format_assign +do + local flatten_explist = flatten_or_mark("explist") + format_assign = function(lhs_exps, assign) + if not (assign) then + return flatten_explist(lhs_exps) + end + for _index_0 = 1, #lhs_exps do + local assign_exp = lhs_exps[_index_0] + if not (is_assignable(assign_exp)) then + error({ + assign_exp, + "left hand expression is not assignable" + }) + end + end + local t = ntype(assign) + local _exp_0 = t + if "assign" == _exp_0 then + return { + "assign", + lhs_exps, + unpack(assign, 2) + } + elseif "update" == _exp_0 then + return { + "update", + lhs_exps[1], + unpack(assign, 2) + } + else + return error("unknown assign expression: " .. tostring(t)) + end + end +end +local format_single_assign +format_single_assign = function(lhs, assign) + if assign then + return format_assign({ + lhs + }, assign) + else + return lhs + end +end +local sym +sym = function(chars) + return Space * chars +end +local symx +symx = function(chars) + return chars +end +local simple_string +simple_string = function(delim, allow_interpolation) + local inner = P("\\" .. tostring(delim)) + "\\\\" + (1 - P(delim)) + if allow_interpolation then + local interp = symx('#{') * V("Exp") * sym('}') + inner = (C((inner - interp) ^ 1) + interp / mark("interpolate")) ^ 0 + else + inner = C(inner ^ 0) + end + return C(symx(delim)) * inner * sym(delim) / mark("string") +end +local wrap_func_arg +wrap_func_arg = function(value) + return { + "call", + { + value + } + } +end +local join_chain +join_chain = function(callee, args) + if #args == 0 then + return callee + end + args = { + "call", + args + } + if ntype(callee) == "chain" then + table.insert(callee, args) + return callee + end + return { + "chain", + callee, + args + } +end +local wrap_decorator +wrap_decorator = function(stm, dec) + if not (dec) then + return stm + end + return { + "decorated", + stm, + dec + } +end +local check_lua_string +check_lua_string = function(str, pos, right, left) + return #left == #right +end +local self_assign +self_assign = function(name, pos) + return { + { + "key_literal", + name + }, + { + "ref", + name, + [-1] = pos + } + } +end +return { + Indent = Indent, + Cut = Cut, + ensure = ensure, + extract_line = extract_line, + mark = mark, + pos = pos, + flatten_or_mark = flatten_or_mark, + is_assignable = is_assignable, + check_assignable = check_assignable, + format_assign = format_assign, + format_single_assign = format_single_assign, + sym = sym, + symx = symx, + simple_string = simple_string, + wrap_func_arg = wrap_func_arg, + join_chain = join_chain, + wrap_decorator = wrap_decorator, + check_lua_string = check_lua_string, + self_assign = self_assign, + got = got, + show_line_position = show_line_position +} diff --git a/prototypes/scp-clicker/moonscript/parse/util.moon b/prototypes/scp-clicker/moonscript/parse/util.moon new file mode 100644 index 0000000..17adf77 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/parse/util.moon @@ -0,0 +1,197 @@ + +import unpack from require "moonscript.util" +import P, C, S, Cp, Cmt, V from require "lpeg" +import ntype from require "moonscript.types" +import Space from require "moonscript.parse.literals" + +-- captures an indentation, returns indent depth +Indent = C(S"\t "^0) / (str) -> + with sum = 0 + for v in str\gmatch "[\t ]" + switch v + when " " + sum += 1 + when "\t" + sum += 4 + + +-- causes pattern in progress to be rejected +-- can't have P(false) because it causes preceding patterns not to run +Cut = P -> false + +-- ensures finally runs regardless of whether pattern fails or passes +ensure = (patt, finally) -> + patt * finally + finally * Cut + +-- take rest of line from pos out of str +extract_line = (str, start_pos) -> + str = str\sub start_pos + if m = str\match "^(.-)\n" + return m + + str\match "^.-$" + +-- print the line with a token showing the position +show_line_position = (str, pos, context=true) -> + lines = { {} } + for c in str\gmatch "." + lines[#lines] or= {} + table.insert lines[#lines], c + if c == "\n" + lines[#lines + 1] = {} + + for i, line in ipairs lines + lines[i] = table.concat line + + local out + + remaining = pos - 1 + for k, line in ipairs lines + if remaining < #line + left = line\sub 1, remaining + right = line\sub remaining + 1 + out = { + "#{left}◉#{right}" + } + + if context + if before = lines[k - 1] + table.insert out, 1, before + + if after = lines[k + 1] + table.insert out, after + + break + else + remaining -= #line + + + return "-" unless out + + out = table.concat out + (out\gsub "\n*$", "") + +-- used to identify a capture with a label +mark = (name) -> + (...) -> {name, ...} + +-- wraps pattern to capture pos into node +-- pos is the character offset from the buffer where the node was parsed from. +-- Used to generate error messages +pos = (patt) -> + (Cp! * patt) / (pos, value) -> + if type(value) == "table" + value[-1] = pos + value + +-- generates a debug pattern that always succeeds and prints out where we are +-- in the buffer with a label +got = (what, context=true) -> + Cmt "", (str, pos) -> + print "++ got #{what}", "[#{show_line_position str, pos, context}]" + true + +-- converts 1 element array to its value, otherwise marks it +flatten_or_mark = (name) -> + (tbl) -> + return tbl[1] if #tbl == 1 + table.insert tbl, 1, name + tbl + +-- determines if node is able to be on left side of assignment +is_assignable = do + chain_assignable = { index: true, dot: true, slice: true } + + (node) -> + return false if node == "..." + switch ntype node + when "ref", "self", "value", "self_class", "table" + true + when "chain" + chain_assignable[ntype node[#node]] + else + false + +check_assignable = (str, pos, value) -> + if is_assignable value + true, value + else + false + +-- joins the two parts of an assign parse into a single node +format_assign = do + flatten_explist = flatten_or_mark "explist" + + (lhs_exps, assign) -> + unless assign + return flatten_explist lhs_exps + + for assign_exp in *lhs_exps + unless is_assignable assign_exp + error {assign_exp, "left hand expression is not assignable"} + + t = ntype assign + switch t + when "assign" + {"assign", lhs_exps, unpack assign, 2} + when "update" + {"update", lhs_exps[1], unpack assign, 2} + else + error "unknown assign expression: #{t}" + +-- helper for if statement, which only has single lhs +format_single_assign = (lhs, assign) -> + if assign + format_assign {lhs}, assign + else + lhs + + +-- a symbol +sym = (chars) -> Space * chars +-- a symbol that doesn't accept whitespace before it +symx = (chars) -> chars + +-- a constructor for quote delimited strings +simple_string = (delim, allow_interpolation) -> + inner = P("\\#{delim}") + "\\\\" + (1 - P delim) + + inner = if allow_interpolation + interp = symx'#{' * V"Exp" * sym'}' + (C((inner - interp)^1) + interp / mark"interpolate")^0 + else + C inner^0 + + C(symx(delim)) * inner * sym(delim) / mark"string" + +-- wraps a single value in format needed to be passed as function arguments +wrap_func_arg = (value) -> {"call", {value}} + +-- chains are parsed in two captures, the chain and then the open arguments +-- if there are open arguments, then append them to the end of the chain as a call +join_chain = (callee, args) -> + return callee if #args == 0 + args = {"call", args} + + if ntype(callee) == "chain" + table.insert callee, args + return callee + + {"chain", callee, args} + +-- constructor for decorator node +wrap_decorator = (stm, dec) -> + return stm unless dec + {"decorated", stm, dec} + +check_lua_string = (str, pos, right, left) -> + #left == #right + +-- constructor for :name self assignments in table literals +self_assign = (name, pos) -> + {{"key_literal", name}, {"ref", name, [-1]: pos}} + +{ :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, + :is_assignable, :check_assignable, :format_assign, :format_single_assign, + :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, :wrap_decorator, + :check_lua_string, :self_assign, :got, :show_line_position } diff --git a/prototypes/scp-clicker/moonscript/transform.lua b/prototypes/scp-clicker/moonscript/transform.lua new file mode 100644 index 0000000..c95f14d --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform.lua @@ -0,0 +1,4 @@ +return { + Statement = require("moonscript.transform.statement"), + Value = require("moonscript.transform.value") +} diff --git a/prototypes/scp-clicker/moonscript/transform.moon b/prototypes/scp-clicker/moonscript/transform.moon new file mode 100644 index 0000000..29a93ad --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform.moon @@ -0,0 +1,5 @@ + +{ + Statement: require "moonscript.transform.statement" + Value: require "moonscript.transform.value" +} diff --git a/prototypes/scp-clicker/moonscript/transform/accumulator.lua b/prototypes/scp-clicker/moonscript/transform/accumulator.lua new file mode 100644 index 0000000..539c65f --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/accumulator.lua @@ -0,0 +1,110 @@ +local types = require("moonscript.types") +local build, ntype, NOOP +build, ntype, NOOP = types.build, types.ntype, types.NOOP +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local insert +insert = table.insert +local is_singular +is_singular = function(body) + if #body ~= 1 then + return false + end + if "group" == ntype(body) then + return is_singular(body[2]) + else + return body[1] + end +end +local transform_last_stm +transform_last_stm = require("moonscript.transform.statements").transform_last_stm +local Accumulator +do + local _class_0 + local _base_0 = { + body_idx = { + ["for"] = 4, + ["while"] = 3, + foreach = 4 + }, + convert = function(self, node) + local index = self.body_idx[ntype(node)] + node[index] = self:mutate_body(node[index]) + return self:wrap(node) + end, + wrap = function(self, node, group_type) + if group_type == nil then + group_type = "block_exp" + end + return build[group_type]({ + build.assign_one(self.accum_name, build.table()), + build.assign_one(self.len_name, 1), + node, + group_type == "block_exp" and self.accum_name or NOOP + }) + end, + mutate_body = function(self, body) + local single_stm = is_singular(body) + local val + if single_stm and types.is_value(single_stm) then + body = { } + val = single_stm + else + body = transform_last_stm(body, function(n) + if types.is_value(n) then + return build.assign_one(self.value_name, n) + else + return build.group({ + { + "declare", + { + self.value_name + } + }, + n + }) + end + end) + val = self.value_name + end + local update = { + build.assign_one(NameProxy.index(self.accum_name, self.len_name), val), + { + "update", + self.len_name, + "+=", + 1 + } + } + insert(body, build.group(update)) + return body + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, accum_name) + self.accum_name = NameProxy("accum") + self.value_name = NameProxy("value") + self.len_name = NameProxy("len") + end, + __base = _base_0, + __name = "Accumulator" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Accumulator = _class_0 +end +local default_accumulator +default_accumulator = function(self, node) + return Accumulator():convert(node) +end +return { + Accumulator = Accumulator, + default_accumulator = default_accumulator +} diff --git a/prototypes/scp-clicker/moonscript/transform/accumulator.moon b/prototypes/scp-clicker/moonscript/transform/accumulator.moon new file mode 100644 index 0000000..62ff015 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/accumulator.moon @@ -0,0 +1,71 @@ +types = require "moonscript.types" + +import build, ntype, NOOP from types +import NameProxy from require "moonscript.transform.names" + +import insert from table + +-- is a body a single expression/statement +is_singular = (body) -> + return false if #body != 1 + if "group" == ntype body + is_singular body[2] + else + body[1] + +import transform_last_stm from require "moonscript.transform.statements" + +class Accumulator + body_idx: { for: 4, while: 3, foreach: 4 } + + new: (accum_name) => + @accum_name = NameProxy "accum" + @value_name = NameProxy "value" + @len_name = NameProxy "len" + + -- wraps node and mutates body + convert: (node) => + index = @body_idx[ntype node] + node[index] = @mutate_body node[index] + @wrap node + + -- wrap the node into a block_exp + wrap: (node, group_type="block_exp") => + build[group_type] { + build.assign_one @accum_name, build.table! + build.assign_one @len_name, 1 + node + group_type == "block_exp" and @accum_name or NOOP + } + + -- mutates the body of a loop construct to save last value into accumulator + mutate_body: (body) => + -- shortcut to write simpler code if body is a single expression + single_stm = is_singular body + val = if single_stm and types.is_value single_stm + body = {} + single_stm + else + body = transform_last_stm body, (n) -> + if types.is_value n + build.assign_one @value_name, n + else + -- just ignore it + build.group { + {"declare", {@value_name}} + n + } + @value_name + + update = { + build.assign_one NameProxy.index(@accum_name, @len_name), val + {"update", @len_name, "+=", 1} + } + + insert body, build.group update + body + +default_accumulator = (node) => + Accumulator!\convert node + +{ :Accumulator, :default_accumulator } diff --git a/prototypes/scp-clicker/moonscript/transform/class.lua b/prototypes/scp-clicker/moonscript/transform/class.lua new file mode 100644 index 0000000..74f93c5 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/class.lua @@ -0,0 +1,486 @@ +local NameProxy, LocalName +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName +end +local Run +Run = require("moonscript.transform.statements").Run +local CONSTRUCTOR_NAME = "new" +local insert +insert = table.insert +local build, ntype, NOOP +do + local _obj_0 = require("moonscript.types") + build, ntype, NOOP = _obj_0.build, _obj_0.ntype, _obj_0.NOOP +end +local unpack +unpack = require("moonscript.util").unpack +local transform_super +transform_super = function(cls_name, on_base, block, chain) + if on_base == nil then + on_base = true + end + local relative_parent = { + "chain", + cls_name, + { + "dot", + "__parent" + } + } + if not (chain) then + return relative_parent + end + local chain_tail = { + unpack(chain, 3) + } + local head = chain_tail[1] + if head == nil then + return relative_parent + end + local new_chain = relative_parent + local _exp_0 = head[1] + if "call" == _exp_0 then + if on_base then + insert(new_chain, { + "dot", + "__base" + }) + end + local calling_name = block:get("current_method") + assert(calling_name, "missing calling name") + chain_tail[1] = { + "call", + { + "self", + unpack(head[2]) + } + } + if ntype(calling_name) == "key_literal" then + insert(new_chain, { + "dot", + calling_name[2] + }) + else + insert(new_chain, { + "index", + calling_name + }) + end + elseif "colon" == _exp_0 then + local call = chain_tail[2] + if call and call[1] == "call" then + chain_tail[1] = { + "dot", + head[2] + } + chain_tail[2] = { + "call", + { + "self", + unpack(call[2]) + } + } + end + end + for _index_0 = 1, #chain_tail do + local item = chain_tail[_index_0] + insert(new_chain, item) + end + return new_chain +end +local super_scope +super_scope = function(value, t, key) + local prev_method + return { + "scoped", + Run(function(self) + prev_method = self:get("current_method") + self:set("current_method", key) + return self:set("super", t) + end), + value, + Run(function(self) + return self:set("current_method", prev_method) + end) + } +end +return function(self, node, ret, parent_assign) + local name, parent_val, body = unpack(node, 2) + if parent_val == "" then + parent_val = nil + end + local parent_cls_name = NameProxy("parent") + local base_name = NameProxy("base") + local self_name = NameProxy("self") + local cls_name = NameProxy("class") + local cls_instance_super + cls_instance_super = function(...) + return transform_super(cls_name, true, ...) + end + local cls_super + cls_super = function(...) + return transform_super(cls_name, false, ...) + end + local statements = { } + local properties = { } + for _index_0 = 1, #body do + local item = body[_index_0] + local _exp_0 = item[1] + if "stm" == _exp_0 then + insert(statements, item[2]) + elseif "props" == _exp_0 then + for _index_1 = 2, #item do + local tuple = item[_index_1] + if ntype(tuple[1]) == "self" then + local k, v + k, v = tuple[1], tuple[2] + v = super_scope(v, cls_super, { + "key_literal", + k[2] + }) + insert(statements, build.assign_one(k, v)) + else + insert(properties, tuple) + end + end + end + end + local constructor + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #properties do + local _continue_0 = false + repeat + local tuple = properties[_index_0] + local key = tuple[1] + local _value_0 + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME then + constructor = tuple[2] + _continue_0 = true + break + else + local val + key, val = tuple[1], tuple[2] + _value_0 = { + key, + super_scope(val, cls_instance_super, key) + } + end + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + properties = _accum_0 + end + if not (constructor) then + if parent_val then + constructor = build.fndef({ + args = { + { + "..." + } + }, + arrow = "fat", + body = { + build.chain({ + base = "super", + { + "call", + { + "..." + } + } + }) + } + }) + else + constructor = build.fndef() + end + end + local real_name = name or parent_assign and parent_assign[2][1] + local _exp_0 = ntype(real_name) + if "chain" == _exp_0 then + local last = real_name[#real_name] + local _exp_1 = ntype(last) + if "dot" == _exp_1 then + real_name = { + "string", + '"', + last[2] + } + elseif "index" == _exp_1 then + real_name = last[2] + else + real_name = "nil" + end + elseif "nil" == _exp_0 then + real_name = "nil" + else + local name_t = type(real_name) + local flattened_name + if name_t == "string" then + flattened_name = real_name + elseif name_t == "table" and real_name[1] == "ref" then + flattened_name = real_name[2] + else + flattened_name = error("don't know how to extract name from " .. tostring(name_t)) + end + real_name = { + "string", + '"', + flattened_name + } + end + local cls = build.table({ + { + "__init", + super_scope(constructor, cls_super, { + "key_literal", + "__init" + }) + }, + { + "__base", + base_name + }, + { + "__name", + real_name + }, + parent_val and { + "__parent", + parent_cls_name + } or nil + }) + local class_index + if parent_val then + local class_lookup = build["if"]({ + cond = { + "exp", + { + "ref", + "val" + }, + "==", + "nil" + }, + ["then"] = { + build.assign_one(LocalName("parent"), build.chain({ + base = "rawget", + { + "call", + { + { + "ref", + "cls" + }, + { + "string", + '"', + "__parent" + } + } + } + })), + build["if"]({ + cond = LocalName("parent"), + ["then"] = { + build.chain({ + base = LocalName("parent"), + { + "index", + "name" + } + }) + } + }) + } + }) + insert(class_lookup, { + "else", + { + "val" + } + }) + class_index = build.fndef({ + args = { + { + "cls" + }, + { + "name" + } + }, + body = { + build.assign_one(LocalName("val"), build.chain({ + base = "rawget", + { + "call", + { + base_name, + { + "ref", + "name" + } + } + } + })), + class_lookup + } + }) + else + class_index = base_name + end + local cls_mt = build.table({ + { + "__index", + class_index + }, + { + "__call", + build.fndef({ + args = { + { + "cls" + }, + { + "..." + } + }, + body = { + build.assign_one(self_name, build.chain({ + base = "setmetatable", + { + "call", + { + "{}", + base_name + } + } + })), + build.chain({ + base = "cls.__init", + { + "call", + { + self_name, + "..." + } + } + }), + self_name + } + }) + } + }) + cls = build.chain({ + base = "setmetatable", + { + "call", + { + cls, + cls_mt + } + } + }) + local value = nil + do + local out_body = { + Run(function(self) + if name then + return self:put_name(name) + end + end), + { + "declare", + { + cls_name + } + }, + { + "declare_glob", + "*" + }, + parent_val and build.assign_one(parent_cls_name, parent_val) or NOOP, + build.assign_one(base_name, { + "table", + properties + }), + build.assign_one(base_name:chain("__index"), base_name), + parent_val and build.chain({ + base = "setmetatable", + { + "call", + { + base_name, + build.chain({ + base = parent_cls_name, + { + "dot", + "__base" + } + }) + } + } + }) or NOOP, + build.assign_one(cls_name, cls), + build.assign_one(base_name:chain("__class"), cls_name), + build.group((function() + if #statements > 0 then + return { + build.assign_one(LocalName("self"), cls_name), + build.group(statements) + } + end + end)()), + parent_val and build["if"]({ + cond = { + "exp", + parent_cls_name:chain("__inherited") + }, + ["then"] = { + parent_cls_name:chain("__inherited", { + "call", + { + parent_cls_name, + cls_name + } + }) + } + }) or NOOP, + build.group((function() + if name then + return { + build.assign_one(name, cls_name) + } + end + end)()), + (function() + if ret then + return ret(cls_name) + end + end)() + } + value = build.group({ + build.group((function() + if ntype(name) == "value" then + return { + build.declare({ + names = { + name + } + }) + } + end + end)()), + build["do"](out_body) + }) + end + return value +end diff --git a/prototypes/scp-clicker/moonscript/transform/class.moon b/prototypes/scp-clicker/moonscript/transform/class.moon new file mode 100644 index 0000000..7451375 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/class.moon @@ -0,0 +1,285 @@ +import NameProxy, LocalName from require "moonscript.transform.names" +import Run from require "moonscript.transform.statements" + +CONSTRUCTOR_NAME = "new" + +import insert from table +import build, ntype, NOOP from require "moonscript.types" +import unpack from require "moonscript.util" + +transform_super = (cls_name, on_base=true, block, chain) -> + relative_parent = { + "chain", + cls_name + {"dot", "__parent"} + } + + return relative_parent unless chain + + chain_tail = { unpack chain, 3 } + head = chain_tail[1] + + if head == nil + return relative_parent + + new_chain = relative_parent + + switch head[1] + -- calling super, inject calling name and self into chain + when "call" + if on_base + insert new_chain, {"dot", "__base"} + + calling_name = block\get "current_method" + assert calling_name, "missing calling name" + chain_tail[1] = {"call", {"self", unpack head[2]}} + + if ntype(calling_name) == "key_literal" + insert new_chain, {"dot", calling_name[2]} + else + insert new_chain, {"index", calling_name} + + -- colon call on super, replace class with self as first arg + when "colon" + call = chain_tail[2] + -- calling chain tail + if call and call[1] == "call" + chain_tail[1] = { + "dot" + head[2] + } + + chain_tail[2] = { + "call" + { + "self" + unpack call[2] + } + } + + insert new_chain, item for item in *chain_tail + new_chain + + +super_scope = (value, t, key) -> + local prev_method + + { + "scoped", + Run => + prev_method = @get "current_method" + @set "current_method", key + @set "super", t + value + Run => + @set "current_method", prev_method + } + +(node, ret, parent_assign) => + name, parent_val, body = unpack node, 2 + parent_val = nil if parent_val == "" + + parent_cls_name = NameProxy "parent" + base_name = NameProxy "base" + self_name = NameProxy "self" + cls_name = NameProxy "class" + + -- super call on instance + cls_instance_super = (...) -> transform_super cls_name, true, ... + + -- super call on parent class + cls_super = (...) -> transform_super cls_name, false, ... + + -- split apart properties and statements + statements = {} + properties = {} + for item in *body + switch item[1] + when "stm" + insert statements, item[2] + when "props" + for tuple in *item[2,] + if ntype(tuple[1]) == "self" + {k,v} = tuple + v = super_scope v, cls_super, {"key_literal", k[2]} + insert statements, build.assign_one k, v + else + insert properties, tuple + + -- find constructor + local constructor + properties = for tuple in *properties + key = tuple[1] + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME + constructor = tuple[2] + continue + else + {key, val} = tuple + {key, super_scope val, cls_instance_super, key} + + + unless constructor + constructor = if parent_val + build.fndef { + args: {{"..."}} + arrow: "fat" + body: { + build.chain { base: "super", {"call", {"..."}} } + } + } + else + build.fndef! + + real_name = name or parent_assign and parent_assign[2][1] + real_name = switch ntype real_name + when "chain" + last = real_name[#real_name] + switch ntype last + when "dot" + {"string", '"', last[2]} + when "index" + last[2] + else + "nil" + when "nil" + "nil" + else + name_t = type real_name + -- TODO: don't use string literal as ref + flattened_name = if name_t == "string" + real_name + elseif name_t == "table" and real_name[1] == "ref" + real_name[2] + else + error "don't know how to extract name from #{name_t}" + + {"string", '"', flattened_name} + + cls = build.table { + {"__init", super_scope constructor, cls_super, {"key_literal", "__init"}} + {"__base", base_name} + {"__name", real_name} -- "quote the string" + parent_val and {"__parent", parent_cls_name} or nil + } + + -- looking up a name in the class object + class_index = if parent_val + class_lookup = build["if"] { + cond: { "exp", {"ref", "val"}, "==", "nil" } + then: { + build.assign_one LocalName"parent", build.chain { + base: "rawget" + { + "call", { + {"ref", "cls"} + {"string", '"', "__parent"} + } + } + } + + build.if { + cond: LocalName "parent" + then: { + build.chain { + base: LocalName "parent" + {"index", "name"} + } + } + } + } + } + insert class_lookup, {"else", {"val"}} + + build.fndef { + args: {{"cls"}, {"name"}} + body: { + build.assign_one LocalName"val", build.chain { + base: "rawget", {"call", {base_name, {"ref", "name"}}} + } + class_lookup + } + } + else + base_name + + cls_mt = build.table { + {"__index", class_index} + {"__call", build.fndef { + args: {{"cls"}, {"..."}} + body: { + build.assign_one self_name, build.chain { + base: "setmetatable" + {"call", {"{}", base_name}} + } + build.chain { + base: "cls.__init" + {"call", {self_name, "..."}} + } + self_name + } + }} + } + + cls = build.chain { + base: "setmetatable" + {"call", {cls, cls_mt}} + } + + value = nil + with build + out_body = { + Run => + -- make sure we don't assign the class to a local inside the do + @put_name name if name + + {"declare", { cls_name }} + {"declare_glob", "*"} + + parent_val and .assign_one(parent_cls_name, parent_val) or NOOP + + .assign_one base_name, {"table", properties} + .assign_one base_name\chain"__index", base_name + + parent_val and .chain({ + base: "setmetatable" + {"call", { + base_name, + .chain { base: parent_cls_name, {"dot", "__base"}} + }} + }) or NOOP + + .assign_one cls_name, cls + .assign_one base_name\chain"__class", cls_name + + .group if #statements > 0 then { + .assign_one LocalName"self", cls_name + .group statements + } + + -- run the inherited callback + parent_val and .if({ + cond: {"exp", parent_cls_name\chain "__inherited" } + then: { + parent_cls_name\chain "__inherited", {"call", { + parent_cls_name, cls_name + }} + } + }) or NOOP + + .group if name then { + .assign_one name, cls_name + } + + if ret + ret cls_name + } + + value = .group { + .group if ntype(name) == "value" then { + .declare names: {name} + } + + .do out_body + } + + value diff --git a/prototypes/scp-clicker/moonscript/transform/comprehension.lua b/prototypes/scp-clicker/moonscript/transform/comprehension.lua new file mode 100644 index 0000000..da33892 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/comprehension.lua @@ -0,0 +1,54 @@ +local is_value +is_value = require("moonscript.types").is_value +local construct_comprehension +construct_comprehension = function(inner, clauses) + local current_stms = inner + for i = #clauses, 1, -1 do + local clause = clauses[i] + local t = clause[1] + local _exp_0 = t + if "for" == _exp_0 then + local _, name, bounds + _, name, bounds = clause[1], clause[2], clause[3] + current_stms = { + "for", + name, + bounds, + current_stms + } + elseif "foreach" == _exp_0 then + local _, names, iter + _, names, iter = clause[1], clause[2], clause[3] + current_stms = { + "foreach", + names, + { + iter + }, + current_stms + } + elseif "when" == _exp_0 then + local _, cond + _, cond = clause[1], clause[2] + current_stms = { + "if", + cond, + current_stms + } + else + current_stms = error("Unknown comprehension clause: " .. t) + end + current_stms = { + current_stms + } + end + return current_stms[1] +end +local comprehension_has_value +comprehension_has_value = function(comp) + return is_value(comp[2]) +end +return { + construct_comprehension = construct_comprehension, + comprehension_has_value = comprehension_has_value +} diff --git a/prototypes/scp-clicker/moonscript/transform/comprehension.moon b/prototypes/scp-clicker/moonscript/transform/comprehension.moon new file mode 100644 index 0000000..a181a89 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/comprehension.moon @@ -0,0 +1,30 @@ + +import is_value from require "moonscript.types" + +construct_comprehension = (inner, clauses) -> + current_stms = inner + for i=#clauses,1,-1 + clause = clauses[i] + t = clause[1] + + current_stms = switch t + when "for" + {_, name, bounds} = clause + {"for", name, bounds, current_stms} + when "foreach" + {_, names, iter} = clause + {"foreach", names, {iter}, current_stms} + when "when" + {_, cond} = clause + {"if", cond, current_stms} + else + error "Unknown comprehension clause: "..t + + current_stms = {current_stms} + + current_stms[1] + +comprehension_has_value = (comp) -> + is_value comp[2] + +{:construct_comprehension, :comprehension_has_value} diff --git a/prototypes/scp-clicker/moonscript/transform/destructure.lua b/prototypes/scp-clicker/moonscript/transform/destructure.lua new file mode 100644 index 0000000..2ec46be --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/destructure.lua @@ -0,0 +1,233 @@ +local ntype, mtype, build +do + local _obj_0 = require("moonscript.types") + ntype, mtype, build = _obj_0.ntype, _obj_0.mtype, _obj_0.build +end +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local insert +insert = table.insert +local unpack +unpack = require("moonscript.util").unpack +local user_error +user_error = require("moonscript.errors").user_error +local join +join = function(...) + do + local out = { } + local i = 1 + local _list_0 = { + ... + } + for _index_0 = 1, #_list_0 do + local tbl = _list_0[_index_0] + for _index_1 = 1, #tbl do + local v = tbl[_index_1] + out[i] = v + i = i + 1 + end + end + return out + end +end +local has_destructure +has_destructure = function(names) + for _index_0 = 1, #names do + local n = names[_index_0] + if ntype(n) == "table" then + return true + end + end + return false +end +local extract_assign_names +extract_assign_names = function(name, accum, prefix) + if accum == nil then + accum = { } + end + if prefix == nil then + prefix = { } + end + local i = 1 + local _list_0 = name[2] + for _index_0 = 1, #_list_0 do + local tuple = _list_0[_index_0] + local value, suffix + if #tuple == 1 then + local s = { + "index", + { + "number", + i + } + } + i = i + 1 + value, suffix = tuple[1], s + else + local key = tuple[1] + local s + if ntype(key) == "key_literal" then + local key_name = key[2] + if ntype(key_name) == "colon" then + s = key_name + else + s = { + "dot", + key_name + } + end + else + s = { + "index", + key + } + end + value, suffix = tuple[2], s + end + suffix = join(prefix, { + suffix + }) + local _exp_0 = ntype(value) + if "value" == _exp_0 or "ref" == _exp_0 or "chain" == _exp_0 or "self" == _exp_0 then + insert(accum, { + value, + suffix + }) + elseif "table" == _exp_0 then + extract_assign_names(value, accum, suffix) + else + user_error("Can't destructure value of type: " .. tostring(ntype(value))) + end + end + return accum +end +local build_assign +build_assign = function(scope, destruct_literal, receiver) + local extracted_names = extract_assign_names(destruct_literal) + local names = { } + local values = { } + local inner = { + "assign", + names, + values + } + local obj + if scope:is_local(receiver) or #extracted_names == 1 then + obj = receiver + else + do + obj = NameProxy("obj") + inner = build["do"]({ + build.assign_one(obj, receiver), + { + "assign", + names, + values + } + }) + obj = obj + end + end + for _index_0 = 1, #extracted_names do + local tuple = extracted_names[_index_0] + insert(names, tuple[1]) + local chain + if obj then + chain = NameProxy.chain(obj, unpack(tuple[2])) + else + chain = "nil" + end + insert(values, chain) + end + return build.group({ + { + "declare", + names + }, + inner + }) +end +local split_assign +split_assign = function(scope, assign) + local names, values = unpack(assign, 2) + local g = { } + local total_names = #names + local total_values = #values + local start = 1 + for i, n in ipairs(names) do + if ntype(n) == "table" then + if i > start then + local stop = i - 1 + insert(g, { + "assign", + (function() + local _accum_0 = { } + local _len_0 = 1 + for i = start, stop do + _accum_0[_len_0] = names[i] + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), + (function() + local _accum_0 = { } + local _len_0 = 1 + for i = start, stop do + _accum_0[_len_0] = values[i] + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + }) + end + insert(g, build_assign(scope, n, values[i])) + start = i + 1 + end + end + if total_names >= start or total_values >= start then + local name_slice + if total_names < start then + name_slice = { + "_" + } + else + do + local _accum_0 = { } + local _len_0 = 1 + for i = start, total_names do + _accum_0[_len_0] = names[i] + _len_0 = _len_0 + 1 + end + name_slice = _accum_0 + end + end + local value_slice + if total_values < start then + value_slice = { + "nil" + } + else + do + local _accum_0 = { } + local _len_0 = 1 + for i = start, total_values do + _accum_0[_len_0] = values[i] + _len_0 = _len_0 + 1 + end + value_slice = _accum_0 + end + end + insert(g, { + "assign", + name_slice, + value_slice + }) + end + return build.group(g) +end +return { + has_destructure = has_destructure, + split_assign = split_assign, + build_assign = build_assign, + extract_assign_names = extract_assign_names +} diff --git a/prototypes/scp-clicker/moonscript/transform/destructure.moon b/prototypes/scp-clicker/moonscript/transform/destructure.moon new file mode 100644 index 0000000..5b22247 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/destructure.moon @@ -0,0 +1,128 @@ + +import ntype, mtype, build from require "moonscript.types" +import NameProxy from require "moonscript.transform.names" +import insert from table +import unpack from require "moonscript.util" + +import user_error from require "moonscript.errors" + +join = (...) -> + with out = {} + i = 1 + for tbl in *{...} + for v in *tbl + out[i] = v + i += 1 + +has_destructure = (names) -> + for n in *names + return true if ntype(n) == "table" + false + +extract_assign_names = (name, accum={}, prefix={}) -> + + i = 1 + for tuple in *name[2] + value, suffix = if #tuple == 1 + s = {"index", {"number", i}} + i += 1 + tuple[1], s + else + key = tuple[1] + + s = if ntype(key) == "key_literal" + key_name = key[2] + if ntype(key_name) == "colon" + key_name + else + {"dot", key_name} + else + {"index", key} + + tuple[2], s + + suffix = join prefix, {suffix} + + switch ntype value + when "value", "ref", "chain", "self" + insert accum, {value, suffix} + when "table" + extract_assign_names value, accum, suffix + else + user_error "Can't destructure value of type: #{ntype value}" + + accum + +build_assign = (scope, destruct_literal, receiver) -> + extracted_names = extract_assign_names destruct_literal + + names = {} + values = {} + + inner = {"assign", names, values} + + obj = if scope\is_local(receiver) or #extracted_names == 1 + receiver + else + with obj = NameProxy "obj" + inner = build.do { + build.assign_one obj, receiver + {"assign", names, values} + } + + for tuple in *extracted_names + insert names, tuple[1] + chain = if obj + NameProxy.chain obj, unpack tuple[2] + else + "nil" + insert values, chain + + build.group { + {"declare", names} + inner + } + +-- applies to destructuring to a assign node +split_assign = (scope, assign) -> + names, values = unpack assign, 2 + + g = {} + total_names = #names + total_values = #values + + -- We have to break apart the assign into groups of regular + -- assigns, and then the destructuring assignments + start = 1 + for i, n in ipairs names + if ntype(n) == "table" + if i > start + stop = i - 1 + insert g, { + "assign" + for i=start,stop + names[i] + for i=start,stop + values[i] + } + + insert g, build_assign scope, n, values[i] + + start = i + 1 + + if total_names >= start or total_values >= start + name_slice = if total_names < start + {"_"} + else + for i=start,total_names do names[i] + + value_slice = if total_values < start + {"nil"} + else + for i=start,total_values do values[i] + + insert g, {"assign", name_slice, value_slice} + + build.group g + +{ :has_destructure, :split_assign, :build_assign, :extract_assign_names } diff --git a/prototypes/scp-clicker/moonscript/transform/names.lua b/prototypes/scp-clicker/moonscript/transform/names.lua new file mode 100644 index 0000000..f6af4c4 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/names.lua @@ -0,0 +1,118 @@ +local build +build = require("moonscript.types").build +local unpack +unpack = require("moonscript.util").unpack +local LocalName +do + local _class_0 + local _base_0 = { + get_name = function(self) + return self.name + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, name) + self.name = name + self[1] = "temp_name" + end, + __base = _base_0, + __name = "LocalName" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + LocalName = _class_0 +end +local NameProxy +do + local _class_0 + local _base_0 = { + get_name = function(self, scope, dont_put) + if dont_put == nil then + dont_put = true + end + if not self.name then + self.name = scope:free_name(self.prefix, dont_put) + end + return self.name + end, + chain = function(self, ...) + local items = { + base = self, + ... + } + for k, v in ipairs(items) do + if type(v) == "string" then + items[k] = { + "dot", + v + } + else + items[k] = v + end + end + return build.chain(items) + end, + index = function(self, key) + if type(key) == "string" then + key = { + "ref", + key + } + end + return build.chain({ + base = self, + { + "index", + key + } + }) + end, + __tostring = function(self) + if self.name then + return ("name<%s>"):format(self.name) + else + return ("name"):format(self.prefix) + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, prefix) + self.prefix = prefix + self[1] = "temp_name" + end, + __base = _base_0, + __name = "NameProxy" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + NameProxy = _class_0 +end +local is_name_proxy +is_name_proxy = function(v) + if not (type(v) == "table") then + return false + end + local _exp_0 = v.__class + if LocalName == _exp_0 or NameProxy == _exp_0 then + return true + end +end +return { + NameProxy = NameProxy, + LocalName = LocalName, + is_name_proxy = is_name_proxy +} diff --git a/prototypes/scp-clicker/moonscript/transform/names.moon b/prototypes/scp-clicker/moonscript/transform/names.moon new file mode 100644 index 0000000..4af6aa0 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/names.moon @@ -0,0 +1,51 @@ + +import build from require "moonscript.types" +import unpack from require "moonscript.util" + +-- always declares as local +class LocalName + new: (@name) => self[1] = "temp_name" + get_name: => @name + +-- creates a unique name when used +class NameProxy + new: (@prefix) => + self[1] = "temp_name" + + get_name: (scope, dont_put=true) => + if not @name + @name = scope\free_name @prefix, dont_put + @name + + chain: (...) => + items = { base: @, ... } + for k,v in ipairs items + items[k] = if type(v) == "string" + {"dot", v} + else + v + + build.chain items + + index: (key) => + if type(key) == "string" + key = {"ref", key} + + build.chain { + base: self, {"index", key} + } + + __tostring: => + if @name + ("name<%s>")\format @name + else + ("name")\format @prefix + +is_name_proxy = (v) -> + return false unless type(v) == "table" + + switch v.__class + when LocalName, NameProxy + true + +{ :NameProxy, :LocalName, :is_name_proxy } diff --git a/prototypes/scp-clicker/moonscript/transform/statement.lua b/prototypes/scp-clicker/moonscript/transform/statement.lua new file mode 100644 index 0000000..edeab69 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/statement.lua @@ -0,0 +1,847 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local NameProxy, LocalName, is_name_proxy +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName, is_name_proxy = _obj_0.NameProxy, _obj_0.LocalName, _obj_0.is_name_proxy +end +local Run, transform_last_stm, implicitly_return, last_stm +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, last_stm = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.last_stm +end +local types = require("moonscript.types") +local build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP +build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP = types.build, types.ntype, types.is_value, types.smart_node, types.value_is_singular, types.is_slice, types.NOOP +local insert +insert = table.insert +local destructure = require("moonscript.transform.destructure") +local construct_comprehension +construct_comprehension = require("moonscript.transform.comprehension").construct_comprehension +local unpack +unpack = require("moonscript.util").unpack +local with_continue_listener +with_continue_listener = function(body) + local continue_name = nil + return { + Run(function(self) + return self:listen("continue", function() + if not (continue_name) then + continue_name = NameProxy("continue") + self:put_name(continue_name) + end + return continue_name + end) + end), + build.group(body), + Run(function(self) + if not (continue_name) then + return + end + local last = last_stm(body) + local enclose_lines = types.terminating[last and ntype(last)] + self:put_name(continue_name, nil) + return self:splice(function(lines) + if enclose_lines then + lines = { + "do", + { + lines + } + } + end + return { + { + "assign", + { + continue_name + }, + { + "false" + } + }, + { + "repeat", + "true", + { + lines, + { + "assign", + { + continue_name + }, + { + "true" + } + } + } + }, + { + "if", + { + "not", + continue_name + }, + { + { + "break" + } + } + } + } + end) + end) + } +end +local extract_declarations +extract_declarations = function(self, body, start, out) + if body == nil then + body = self.current_stms + end + if start == nil then + start = self.current_stm_i + 1 + end + if out == nil then + out = { } + end + for i = start, #body do + local _continue_0 = false + repeat + local stm = body[i] + if stm == nil then + _continue_0 = true + break + end + stm = self.transform.statement(stm) + body[i] = stm + local _exp_0 = stm[1] + if "assign" == _exp_0 or "declare" == _exp_0 then + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + insert(out, name) + elseif type(name) == "string" then + insert(out, name) + end + end + elseif "group" == _exp_0 then + extract_declarations(self, stm[2], 1, out) + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return out +end +local expand_elseif_assign +expand_elseif_assign = function(ifstm) + for i = 4, #ifstm do + local case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" then + local split = { + unpack(ifstm, 1, i - 1) + } + insert(split, { + "else", + { + { + "if", + case[2], + case[3], + unpack(ifstm, i + 1) + } + } + }) + return split + end + end + return ifstm +end +return Transformer({ + transform = function(self, tuple) + local _, node, fn + _, node, fn = tuple[1], tuple[2], tuple[3] + return fn(node) + end, + root_stms = function(self, body) + return transform_last_stm(body, implicitly_return(self)) + end, + ["return"] = function(self, node) + local ret_val = node[2] + local ret_val_type = ntype(ret_val) + if ret_val_type == "explist" and #ret_val == 2 then + ret_val = ret_val[2] + ret_val_type = ntype(ret_val) + end + if types.cascading[ret_val_type] then + return implicitly_return(self)(ret_val) + end + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" then + local Value = require("moonscript.transform.value") + ret_val = Value:transform_once(self, ret_val) + if ntype(ret_val) == "block_exp" then + return build.group(transform_last_stm(ret_val[2], function(stm) + return { + "return", + stm + } + end)) + end + end + node[2] = ret_val + return node + end, + declare_glob = function(self, node) + local names = extract_declarations(self) + if node[2] == "^" then + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + local str_name + if ntype(name) == "ref" then + str_name = name[2] + else + str_name = name + end + if not (str_name:match("^%u")) then + _continue_0 = true + break + end + local _value_0 = name + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + names = _accum_0 + end + end + return { + "declare", + names + } + end, + assign = function(self, node) + local names, values = unpack(node, 2) + local num_values = #values + local num_names = #values + if num_names == 1 and num_values == 1 then + local first_value = values[1] + local first_name = names[1] + local first_type = ntype(first_value) + if first_type == "chain" then + local Value = require("moonscript.transform.value") + first_value = Value:transform_once(self, first_value) + first_type = ntype(first_value) + end + local _exp_0 = ntype(first_value) + if "block_exp" == _exp_0 then + local block_body = first_value[2] + local idx = #block_body + block_body[idx] = build.assign_one(first_name, block_body[idx]) + return build.group({ + { + "declare", + { + first_name + } + }, + { + "do", + block_body + } + }) + elseif "comprehension" == _exp_0 or "tblcomprehension" == _exp_0 or "foreach" == _exp_0 or "for" == _exp_0 or "while" == _exp_0 then + local Value = require("moonscript.transform.value") + return build.assign_one(first_name, Value:transform_once(self, first_value)) + else + values[1] = first_value + end + end + local transformed + if num_values == 1 then + local value = values[1] + local t = ntype(value) + if t == "decorated" then + value = self.transform.statement(value) + t = ntype(value) + end + if types.cascading[t] then + local ret + ret = function(stm) + if is_value(stm) then + return { + "assign", + names, + { + stm + } + } + else + return stm + end + end + transformed = build.group({ + { + "declare", + names + }, + self.transform.statement(value, ret, node) + }) + end + end + node = transformed or node + if destructure.has_destructure(names) then + return destructure.split_assign(self, node) + end + return node + end, + continue = function(self, node) + local continue_name = self:send("continue") + if not (continue_name) then + error("continue must be inside of a loop") + end + return build.group({ + build.assign_one(continue_name, "true"), + { + "break" + } + }) + end, + export = function(self, node) + if #node > 2 then + if node[2] == "class" then + local cls = smart_node(node[3]) + return build.group({ + { + "export", + { + cls.name + } + }, + cls + }) + else + return build.group({ + { + "export", + node[2] + }, + build.assign({ + names = node[2], + values = node[3] + }) + }) + end + else + return nil + end + end, + update = function(self, node) + local name, op, exp = unpack(node, 2) + local op_final = op:match("^(.+)=$") + if not op_final then + error("Unknown op: " .. op) + end + local lifted + if ntype(name) == "chain" then + lifted = { } + local new_chain + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 3, #name do + local part = name[_index_0] + if ntype(part) == "index" then + local proxy = NameProxy("update") + table.insert(lifted, { + proxy, + part[2] + }) + _accum_0[_len_0] = { + "index", + proxy + } + else + _accum_0[_len_0] = part + end + _len_0 = _len_0 + 1 + end + new_chain = _accum_0 + end + if next(lifted) then + name = { + name[1], + name[2], + unpack(new_chain) + } + end + end + if not (value_is_singular(exp)) then + exp = { + "parens", + exp + } + end + local out = build.assign_one(name, { + "exp", + name, + op_final, + exp + }) + if lifted and next(lifted) then + local names + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #lifted do + local l = lifted[_index_0] + _accum_0[_len_0] = l[1] + _len_0 = _len_0 + 1 + end + names = _accum_0 + end + local values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #lifted do + local l = lifted[_index_0] + _accum_0[_len_0] = l[2] + _len_0 = _len_0 + 1 + end + values = _accum_0 + end + out = build.group({ + { + "assign", + names, + values + }, + out + }) + end + return out + end, + import = function(self, node) + local names, source = unpack(node, 2) + local table_values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + local dest_name + if ntype(name) == "colon" then + dest_name = name[2] + else + dest_name = name + end + local _value_0 = { + { + "key_literal", + name + }, + dest_name + } + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + table_values = _accum_0 + end + local dest = { + "table", + table_values + } + return { + "assign", + { + dest + }, + { + source + }, + [-1] = node[-1] + } + end, + comprehension = function(self, node, action) + local exp, clauses = unpack(node, 2) + action = action or function(exp) + return { + exp + } + end + return construct_comprehension(action(exp), clauses) + end, + ["do"] = function(self, node, ret) + if ret then + node[2] = transform_last_stm(node[2], ret) + end + return node + end, + decorated = function(self, node) + local stm, dec = unpack(node, 2) + local wrapped + local _exp_0 = dec[1] + if "if" == _exp_0 then + local cond, fail = unpack(dec, 2) + if fail then + fail = { + "else", + { + fail + } + } + end + wrapped = { + "if", + cond, + { + stm + }, + fail + } + elseif "unless" == _exp_0 then + wrapped = { + "unless", + dec[2], + { + stm + } + } + elseif "comprehension" == _exp_0 then + wrapped = { + "comprehension", + stm, + dec[2] + } + else + wrapped = error("Unknown decorator " .. dec[1]) + end + if ntype(stm) == "assign" then + wrapped = build.group({ + build.declare({ + names = (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + _accum_0[_len_0] = name + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end)() + }), + wrapped + }) + end + return wrapped + end, + unless = function(self, node) + local clause = node[2] + if ntype(clause) == "assign" then + if destructure.has_destructure(clause[2]) then + error("destructure not allowed in unless assignment") + end + return build["do"]({ + clause, + { + "if", + { + "not", + clause[2][1] + }, + unpack(node, 3) + } + }) + else + return { + "if", + { + "not", + { + "parens", + clause + } + }, + unpack(node, 3) + } + end + end, + ["if"] = function(self, node, ret) + if ntype(node[2]) == "assign" then + local assign, body = unpack(node, 2) + if destructure.has_destructure(assign[2]) then + local name = NameProxy("des") + body = { + destructure.build_assign(self, assign[2][1], name), + build.group(node[3]) + } + return build["do"]({ + build.assign_one(name, assign[3][1]), + { + "if", + name, + body, + unpack(node, 4) + } + }) + else + local name = assign[2][1] + return build["do"]({ + assign, + { + "if", + name, + unpack(node, 3) + } + }) + end + end + node = expand_elseif_assign(node) + if ret then + smart_node(node) + node['then'] = transform_last_stm(node['then'], ret) + for i = 4, #node do + local case = node[i] + local body_idx = #node[i] + case[body_idx] = transform_last_stm(case[body_idx], ret) + end + end + return node + end, + with = function(self, node, ret) + local exp, block = unpack(node, 2) + local copy_scope = true + local scope_name, named_assign + do + local last = last_stm(block) + if last then + if types.terminating[ntype(last)] then + ret = false + end + end + end + if ntype(exp) == "assign" then + local names, values = unpack(exp, 2) + local first_name = names[1] + if ntype(first_name) == "ref" then + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy("with") + exp = values[1] + values[1] = scope_name + named_assign = { + "assign", + names, + values + } + end + elseif self:is_local(exp) then + scope_name = exp + copy_scope = false + end + scope_name = scope_name or NameProxy("with") + local out = build["do"]({ + copy_scope and build.assign_one(scope_name, exp) or NOOP, + named_assign or NOOP, + Run(function(self) + return self:set("scope_var", scope_name) + end), + unpack(block) + }) + if ret then + table.insert(out[2], ret(scope_name)) + end + return out + end, + foreach = function(self, node, _) + smart_node(node) + local source = unpack(node.iter) + local destructures = { } + do + local _accum_0 = { } + local _len_0 = 1 + for i, name in ipairs(node.names) do + if ntype(name) == "table" then + do + local proxy = NameProxy("des") + insert(destructures, destructure.build_assign(self, name, proxy)) + _accum_0[_len_0] = proxy + end + else + _accum_0[_len_0] = name + end + _len_0 = _len_0 + 1 + end + node.names = _accum_0 + end + if next(destructures) then + insert(destructures, build.group(node.body)) + node.body = destructures + end + if ntype(source) == "unpack" then + local list = source[2] + local index_name = NameProxy("index") + local list_name = self:is_local(list) and list or NameProxy("list") + local slice_var = nil + local bounds + if is_slice(list) then + local slice = list[#list] + table.remove(list) + table.remove(slice, 1) + if self:is_local(list) then + list_name = list + end + if slice[2] and slice[2] ~= "" then + local max_tmp_name = NameProxy("max") + slice_var = build.assign_one(max_tmp_name, slice[2]) + slice[2] = { + "exp", + max_tmp_name, + "<", + 0, + "and", + { + "length", + list_name + }, + "+", + max_tmp_name, + "or", + max_tmp_name + } + else + slice[2] = { + "length", + list_name + } + end + bounds = slice + else + bounds = { + 1, + { + "length", + list_name + } + } + end + local names + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = node.names + for _index_0 = 1, #_list_0 do + local n = _list_0[_index_0] + _accum_0[_len_0] = is_name_proxy(n) and n or LocalName(n) or n + _len_0 = _len_0 + 1 + end + names = _accum_0 + end + return build.group({ + list_name ~= list and build.assign_one(list_name, list) or NOOP, + slice_var or NOOP, + build["for"]({ + name = index_name, + bounds = bounds, + body = { + { + "assign", + names, + { + NameProxy.index(list_name, index_name) + } + }, + build.group(node.body) + } + }) + }) + end + node.body = with_continue_listener(node.body) + end, + ["while"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + ["for"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + switch = function(self, node, ret) + local exp, conds = unpack(node, 2) + local exp_name = NameProxy("exp") + local convert_cond + convert_cond = function(cond) + local t, case_exps, body = unpack(cond) + local out = { } + insert(out, t == "case" and "elseif" or "else") + if t ~= "else" then + local cond_exp = { } + for i, case in ipairs(case_exps) do + if i == 1 then + insert(cond_exp, "exp") + else + insert(cond_exp, "or") + end + if not (value_is_singular(case)) then + case = { + "parens", + case + } + end + insert(cond_exp, { + "exp", + case, + "==", + exp_name + }) + end + insert(out, cond_exp) + else + body = case_exps + end + if ret then + body = transform_last_stm(body, ret) + end + insert(out, body) + return out + end + local first = true + local if_stm = { + "if" + } + for _index_0 = 1, #conds do + local cond = conds[_index_0] + local if_cond = convert_cond(cond) + if first then + first = false + insert(if_stm, if_cond[2]) + insert(if_stm, if_cond[3]) + else + insert(if_stm, if_cond) + end + end + return build.group({ + build.assign_one(exp_name, exp), + if_stm + }) + end, + class = require("moonscript.transform.class") +}) diff --git a/prototypes/scp-clicker/moonscript/transform/statement.moon b/prototypes/scp-clicker/moonscript/transform/statement.moon new file mode 100644 index 0000000..2e4b8e8 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/statement.moon @@ -0,0 +1,522 @@ +import Transformer from require "moonscript.transform.transformer" + +import NameProxy, LocalName, is_name_proxy from require "moonscript.transform.names" + +import Run, transform_last_stm, implicitly_return, last_stm + from require "moonscript.transform.statements" + +types = require "moonscript.types" + +import build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP + from types + +import insert from table + +destructure = require "moonscript.transform.destructure" +import construct_comprehension from require "moonscript.transform.comprehension" + +import unpack from require "moonscript.util" + +with_continue_listener = (body) -> + continue_name = nil + + { + Run => + @listen "continue", -> + unless continue_name + continue_name = NameProxy"continue" + @put_name continue_name + continue_name + + build.group body + + Run => + return unless continue_name + last = last_stm body + enclose_lines = types.terminating[last and ntype(last)] + + @put_name continue_name, nil + @splice (lines) -> + lines = {"do", {lines}} if enclose_lines + + { + {"assign", {continue_name}, {"false"}} + {"repeat", "true", { + lines + {"assign", {continue_name}, {"true"}} + }} + {"if", {"not", continue_name}, { + {"break"} + }} + } + } + + +-- this mutates body searching for assigns +extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) => + for i=start,#body + stm = body[i] + continue if stm == nil + stm = @transform.statement stm + body[i] = stm + switch stm[1] + when "assign", "declare" + for name in *stm[2] + if ntype(name) == "ref" + insert out, name + elseif type(name) == "string" + -- TODO: don't use string literal as ref + insert out, name + when "group" + extract_declarations @, stm[2], 1, out + out + +expand_elseif_assign = (ifstm) -> + for i = 4, #ifstm + case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" + split = { unpack ifstm, 1, i - 1 } + insert split, { + "else", { + {"if", case[2], case[3], unpack ifstm, i + 1} + } + } + return split + + ifstm + + +Transformer { + transform: (tuple) => + {_, node, fn} = tuple + fn node + + root_stms: (body) => + transform_last_stm body, implicitly_return @ + + return: (node) => + ret_val = node[2] + ret_val_type = ntype ret_val + + if ret_val_type == "explist" and #ret_val == 2 + ret_val = ret_val[2] + ret_val_type = ntype ret_val + + if types.cascading[ret_val_type] + return implicitly_return(@) ret_val + + -- flatten things that create block exp + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" + -- TODO: clean this up + Value = require "moonscript.transform.value" + ret_val = Value\transform_once @, ret_val + if ntype(ret_val) == "block_exp" + return build.group transform_last_stm ret_val[2], (stm)-> + {"return", stm} + + node[2] = ret_val + node + + declare_glob: (node) => + names = extract_declarations @ + + if node[2] == "^" + names = for name in *names + str_name = if ntype(name) == "ref" + name[2] + else + name + + continue unless str_name\match "^%u" + name + + {"declare", names} + + assign: (node) => + names, values = unpack node, 2 + + num_values = #values + num_names = #values + + -- special code simplifications for single assigns + if num_names == 1 and num_values == 1 + first_value = values[1] + first_name = names[1] + first_type = ntype first_value + + -- reduce colon stub chain to block exp + if first_type == "chain" + -- TODO: clean this up + Value = require "moonscript.transform.value" + first_value = Value\transform_once @, first_value + first_type = ntype first_value + + switch ntype first_value + when "block_exp" + block_body = first_value[2] + idx = #block_body + block_body[idx] = build.assign_one first_name, block_body[idx] + + return build.group { + {"declare", {first_name}} + {"do", block_body} + } + + when "comprehension", "tblcomprehension", "foreach", "for", "while" + -- TODO: clean this up + Value = require "moonscript.transform.value" + return build.assign_one first_name, Value\transform_once @, first_value + else + values[1] = first_value + + -- bubble cascading assigns + transformed = if num_values == 1 + value = values[1] + t = ntype value + + if t == "decorated" + value = @transform.statement value + t = ntype value + + if types.cascading[t] + ret = (stm) -> + if is_value stm + {"assign", names, {stm}} + else + stm + + build.group { + {"declare", names} + @transform.statement value, ret, node + } + + node = transformed or node + + if destructure.has_destructure names + return destructure.split_assign @, node + + node + + continue: (node) => + continue_name = @send "continue" + error "continue must be inside of a loop" unless continue_name + build.group { + build.assign_one continue_name, "true" + {"break"} + } + + export: (node) => + -- assign values if they are included + if #node > 2 + if node[2] == "class" + cls = smart_node node[3] + build.group { + {"export", {cls.name}} + cls + } + else + -- pull out vawlues and assign them after the export + build.group { + { "export", node[2] } + build.assign { + names: node[2] + values: node[3] + } + } + else + nil + + update: (node) => + name, op, exp = unpack node, 2 + op_final = op\match "^(.+)=$" + + error "Unknown op: "..op if not op_final + + local lifted + + if ntype(name) == "chain" + lifted = {} + new_chain = for part in *name[3,] + if ntype(part) == "index" + proxy = NameProxy "update" + table.insert lifted, { proxy, part[2] } + { "index", proxy } + else + part + + if next lifted + name = {name[1], name[2], unpack new_chain} + + exp = {"parens", exp} unless value_is_singular exp + out = build.assign_one name, {"exp", name, op_final, exp} + + if lifted and next lifted + names = [l[1] for l in *lifted] + values = [l[2] for l in *lifted] + + out = build.group { + {"assign", names, values} + out + } + + out + + import: (node) => + names, source = unpack node, 2 + table_values = for name in *names + dest_name = if ntype(name) == "colon" + name[2] + else + name + + {{"key_literal", name}, dest_name} + + dest = { "table", table_values } + { "assign", {dest}, {source}, [-1]: node[-1] } + + comprehension: (node, action) => + exp, clauses = unpack node, 2 + + action = action or (exp) -> {exp} + construct_comprehension action(exp), clauses + + do: (node, ret) => + node[2] = transform_last_stm node[2], ret if ret + node + + decorated: (node) => + stm, dec = unpack node, 2 + + wrapped = switch dec[1] + when "if" + cond, fail = unpack dec, 2 + fail = { "else", { fail } } if fail + { "if", cond, { stm }, fail } + when "unless" + { "unless", dec[2], { stm } } + when "comprehension" + { "comprehension", stm, dec[2] } + else + error "Unknown decorator " .. dec[1] + + if ntype(stm) == "assign" + wrapped = build.group { + build.declare names: [name for name in *stm[2] when ntype(name) == "ref"] + wrapped + } + + wrapped + + unless: (node) => + clause = node[2] + + if ntype(clause) == "assign" + if destructure.has_destructure clause[2] + error "destructure not allowed in unless assignment" + + build.do { + clause + { "if", {"not", clause[2][1]}, unpack node, 3 } + } + + else + { "if", {"not", {"parens", clause}}, unpack node, 3 } + + if: (node, ret) => + -- expand assign in cond + if ntype(node[2]) == "assign" + assign, body = unpack node, 2 + if destructure.has_destructure assign[2] + name = NameProxy "des" + + body = { + destructure.build_assign @, assign[2][1], name + build.group node[3] + } + + return build.do { + build.assign_one name, assign[3][1] + {"if", name, body, unpack node, 4} + } + else + name = assign[2][1] + return build.do { + assign + {"if", name, unpack node, 3} + } + + node = expand_elseif_assign node + + -- apply cascading return decorator + if ret + smart_node node + -- mutate all the bodies + node['then'] = transform_last_stm node['then'], ret + for i = 4, #node + case = node[i] + body_idx = #node[i] + case[body_idx] = transform_last_stm case[body_idx], ret + + node + + with: (node, ret) => + exp, block = unpack node, 2 + + copy_scope = true + local scope_name, named_assign + + if last = last_stm block + ret = false if types.terminating[ntype(last)] + + if ntype(exp) == "assign" + names, values = unpack exp, 2 + first_name = names[1] + + if ntype(first_name) == "ref" + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy "with" + exp = values[1] + values[1] = scope_name + named_assign = {"assign", names, values} + + elseif @is_local exp + scope_name = exp + copy_scope = false + + scope_name or= NameProxy "with" + + out = build.do { + copy_scope and build.assign_one(scope_name, exp) or NOOP + named_assign or NOOP + Run => @set "scope_var", scope_name + unpack block + } + + if ret + table.insert out[2], ret scope_name + + out + + foreach: (node, _) => + smart_node node + source = unpack node.iter + + destructures = {} + node.names = for i, name in ipairs node.names + if ntype(name) == "table" + with proxy = NameProxy "des" + insert destructures, destructure.build_assign @, name, proxy + else + name + + if next destructures + insert destructures, build.group node.body + node.body = destructures + + if ntype(source) == "unpack" + list = source[2] + + index_name = NameProxy "index" + + list_name = @is_local(list) and list or NameProxy "list" + + slice_var = nil + bounds = if is_slice list + slice = list[#list] + table.remove list + table.remove slice, 1 + + list_name = list if @is_local list + + slice[2] = if slice[2] and slice[2] != "" + max_tmp_name = NameProxy "max" + slice_var = build.assign_one max_tmp_name, slice[2] + {"exp", max_tmp_name, "<", 0 + "and", {"length", list_name}, "+", max_tmp_name + "or", max_tmp_name } + else + {"length", list_name} + + slice + else + {1, {"length", list_name}} + + names = [is_name_proxy(n) and n or LocalName(n) or n for n in *node.names] + + return build.group { + list_name != list and build.assign_one(list_name, list) or NOOP + slice_var or NOOP + build["for"] { + name: index_name + bounds: bounds + body: { + {"assign", names, { NameProxy.index list_name, index_name }} + build.group node.body + } + } + } + + node.body = with_continue_listener node.body + + while: (node) => + smart_node node + node.body = with_continue_listener node.body + + for: (node) => + smart_node node + node.body = with_continue_listener node.body + + switch: (node, ret) => + exp, conds = unpack node, 2 + exp_name = NameProxy "exp" + + -- convert switch conds into if statment conds + convert_cond = (cond) -> + t, case_exps, body = unpack cond + out = {} + insert out, t == "case" and "elseif" or "else" + if t != "else" + cond_exp = {} + for i, case in ipairs case_exps + if i == 1 + insert cond_exp, "exp" + else + insert cond_exp, "or" + + case = {"parens", case} unless value_is_singular case + insert cond_exp, {"exp", case, "==", exp_name} + + insert out, cond_exp + else + body = case_exps + + if ret + body = transform_last_stm body, ret + + insert out, body + + out + + first = true + if_stm = {"if"} + for cond in *conds + if_cond = convert_cond cond + if first + first = false + insert if_stm, if_cond[2] + insert if_stm, if_cond[3] + else + insert if_stm, if_cond + + build.group { + build.assign_one exp_name, exp + if_stm + } + + class: require "moonscript.transform.class" + +} diff --git a/prototypes/scp-clicker/moonscript/transform/statements.lua b/prototypes/scp-clicker/moonscript/transform/statements.lua new file mode 100644 index 0000000..6a49b77 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/statements.lua @@ -0,0 +1,115 @@ +local types = require("moonscript.types") +local ntype, mtype, is_value, NOOP +ntype, mtype, is_value, NOOP = types.ntype, types.mtype, types.is_value, types.NOOP +local comprehension_has_value +comprehension_has_value = require("moonscript.transform.comprehension").comprehension_has_value +local Run +do + local _class_0 + local _base_0 = { + call = function(self, state) + return self.fn(state) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, fn) + self.fn = fn + self[1] = "run" + end, + __base = _base_0, + __name = "Run" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Run = _class_0 +end +local last_stm +last_stm = function(stms) + local last_exp_id = 0 + for i = #stms, 1, -1 do + local stm = stms[i] + if stm and mtype(stm) ~= Run then + if ntype(stm) == "group" then + return last_stm(stm[2]) + end + last_exp_id = i + break + end + end + return stms[last_exp_id], last_exp_id, stms +end +local transform_last_stm +transform_last_stm = function(stms, fn) + local _, last_idx, _stms = last_stm(stms) + if _stms ~= stms then + error("cannot transform last node in group") + end + return (function() + local _accum_0 = { } + local _len_0 = 1 + for i, stm in ipairs(stms) do + if i == last_idx then + _accum_0[_len_0] = { + "transform", + stm, + fn + } + else + _accum_0[_len_0] = stm + end + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() +end +local chain_is_stub +chain_is_stub = function(chain) + local stub = chain[#chain] + return stub and ntype(stub) == "colon" +end +local implicitly_return +implicitly_return = function(scope) + local is_top = true + local fn + fn = function(stm) + local t = ntype(stm) + if t == "decorated" then + stm = scope.transform.statement(stm) + t = ntype(stm) + end + if types.cascading[t] then + is_top = false + return scope.transform.statement(stm, fn) + elseif types.manual_return[t] or not is_value(stm) then + if is_top and t == "return" and stm[2] == "" then + return NOOP + else + return stm + end + else + if t == "comprehension" and not comprehension_has_value(stm) then + return stm + else + return { + "return", + stm + } + end + end + end + return fn +end +return { + Run = Run, + last_stm = last_stm, + transform_last_stm = transform_last_stm, + chain_is_stub = chain_is_stub, + implicitly_return = implicitly_return +} diff --git a/prototypes/scp-clicker/moonscript/transform/statements.moon b/prototypes/scp-clicker/moonscript/transform/statements.moon new file mode 100644 index 0000000..164e6b5 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/statements.moon @@ -0,0 +1,78 @@ + +types = require "moonscript.types" +import ntype, mtype, is_value, NOOP from types + +import comprehension_has_value from require "moonscript.transform.comprehension" + +-- A Run is a special statement node that lets a function run and mutate the +-- state of the compiler +class Run + new: (@fn) => + @[1] = "run" + + call: (state) => + @.fn state + +-- extract the last statment from an array of statements +-- is group aware +-- returns: the last statement, the index, the table it was fetched from +last_stm = (stms) -> + last_exp_id = 0 + for i = #stms, 1, -1 + stm = stms[i] + if stm and mtype(stm) != Run + if ntype(stm) == "group" + return last_stm stm[2] + + last_exp_id = i + break + + stms[last_exp_id], last_exp_id, stms + +-- transform the last stm is a list of stms +-- will puke on group +transform_last_stm = (stms, fn) -> + _, last_idx, _stms = last_stm stms + + if _stms != stms + error "cannot transform last node in group" + + return for i, stm in ipairs stms + if i == last_idx + {"transform", stm, fn} + else + stm + +chain_is_stub = (chain) -> + stub = chain[#chain] + stub and ntype(stub) == "colon" + +implicitly_return = (scope) -> + is_top = true + fn = (stm) -> + t = ntype stm + + -- expand decorated + if t == "decorated" + stm = scope.transform.statement stm + t = ntype stm + + if types.cascading[t] + is_top = false + scope.transform.statement stm, fn + elseif types.manual_return[t] or not is_value stm + -- remove blank return statement + if is_top and t == "return" and stm[2] == "" + NOOP + else + stm + else + if t == "comprehension" and not comprehension_has_value stm + stm + else + {"return", stm} + + fn + +{:Run, :last_stm, :transform_last_stm, :chain_is_stub, :implicitly_return } + diff --git a/prototypes/scp-clicker/moonscript/transform/transformer.lua b/prototypes/scp-clicker/moonscript/transform/transformer.lua new file mode 100644 index 0000000..4ea1695 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/transformer.lua @@ -0,0 +1,74 @@ +local ntype +ntype = require("moonscript.types").ntype +local Transformer +do + local _class_0 + local _base_0 = { + transform_once = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + local transformer = self.transformers[ntype(node)] + if transformer then + return transformer(scope, node, ...) or node + else + return node + end + end, + transform = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + while true do + local transformer = self.transformers[ntype(node)] + local res + if transformer then + res = transformer(scope, node, ...) or node + else + res = node + end + if res == node then + return node + end + node = res + end + return node + end, + bind = function(self, scope) + return function(...) + return self:transform(scope, ...) + end + end, + __call = function(self, ...) + return self:transform(...) + end, + can_transform = function(self, node) + return self.transformers[ntype(node)] ~= nil + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, transformers) + self.transformers = transformers + self.seen_nodes = setmetatable({ }, { + __mode = "k" + }) + end, + __base = _base_0, + __name = "Transformer" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Transformer = _class_0 +end +return { + Transformer = Transformer +} diff --git a/prototypes/scp-clicker/moonscript/transform/transformer.moon b/prototypes/scp-clicker/moonscript/transform/transformer.moon new file mode 100644 index 0000000..8a40c3c --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/transformer.moon @@ -0,0 +1,42 @@ +import ntype from require "moonscript.types" + +class Transformer + new: (@transformers) => + @seen_nodes = setmetatable {}, __mode: "k" + + transform_once: (scope, node, ...) => + return node if @seen_nodes[node] + @seen_nodes[node] = true + + transformer = @transformers[ntype node] + if transformer + transformer(scope, node, ...) or node + else + node + + transform: (scope, node, ...) => + return node if @seen_nodes[node] + + @seen_nodes[node] = true + while true + transformer = @transformers[ntype node] + res = if transformer + transformer(scope, node, ...) or node + else + node + + return node if res == node + node = res + + node + + bind: (scope) => + (...) -> @transform scope, ... + + __call: (...) => @transform ... + + can_transform: (node) => + @transformers[ntype node] != nil + + +{ :Transformer } diff --git a/prototypes/scp-clicker/moonscript/transform/value.lua b/prototypes/scp-clicker/moonscript/transform/value.lua new file mode 100644 index 0000000..33aaef9 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/value.lua @@ -0,0 +1,265 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local build, ntype, smart_node +do + local _obj_0 = require("moonscript.types") + build, ntype, smart_node = _obj_0.build, _obj_0.ntype, _obj_0.smart_node +end +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local Accumulator, default_accumulator +do + local _obj_0 = require("moonscript.transform.accumulator") + Accumulator, default_accumulator = _obj_0.Accumulator, _obj_0.default_accumulator +end +local lua_keywords +lua_keywords = require("moonscript.data").lua_keywords +local Run, transform_last_stm, implicitly_return, chain_is_stub +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, chain_is_stub = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.chain_is_stub +end +local construct_comprehension +construct_comprehension = require("moonscript.transform.comprehension").construct_comprehension +local insert +insert = table.insert +local unpack +unpack = require("moonscript.util").unpack +return Transformer({ + ["for"] = default_accumulator, + ["while"] = default_accumulator, + foreach = default_accumulator, + ["do"] = function(self, node) + return build.block_exp(node[2]) + end, + decorated = function(self, node) + return self.transform.statement(node) + end, + class = function(self, node) + return build.block_exp({ + node + }) + end, + string = function(self, node) + local delim = node[2] + local convert_part + convert_part = function(part) + if type(part) == "string" or part == nil then + return { + "string", + delim, + part or "" + } + else + return build.chain({ + base = "tostring", + { + "call", + { + part[2] + } + } + }) + end + end + if #node <= 3 then + if type(node[3]) == "string" then + return node + else + return convert_part(node[3]) + end + end + local e = { + "exp", + convert_part(node[3]) + } + for i = 4, #node do + insert(e, "..") + insert(e, convert_part(node[i])) + end + return e + end, + comprehension = function(self, node) + local a = Accumulator() + node = self.transform.statement(node, function(exp) + return a:mutate_body({ + exp + }) + end) + return a:wrap(node) + end, + tblcomprehension = function(self, node) + local explist, clauses = unpack(node, 2) + local key_exp, value_exp = unpack(explist) + local accum = NameProxy("tbl") + local inner + if value_exp then + local dest = build.chain({ + base = accum, + { + "index", + key_exp + } + }) + inner = { + build.assign_one(dest, value_exp) + } + else + local key_name, val_name = NameProxy("key"), NameProxy("val") + local dest = build.chain({ + base = accum, + { + "index", + key_name + } + }) + inner = { + build.assign({ + names = { + key_name, + val_name + }, + values = { + key_exp + } + }), + build.assign_one(dest, val_name) + } + end + return build.block_exp({ + build.assign_one(accum, build.table()), + construct_comprehension(inner, clauses), + accum + }) + end, + fndef = function(self, node) + smart_node(node) + node.body = transform_last_stm(node.body, implicitly_return(self)) + node.body = { + Run(function(self) + return self:listen("varargs", function() end) + end), + unpack(node.body) + } + return node + end, + ["if"] = function(self, node) + return build.block_exp({ + node + }) + end, + unless = function(self, node) + return build.block_exp({ + node + }) + end, + with = function(self, node) + return build.block_exp({ + node + }) + end, + switch = function(self, node) + return build.block_exp({ + node + }) + end, + chain = function(self, node) + for i = 2, #node do + local part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] then + node[i] = { + "index", + { + "string", + '"', + part[2] + } + } + end + end + if ntype(node[2]) == "string" then + node[2] = { + "parens", + node[2] + } + end + if chain_is_stub(node) then + local base_name = NameProxy("base") + local fn_name = NameProxy("fn") + local colon = table.remove(node) + local is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + return build.block_exp({ + build.assign({ + names = { + base_name + }, + values = { + node + } + }), + build.assign({ + names = { + fn_name + }, + values = { + build.chain({ + base = base_name, + { + "dot", + colon[2] + } + }) + } + }), + build.fndef({ + args = { + { + "..." + } + }, + body = { + build.chain({ + base = fn_name, + { + "call", + { + is_super and "self" or base_name, + "..." + } + } + }) + } + }) + }) + end + end, + block_exp = function(self, node) + local body = unpack(node, 2) + local fn = nil + local arg_list = { } + fn = smart_node(build.fndef({ + body = { + Run(function(self) + return self:listen("varargs", function() + insert(arg_list, "...") + insert(fn.args, { + "..." + }) + return self:unlisten("varargs") + end) + end), + unpack(body) + } + })) + return build.chain({ + base = { + "parens", + fn + }, + { + "call", + arg_list + } + }) + end +}) diff --git a/prototypes/scp-clicker/moonscript/transform/value.moon b/prototypes/scp-clicker/moonscript/transform/value.moon new file mode 100644 index 0000000..04e16ae --- /dev/null +++ b/prototypes/scp-clicker/moonscript/transform/value.moon @@ -0,0 +1,164 @@ +import Transformer from require "moonscript.transform.transformer" +import build, ntype, smart_node from require "moonscript.types" + +import NameProxy from require "moonscript.transform.names" +import Accumulator, default_accumulator from require "moonscript.transform.accumulator" +import lua_keywords from require "moonscript.data" + +import Run, transform_last_stm, implicitly_return, chain_is_stub from require "moonscript.transform.statements" + +import construct_comprehension from require "moonscript.transform.comprehension" + +import insert from table +import unpack from require "moonscript.util" + +Transformer { + for: default_accumulator + while: default_accumulator + foreach: default_accumulator + + do: (node) => + build.block_exp node[2] + + decorated: (node) => + @transform.statement node + + class: (node) => + build.block_exp { node } + + string: (node) => + delim = node[2] + + convert_part = (part) -> + if type(part) == "string" or part == nil + {"string", delim, part or ""} + else + build.chain { base: "tostring", {"call", {part[2]}} } + + -- reduced to single item + if #node <= 3 + return if type(node[3]) == "string" + node + else + convert_part node[3] + + e = {"exp", convert_part node[3]} + + for i=4, #node + insert e, ".." + insert e, convert_part node[i] + e + + comprehension: (node) => + a = Accumulator! + node = @transform.statement node, (exp) -> + a\mutate_body {exp} + a\wrap node + + tblcomprehension: (node) => + explist, clauses = unpack node, 2 + key_exp, value_exp = unpack explist + + accum = NameProxy "tbl" + + inner = if value_exp + dest = build.chain { base: accum, {"index", key_exp} } + { build.assign_one dest, value_exp } + else + -- If we only have single expression then + -- unpack the result into key and value + key_name, val_name = NameProxy"key", NameProxy"val" + dest = build.chain { base: accum, {"index", key_name} } + { + build.assign names: {key_name, val_name}, values: {key_exp} + build.assign_one dest, val_name + } + + build.block_exp { + build.assign_one accum, build.table! + construct_comprehension inner, clauses + accum + } + + fndef: (node) => + smart_node node + node.body = transform_last_stm node.body, implicitly_return self + node.body = { + Run => @listen "varargs", -> -- capture event + unpack node.body + } + + node + + if: (node) => + build.block_exp { node } + + unless: (node) => + build.block_exp { node } + + with: (node) => + build.block_exp { node } + + switch: (node) => + build.block_exp { node } + + -- pull out colon chain + chain: (node) => + -- escape lua keywords used in dot accessors + for i=2,#node + part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] + node[i] = { "index", {"string", '"', part[2]} } + + if ntype(node[2]) == "string" + -- add parens if callee is raw string + node[2] = {"parens", node[2] } + + if chain_is_stub node + base_name = NameProxy "base" + fn_name = NameProxy "fn" + colon = table.remove node + + is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + build.block_exp { + build.assign { + names: {base_name} + values: {node} + } + + build.assign { + names: {fn_name} + values: { + build.chain { base: base_name, {"dot", colon[2]} } + } + } + + build.fndef { + args: {{"..."}} + body: { + build.chain { + base: fn_name, {"call", {is_super and "self" or base_name, "..."}} + } + } + } + } + + block_exp: (node) => + body = unpack node, 2 + + fn = nil + arg_list = {} + + fn = smart_node build.fndef body: { + Run => + @listen "varargs", -> + insert arg_list, "..." + insert fn.args, {"..."} + @unlisten "varargs" + + unpack body + } + + build.chain { base: {"parens", fn}, {"call", arg_list} } +} + diff --git a/prototypes/scp-clicker/moonscript/types.lua b/prototypes/scp-clicker/moonscript/types.lua new file mode 100644 index 0000000..e8efc40 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/types.lua @@ -0,0 +1,323 @@ +local util = require("moonscript.util") +local Set +Set = require("moonscript.data").Set +local insert +insert = table.insert +local unpack +unpack = util.unpack +local manual_return = Set({ + "foreach", + "for", + "while", + "return" +}) +local cascading = Set({ + "if", + "unless", + "with", + "switch", + "class", + "do" +}) +local terminating = Set({ + "return", + "break" +}) +local ntype +ntype = function(node) + local _exp_0 = type(node) + if "nil" == _exp_0 then + return "nil" + elseif "table" == _exp_0 then + return node[1] + else + return "value" + end +end +local mtype +do + local moon_type = util.moon.type + mtype = function(val) + local mt = getmetatable(val) + if mt and mt.smart_node then + return "table" + end + return moon_type(val) + end +end +local value_can_be_statement +value_can_be_statement = function(node) + if not (ntype(node) == "chain") then + return false + end + return ntype(node[#node]) == "call" +end +local is_value +is_value = function(stm) + local compile = require("moonscript.compile") + local transform = require("moonscript.transform") + return compile.Block:is_value(stm) or transform.Value:can_transform(stm) +end +local value_is_singular +value_is_singular = function(node) + return type(node) ~= "table" or node[1] ~= "exp" or #node == 2 +end +local is_slice +is_slice = function(node) + return ntype(node) == "chain" and ntype(node[#node]) == "slice" +end +local t = { } +local node_types = { + class = { + { + "name", + "Tmp" + }, + { + "body", + t + } + }, + fndef = { + { + "args", + t + }, + { + "whitelist", + t + }, + { + "arrow", + "slim" + }, + { + "body", + t + } + }, + foreach = { + { + "names", + t + }, + { + "iter" + }, + { + "body", + t + } + }, + ["for"] = { + { + "name" + }, + { + "bounds", + t + }, + { + "body", + t + } + }, + ["while"] = { + { + "cond", + t + }, + { + "body", + t + } + }, + assign = { + { + "names", + t + }, + { + "values", + t + } + }, + declare = { + { + "names", + t + } + }, + ["if"] = { + { + "cond", + t + }, + { + "then", + t + } + } +} +local build_table +build_table = function() + local key_table = { } + for node_name, args in pairs(node_types) do + local index = { } + for i, tuple in ipairs(args) do + local prop_name = tuple[1] + index[prop_name] = i + 1 + end + key_table[node_name] = index + end + return key_table +end +local key_table = build_table() +local make_builder +make_builder = function(name) + local spec = node_types[name] + if not spec then + error("don't know how to build node: " .. name) + end + return function(props) + if props == nil then + props = { } + end + local node = { + name + } + for i, arg in ipairs(spec) do + local key, default_value = unpack(arg) + local val + if props[key] then + val = props[key] + else + val = default_value + end + if val == t then + val = { } + end + node[i + 1] = val + end + return node + end +end +local build = nil +build = setmetatable({ + group = function(body) + if body == nil then + body = { } + end + return { + "group", + body + } + end, + ["do"] = function(body) + return { + "do", + body + } + end, + assign_one = function(name, value) + return build.assign({ + names = { + name + }, + values = { + value + } + }) + end, + table = function(tbl) + if tbl == nil then + tbl = { } + end + for _index_0 = 1, #tbl do + local tuple = tbl[_index_0] + if type(tuple[1]) == "string" then + tuple[1] = { + "key_literal", + tuple[1] + } + end + end + return { + "table", + tbl + } + end, + block_exp = function(body) + return { + "block_exp", + body + } + end, + chain = function(parts) + local base = parts.base or error("expecting base property for chain") + if type(base) == "string" then + base = { + "ref", + base + } + end + local node = { + "chain", + base + } + for _index_0 = 1, #parts do + local part = parts[_index_0] + insert(node, part) + end + return node + end +}, { + __index = function(self, name) + self[name] = make_builder(name) + return rawget(self, name) + end +}) +local smart_node_mt = setmetatable({ }, { + __index = function(self, node_type) + local index = key_table[node_type] + local mt = { + smart_node = true, + __index = function(node, key) + if index[key] then + return rawget(node, index[key]) + elseif type(key) == "string" then + return error("unknown key: `" .. key .. "` on node type: `" .. ntype(node) .. "`") + end + end, + __newindex = function(node, key, value) + if index[key] then + key = index[key] + end + return rawset(node, key, value) + end + } + self[node_type] = mt + return mt + end +}) +local smart_node +smart_node = function(node) + return setmetatable(node, smart_node_mt[ntype(node)]) +end +local NOOP = { + "noop" +} +return { + ntype = ntype, + smart_node = smart_node, + build = build, + is_value = is_value, + is_slice = is_slice, + manual_return = manual_return, + cascading = cascading, + value_is_singular = value_is_singular, + value_can_be_statement = value_can_be_statement, + mtype = mtype, + terminating = terminating, + NOOP = NOOP +} diff --git a/prototypes/scp-clicker/moonscript/types.moon b/prototypes/scp-clicker/moonscript/types.moon new file mode 100644 index 0000000..23f4ed9 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/types.moon @@ -0,0 +1,197 @@ + +util = require "moonscript.util" +import Set from require "moonscript.data" + +import insert from table +import unpack from util + +-- implicit return does not work on these statements +manual_return = Set { + "foreach", "for", "while", "return" +} + +-- Assigns and returns are bubbled into their bodies. +-- All cascading statement transform functions accept a second arugment that +-- is the transformation to apply to the last statement in their body +cascading = Set { + "if", "unless", "with", "switch", "class", "do" +} + +terminating = Set { + "return", "break" +} + +-- type of node as string +ntype = (node) -> + switch type node + when "nil" + "nil" + when "table" + node[1] + else + "value" + +-- gets the class of a type if possible +mtype = do + moon_type = util.moon.type + -- lets us check a smart node without throwing an error + (val) -> + mt = getmetatable val + return "table" if mt and mt.smart_node + moon_type val + +-- can this value be compiled in a line by itself +value_can_be_statement = (node) -> + return false unless ntype(node) == "chain" + -- it's a function call + ntype(node[#node]) == "call" + +is_value = (stm) -> + compile = require "moonscript.compile" + transform = require "moonscript.transform" + + compile.Block\is_value(stm) or transform.Value\can_transform stm + +value_is_singular = (node) -> + type(node) != "table" or node[1] != "exp" or #node == 2 + +is_slice = (node) -> + ntype(node) == "chain" and ntype(node[#node]) == "slice" + +t = {} +node_types = { + class: { + {"name", "Tmp"} + {"body", t} + } + fndef: { + {"args", t} + {"whitelist", t} + {"arrow", "slim"} + {"body", t} + } + foreach: { + {"names", t} + {"iter"} + {"body", t} + } + for: { + {"name"} + {"bounds", t} + {"body", t} + } + while: { + {"cond", t} + {"body", t} + } + assign: { + {"names", t} + {"values", t} + } + declare: { + {"names", t} + } + if: { + {"cond", t} + {"then", t} + } +} + +build_table = -> + key_table = {} + for node_name, args in pairs node_types + index = {} + for i, tuple in ipairs args + prop_name = tuple[1] + index[prop_name] = i + 1 + key_table[node_name] = index + key_table + +key_table = build_table! + + +make_builder = (name) -> + spec = node_types[name] + error "don't know how to build node: "..name if not spec + (props={}) -> + node = { name } + for i, arg in ipairs spec + key, default_value = unpack arg + val = if props[key] then props[key] else default_value + val = {} if val == t + node[i + 1] = val + node + +build = nil +build = setmetatable { + group: (body={}) -> + {"group", body} + + do: (body) -> + {"do", body} + + assign_one: (name, value) -> + build.assign { + names: {name} + values: {value} + } + + table: (tbl={}) -> + -- convert strings to key literals + for tuple in *tbl + if type(tuple[1]) == "string" + tuple[1] = {"key_literal", tuple[1]} + + {"table", tbl} + block_exp: (body) -> + {"block_exp", body} + + chain: (parts) -> + base = parts.base or error"expecting base property for chain" + + if type(base) == "string" + base = {"ref", base} + + node = {"chain", base} + for part in *parts + insert node, part + node +}, { + __index: (name) => + self[name] = make_builder name + rawget self, name +} + +smart_node_mt = setmetatable {}, { + __index: (node_type) => + index = key_table[node_type] + mt = { + smart_node: true + + __index: (node, key) -> + if index[key] + rawget node, index[key] + elseif type(key) == "string" + error "unknown key: `"..key.."` on node type: `"..ntype(node).. "`" + + __newindex: (node, key, value) -> + key = index[key] if index[key] + rawset node, key, value + } + self[node_type] = mt + mt +} + +-- makes it so node properties can be accessed by name instead of index +smart_node = (node) -> + setmetatable node, smart_node_mt[ntype node] + +NOOP = {"noop"} + +{ + :ntype, :smart_node, :build, :is_value, :is_slice, :manual_return, + :cascading, :value_is_singular, + :value_can_be_statement, :mtype, :terminating + :NOOP +} + diff --git a/prototypes/scp-clicker/moonscript/util.lua b/prototypes/scp-clicker/moonscript/util.lua new file mode 100644 index 0000000..bde0d57 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/util.lua @@ -0,0 +1,213 @@ +local concat +concat = table.concat +local unpack = unpack or table.unpack +local type = type +local moon = { + is_object = function(value) + return type(value) == "table" and value.__class + end, + is_a = function(thing, t) + if not (type(thing) == "table") then + return false + end + local cls = thing.__class + while cls do + if cls == t then + return true + end + cls = cls.__parent + end + return false + end, + type = function(value) + local base_type = type(value) + if base_type == "table" then + local cls = value.__class + if cls then + return cls + end + end + return base_type + end +} +local pos_to_line +pos_to_line = function(str, pos) + local line = 1 + for _ in str:sub(1, pos):gmatch("\n") do + line = line + 1 + end + return line +end +local trim +trim = function(str) + return str:match("^%s*(.-)%s*$") +end +local get_line +get_line = function(str, line_num) + for line in str:gmatch("([^\n]*)\n?") do + if line_num == 1 then + return line + end + line_num = line_num - 1 + end +end +local get_closest_line +get_closest_line = function(str, line_num) + local line = get_line(str, line_num) + if (not line or trim(line) == "") and line_num > 1 then + return get_closest_line(str, line_num - 1) + else + return line, line_num + end +end +local split +split = function(str, delim) + if str == "" then + return { } + end + str = str .. delim + local _accum_0 = { } + local _len_0 = 1 + for m in str:gmatch("(.-)" .. delim) do + _accum_0[_len_0] = m + _len_0 = _len_0 + 1 + end + return _accum_0 +end +local dump +dump = function(what) + local seen = { } + local _dump + _dump = function(what, depth) + if depth == nil then + depth = 0 + end + local t = type(what) + if t == "string" then + return '"' .. what .. '"\n' + elseif t == "table" then + if seen[what] then + return "recursion(" .. tostring(what) .. ")...\n" + end + seen[what] = true + depth = depth + 1 + local lines + do + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(what) do + _accum_0[_len_0] = (" "):rep(depth * 4) .. "[" .. tostring(k) .. "] = " .. _dump(v, depth) + _len_0 = _len_0 + 1 + end + lines = _accum_0 + end + seen[what] = false + return "{\n" .. concat(lines) .. (" "):rep((depth - 1) * 4) .. "}\n" + else + return tostring(what) .. "\n" + end + end + return _dump(what) +end +local debug_posmap +debug_posmap = function(posmap, moon_code, lua_code) + local tuples + do + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(posmap) do + _accum_0[_len_0] = { + k, + v + } + _len_0 = _len_0 + 1 + end + tuples = _accum_0 + end + table.sort(tuples, function(a, b) + return a[1] < b[1] + end) + local lines + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #tuples do + local pair = tuples[_index_0] + local lua_line, pos = unpack(pair) + local moon_line = pos_to_line(moon_code, pos) + local lua_text = get_line(lua_code, lua_line) + local moon_text = get_closest_line(moon_code, moon_line) + local _value_0 = tostring(pos) .. "\t " .. tostring(lua_line) .. ":[ " .. tostring(trim(lua_text)) .. " ] >> " .. tostring(moon_line) .. ":[ " .. tostring(trim(moon_text)) .. " ]" + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + lines = _accum_0 + end + return concat(lines, "\n") +end +local setfenv = setfenv or function(fn, env) + local name + local i = 1 + while true do + name = debug.getupvalue(fn, i) + if not name or name == "_ENV" then + break + end + i = i + 1 + end + if name then + debug.upvaluejoin(fn, i, (function() + return env + end), 1) + end + return fn +end +local getfenv = getfenv or function(fn) + local i = 1 + while true do + local name, val = debug.getupvalue(fn, i) + if not (name) then + break + end + if name == "_ENV" then + return val + end + i = i + 1 + end + return nil +end +local get_options +get_options = function(...) + local count = select("#", ...) + local opts = select(count, ...) + if type(opts) == "table" then + return opts, unpack({ + ... + }, nil, count - 1) + else + return { }, ... + end +end +local safe_module +safe_module = function(name, tbl) + return setmetatable(tbl, { + __index = function(self, key) + return error("Attempted to import non-existent `" .. tostring(key) .. "` from " .. tostring(name)) + end + }) +end +return { + moon = moon, + pos_to_line = pos_to_line, + get_closest_line = get_closest_line, + get_line = get_line, + trim = trim, + split = split, + dump = dump, + debug_posmap = debug_posmap, + getfenv = getfenv, + setfenv = setfenv, + get_options = get_options, + unpack = unpack, + safe_module = safe_module +} diff --git a/prototypes/scp-clicker/moonscript/util.moon b/prototypes/scp-clicker/moonscript/util.moon new file mode 100644 index 0000000..ea7c8a5 --- /dev/null +++ b/prototypes/scp-clicker/moonscript/util.moon @@ -0,0 +1,137 @@ + +import concat from table + +unpack = unpack or table.unpack +type = type + +moon = + is_object: (value) -> -- is a moonscript object + type(value) == "table" and value.__class + + is_a: (thing, t) -> + return false unless type(thing) == "table" + cls = thing.__class + while cls + if cls == t + return true + cls = cls.__parent + + false + + type: (value) -> -- the moonscript object class + base_type = type value + if base_type == "table" + cls = value.__class + return cls if cls + base_type + +-- convet position in text to line number +pos_to_line = (str, pos) -> + line = 1 + for _ in str\sub(1, pos)\gmatch("\n") + line += 1 + line + +trim = (str) -> + str\match "^%s*(.-)%s*$" + +get_line = (str, line_num) -> + -- todo: this returns an extra blank line at the end + for line in str\gmatch "([^\n]*)\n?" + return line if line_num == 1 + line_num -= 1 + +get_closest_line = (str, line_num) -> + line = get_line str, line_num + if (not line or trim(line) == "") and line_num > 1 + get_closest_line(str, line_num - 1) + else + line, line_num + +split = (str, delim) -> + return {} if str == "" + str ..= delim + [m for m in str\gmatch("(.-)"..delim)] + +dump = (what) -> + seen = {} + _dump = (what, depth=0) -> + t = type what + if t == "string" + '"'..what..'"\n' + elseif t == "table" + if seen[what] + return "recursion("..tostring(what) ..")...\n" + seen[what] = true + + depth += 1 + lines = for k,v in pairs what + (" ")\rep(depth*4).."["..tostring(k).."] = ".._dump(v, depth) + + seen[what] = false + + "{\n" .. concat(lines) .. (" ")\rep((depth - 1)*4) .. "}\n" + else + tostring(what).."\n" + + _dump what + + +debug_posmap = (posmap, moon_code, lua_code) -> + tuples = [{k, v} for k, v in pairs posmap] + table.sort tuples, (a, b) -> a[1] < b[1] + + lines = for pair in *tuples + lua_line, pos = unpack pair + moon_line = pos_to_line moon_code, pos + + lua_text = get_line lua_code, lua_line + moon_text = get_closest_line moon_code, moon_line + + "#{pos}\t #{lua_line}:[ #{trim lua_text} ] >> #{moon_line}:[ #{trim moon_text} ]" + + concat(lines, "\n") + +setfenv = setfenv or (fn, env) -> + local name + i = 1 + while true + name = debug.getupvalue fn, i + break if not name or name == "_ENV" + i += 1 + + if name + debug.upvaluejoin fn, i, (-> env), 1 + + fn + +getfenv = getfenv or (fn) -> + i = 1 + while true + name, val = debug.getupvalue fn, i + break unless name + return val if name == "_ENV" + i += 1 + nil + +-- moves the last argument to the front if it's a table, or returns empty table +-- inserted to the front of args +get_options = (...) -> + count = select "#", ... + opts = select count, ... + if type(opts) == "table" + opts, unpack {...}, nil, count - 1 + else + {}, ... + +safe_module = (name, tbl) -> + setmetatable tbl, { + __index: (key) => + error "Attempted to import non-existent `#{key}` from #{name}" + } + +{ + :moon, :pos_to_line, :get_closest_line, :get_line, :trim, :split, :dump, + :debug_posmap, :getfenv, :setfenv, :get_options, :unpack, :safe_module +} + diff --git a/prototypes/scp-clicker/moonscript/version.lua b/prototypes/scp-clicker/moonscript/version.lua new file mode 100644 index 0000000..91f885a --- /dev/null +++ b/prototypes/scp-clicker/moonscript/version.lua @@ -0,0 +1,7 @@ +local version = "0.5.0" +return { + version = version, + print_version = function() + return print("MoonScript version " .. tostring(version)) + end +} diff --git a/prototypes/scp-clicker/moonscript/version.moon b/prototypes/scp-clicker/moonscript/version.moon new file mode 100644 index 0000000..68c483e --- /dev/null +++ b/prototypes/scp-clicker/moonscript/version.moon @@ -0,0 +1,8 @@ + +version = "0.5.0" + +{ + version: version, + print_version: -> + print "MoonScript version #{version}" +} diff --git a/prototypes/scp-clicker/slab/init.lua b/prototypes/scp-clicker/slab/init.lua new file mode 100644 index 0000000..479c007 --- /dev/null +++ b/prototypes/scp-clicker/slab/init.lua @@ -0,0 +1,8 @@ +local path = ... +if path:sub(-4) == "init" then + path = path:sub(1, -6) + if not (path) then + path = "." + end +end +return require(tostring(path) .. "/slab") diff --git a/prototypes/scp-clicker/slab/init.moon b/prototypes/scp-clicker/slab/init.moon new file mode 100644 index 0000000..70729f0 --- /dev/null +++ b/prototypes/scp-clicker/slab/init.moon @@ -0,0 +1,7 @@ +path = ... + +if path\sub(-4) == "init" + path = path\sub 1, -6 + path = "." unless path + +return require "#{path}/slab" diff --git a/prototypes/scp-clicker/slab/slab.lua b/prototypes/scp-clicker/slab/slab.lua new file mode 100644 index 0000000..d0aaf62 --- /dev/null +++ b/prototypes/scp-clicker/slab/slab.lua @@ -0,0 +1,180 @@ +local path = (...):sub(1, -(("slab"):len() + 2)) +local graphics +graphics = love.graphics +local slab = { + _VERSION = "0.1.0", + _DESCRIPTION = "GUI library for LÖVE.", + _URL = nil, + _LICENSE = "The MIT License (MIT)", + _AUTHOR = "Paul Liverman III" +} +slab.ui = { + x = 0, + y = 0, + w = graphics.getWidth(), + h = graphics.getHeight() +} +slab.hovered = false +slab.focused = false +slab.types = { + "menu", + "spinner", + "text" +} +slab.generic = require(tostring(path) .. "/types/generic") +local _list_0 = slab.types +for _index_0 = 1, #_list_0 do + local t = _list_0[_index_0] + slab[t] = require(tostring(path) .. "/types/" .. tostring(t)) +end +slab.make = function(element, parent) + if element == nil then + element = { } + end + if parent == nil then + parent = slab.ui + end + local _list_1 = slab.types + for _index_0 = 1, #_list_1 do + local t = _list_1[_index_0] + if element[t] then + return slab[t](element, parent) + end + end + return slab.generic(element, parent) +end +slab.update = function(dt, element) + if element == nil then + element = slab.ui + end + if element.update then + element:update(dt) + end + for _index_0 = 1, #element do + local child = element[_index_0] + slab.update(dt, child) + end +end +slab.draw = function(element) + if element == nil then + element = slab.ui + end + if element == slab.ui then + graphics.push("all") + graphics.origin() + end + local drawChildren + if element.visible and element.draw then + drawChildren = element:draw() + end + if drawChildren ~= false then + for _index_0 = 1, #element do + local child = element[_index_0] + slab.draw(child) + end + end + if element == slab.ui then + return graphics.pop() + end +end +slab.mousemoved = function(x, y, dx, dy, element) + if element == nil then + element = slab.ui + end + local previous + if element == slab.ui then + previous = slab.hovered + end + if element.visible and element.hoverable and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + slab.hovered = element + for _index_0 = 1, #element do + local child = element[_index_0] + slab.mousemoved(x, y, dx, dy, child) + end + end + if element == slab.ui then + if slab.hovered ~= previous then + if previous and previous.hovered then + previous:hovered(false) + end + if slab.hovered.hovered then + slab.hovered:hovered(true) + end + end + if slab.focused and slab.focused.mousemoved then + return slab.focused:mousemoved(x - slab.focused.x, y - slab.focused.y, dx, dy) + end + end +end +slab.mousepressed = function(x, y, btn, element) + if element == nil then + element = screen.ui + end + if element == screen.ui then + if btn == "wd" then + return slab.wheelmoved(0, -1) + elseif btn == "wu" then + return slab.wheelmoved(0, 1) + end + end + local handled = false + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + for i = #element, 1, -1 do + handled = slab.mousepressed(x, y, btn, element[i]) + if handled ~= false then + return handled + end + end + if element.mousepressed then + handled = element:mousepressed(btn, x, y) + end + end + return handled +end +slab.mousereleased = function(x, y, btn, element) + local clickHandled, mousereleaseHandled = false, false + if element then + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + for i = #element, 1, -1 do + clickHandled, mousereleaseHandled = slab.mousereleased(x, y, btn, element[i]) + if clickHandled ~= false or mousereleaseHandled ~= false then + return clickHandled, mousereleaseHandled + end + end + if element.clicked then + clickHandled = element:clicked(btn, x - element.x, y - element.y) + end + if element.mousereleased then + mousereleaseHandled = element:mousereleased(btn, x - element.x, y - element.y) + end + if clickHandled then + slab.focused = element + end + end + else + do + element = slab.focused + if element then + if element.mousereleased then + mousereleaseHandled = element:mousereleased(btn, x - element.x, y - element.y) + end + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h then + if element.clicked then + clickHandled = element:clicked(btn, x - element.x, y - element.y) + end + end + if clickHandled ~= false or mousereleaseHandled ~= false then + return clickHandled, mousereleaseHandled + end + return slab.mousereleased(x, y, btn, slab.ui) + end + end + end +end +slab.wheelmoved = function(x, y) + if slab.hovered and slab.hovered.wheelmoved then + return slab.hovered:wheelmoved(x, y) + end + return false +end +return slab diff --git a/prototypes/scp-clicker/slab/slab.moon b/prototypes/scp-clicker/slab/slab.moon new file mode 100644 index 0000000..7bbda5c --- /dev/null +++ b/prototypes/scp-clicker/slab/slab.moon @@ -0,0 +1,108 @@ +path = (...)\sub 1, -("slab"\len! + 2) + +import graphics from love + +slab = { + _VERSION: "0.1.0" + _DESCRIPTION: "GUI library for LÖVE." + _URL: nil + _LICENSE: "The MIT License (MIT)" + _AUTHOR: "Paul Liverman III" +} + +slab.ui = { x: 0, y: 0, w: graphics.getWidth!, h: graphics.getHeight! } +slab.hovered = false +slab.focused = false +slab.types = { "menu", "spinner", "text" } +slab.generic = require "#{path}/types/generic" +for t in *slab.types + slab[t] = require "#{path}/types/#{t}" + -- slab[t]\load slab if slab[t].load + +slab.make = (element={}, parent=slab.ui) -> + for t in *slab.types + return slab[t](element, parent) if element[t] + return slab.generic element, parent + +slab.update = (dt, element=slab.ui) -> + element\update dt if element.update + for child in *element + slab.update dt, child + +slab.draw = (element=slab.ui) -> + if element == slab.ui + graphics.push "all" + graphics.origin! + + local drawChildren + drawChildren = element\draw! if element.visible and element.draw + if drawChildren != false + for child in *element + slab.draw child + + if element == slab.ui + graphics.pop! + +slab.mousemoved = (x, y, dx, dy, element=slab.ui) -> + local previous + if element == slab.ui + previous = slab.hovered + + if element.visible and element.hoverable and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + slab.hovered = element + for child in *element + slab.mousemoved x, y, dx, dy, child + + if element == slab.ui + if slab.hovered != previous + previous\hovered false if previous and previous.hovered + slab.hovered\hovered true if slab.hovered.hovered + if slab.focused and slab.focused.mousemoved + return slab.focused\mousemoved x - slab.focused.x, y - slab.focused.y, dx, dy + +slab.mousepressed = (x, y, btn, element=screen.ui) -> + if element == screen.ui + if btn == "wd" + return slab.wheelmoved 0, -1 + elseif btn == "wu" + return slab.wheelmoved 0, 1 + + handled = false + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + for i = #element, 1, -1 + handled = slab.mousepressed x, y, btn, element[i] + return handled if handled != false + + if element.mousepressed + handled = element\mousepressed btn, x, y + + return handled + +slab.mousereleased = (x, y, btn, element) -> + clickHandled, mousereleaseHandled = false, false + if element + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + for i = #element, 1, -1 + clickHandled, mousereleaseHandled = slab.mousereleased x, y, btn, element[i] + if clickHandled != false or mousereleaseHandled != false + return clickHandled, mousereleaseHandled + + clickHandled = element\clicked btn, x - element.x, y - element.y if element.clicked + mousereleaseHandled = element\mousereleased btn, x - element.x, y - element.y if element.mousereleased + slab.focused = element if clickHandled + + elseif element = slab.focused + mousereleaseHandled = element\mousereleased btn, x - element.x, y - element.y if element.mousereleased + if element.visible and x >= element.x and x <= element.x + element.w and y >= element.y and y <= element.y + element.h + clickHandled = element\clicked btn, x - element.x, y - element.y if element.clicked + + if clickHandled != false or mousereleaseHandled != false + return clickHandled, mousereleaseHandled + + slab.mousereleased x, y, btn, slab.ui + +slab.wheelmoved = (x, y) -> + return slab.hovered\wheelmoved x, y if slab.hovered and slab.hovered.wheelmoved + return false + +return slab diff --git a/prototypes/scp-clicker/slab/types/generic.lua b/prototypes/scp-clicker/slab/types/generic.lua new file mode 100644 index 0000000..1463d31 --- /dev/null +++ b/prototypes/scp-clicker/slab/types/generic.lua @@ -0,0 +1,90 @@ +local path = (...):sub(1, -(("generic"):len() + 2)) +local floor +floor = math.floor +local generic +do + local _class_0 + local _base_0 = { + draw = function(self) end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + if parent == nil then + parent = slab.ui + end + for k, v in pairs(element) do + self[k] = v + end + if self.visible == nil then + self.visible = true + end + if self.hovered then + self.hoverable = true + end + if self.hoverable == nil then + self.hoverable = false + end + if self.width then + if self.width <= 1 then + self.w = floor(parent.w * self.width) + else + self.w = floor(self.width) + end + else + self.width = 0 + self.w = 0 + end + if self.height then + if self.height <= 1 then + self.h = floor(parent.h * self.height) + else + self.h = floor(self.height) + end + else + self.height = 0 + self.h = 0 + end + if "table" == type(self.background) then + if not (self.color) then + self.color = self.background + end + self.background = nil + else + if not (self.color) then + self.color = { + 255, + 255, + 255, + 255 + } + end + end + if self.background then + self.aspectRatio = self.background:getWidth() / self.background:getHeight() + end + if #self > 0 then + local slab = require(tostring(path:sub(1, -7)) .. "/slab") + for _index_0 = 1, #self do + local child = self[_index_0] + slab.make(child, self) + end + end + end, + __base = _base_0, + __name = "generic" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + generic = _class_0 + return _class_0 +end diff --git a/prototypes/scp-clicker/slab/types/generic.moon b/prototypes/scp-clicker/slab/types/generic.moon new file mode 100644 index 0000000..6efb806 --- /dev/null +++ b/prototypes/scp-clicker/slab/types/generic.moon @@ -0,0 +1,76 @@ +path = (...)\sub 1, -("generic"\len! + 2) + +import floor from math + +-- local slab +class generic + -- @load: (lib) => + -- print "LOAD", self, lib + -- slab = lib + + new: (element={}, parent=slab.ui) => + for k,v in pairs element + @[k] = v + + @visible = true if @visible == nil + @hoverable = true if @hovered + @hoverable = false if @hoverable == nil + + if @width + if @width <= 1 + @w = floor parent.w * @width + else + @w = floor(@width) + else + @width = 0 + @w = 0 + + if @height + if @height <= 1 + @h = floor parent.h * @height + else + @h = floor(@height) + else + @height = 0 + @h = 0 + + if "table" == type @background + @color = @background unless @color + @background = nil + else + @color = {255, 255, 255, 255} unless @color + + if @background + @aspectRatio = @background\getWidth! / @background\getHeight! + + -- print @__class.__name + if #@ > 0 + slab = require "#{path\sub 1, -7}/slab" + for child in *@ + slab.make child, @ + + -- align if generic + + draw: => + -- TODO set color and draw background + +------------------------ NOTE s ----------------------- +-- 1. menu size set (full height, 1/3 width) +-- 2. spinner size set to zero width, 1/3 height, no aspect ratio +-- 3. generic size set to zero width (0.8*0) and zero height +-- 4. text -> size handled by font + +-- { -- menu (set size based on parent) +-- { -- spinner (size NOT set, menu sets height only, need to set width based on aspect ratio (which is unknown initially)) +-- {} -- generic (w background image) (width set based on parent, height needs to be set by aspect ratio) +-- {} -- text (size will be set by slab's default font size) +-- } +-- { -- spinner +-- {} -- generic (w background image) +-- {} -- text +-- } +-- { -- spinner +-- {} -- generic (w background image) +-- {} -- text +-- } +-- } diff --git a/prototypes/scp-clicker/slab/types/menu.lua b/prototypes/scp-clicker/slab/types/menu.lua new file mode 100644 index 0000000..6c5fee9 --- /dev/null +++ b/prototypes/scp-clicker/slab/types/menu.lua @@ -0,0 +1,72 @@ +local path = (...):sub(1, -(("menu"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local insert +insert = table.insert +local menu +do + local _class_0 + local _parent_0 = generic + local _base_0 = { } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + _class_0.__parent.__init(self, element, parent) + for _index_0 = 1, #self do + local child = self[_index_0] + if self.menu.width then + if not (child.width) then + child.width = self.menu.width + end + end + if self.menu.height then + if not (child.height) then + child.height = self.menu.height + end + end + if self.menu.align then + if not (child.align) then + child.align = self.menu.align + end + end + end + if #self.menu > 0 then + local slab = require(tostring(path:sub(1, -7)) .. "/slab") + local _list_0 = self.menu + for _index_0 = 1, #_list_0 do + local child = _list_0[_index_0] + insert(self, slab.make(child, self)) + end + end + end, + __base = _base_0, + __name = "menu", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + menu = _class_0 + return _class_0 +end diff --git a/prototypes/scp-clicker/slab/types/menu.moon b/prototypes/scp-clicker/slab/types/menu.moon new file mode 100644 index 0000000..038e04f --- /dev/null +++ b/prototypes/scp-clicker/slab/types/menu.moon @@ -0,0 +1,21 @@ +path = (...)\sub 1, -("menu"\len! + 2) +generic = require "#{path}/generic" + +import insert from table + +class menu extends generic + new: (element={}, parent) => + super element, parent + + for child in *@ + if @menu.width + child.width = @menu.width unless child.width + if @menu.height + child.height = @menu.height unless child.height + if @menu.align + child.align = @menu.align unless child.align + + if #@menu > 0 + slab = require "#{path\sub 1, -7}/slab" + for child in *@menu + insert(@, slab.make child, @) diff --git a/prototypes/scp-clicker/slab/types/spinner.lua b/prototypes/scp-clicker/slab/types/spinner.lua new file mode 100644 index 0000000..3b69cd1 --- /dev/null +++ b/prototypes/scp-clicker/slab/types/spinner.lua @@ -0,0 +1,61 @@ +local path = (...):sub(1, -(("spinner"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local graphics +graphics = love.graphics +local pi, min +do + local _obj_0 = math + pi, min = _obj_0.pi, _obj_0.min +end +local tau = pi * 2 +local half_pi = pi / 2 +local spinner +do + local _class_0 + local _parent_0 = generic + local _base_0 = { + draw = function(self) + return graphics.arc("line", self.x + self.w / 2, self.y + self.h / 2, min(self.w, self.h), self.offset, self.offset + self.value * tau) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + _class_0.__parent.__init(self, element, parent) + if not (self.offset) then + self.offset = 0 + end + self.value = 0.1 + end, + __base = _base_0, + __name = "spinner", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + spinner = _class_0 + return _class_0 +end diff --git a/prototypes/scp-clicker/slab/types/spinner.moon b/prototypes/scp-clicker/slab/types/spinner.moon new file mode 100644 index 0000000..698e02f --- /dev/null +++ b/prototypes/scp-clicker/slab/types/spinner.moon @@ -0,0 +1,21 @@ +path = (...)\sub 1, -("spinner"\len! + 2) +generic = require "#{path}/generic" + +import graphics from love +import pi, min from math +tau = pi * 2 +half_pi = pi / 2 + +class spinner extends generic + new: (element={}, parent) => + super element, parent + + -- @offset = -half_pi + @offset = 0 unless @offset + @value = 0.1 -- temporary + + -- slab = require "#{path\sub 1, -7}/slab" + + draw: => + -- TODO should be setting color! + graphics.arc "line", @x + @w / 2, @y + @h / 2, min(@w, @h), @offset, @offset + @value * tau diff --git a/prototypes/scp-clicker/slab/types/text.lua b/prototypes/scp-clicker/slab/types/text.lua new file mode 100644 index 0000000..37166ed --- /dev/null +++ b/prototypes/scp-clicker/slab/types/text.lua @@ -0,0 +1,48 @@ +local path = (...):sub(1, -(("text"):len() + 2)) +local generic = require(tostring(path) .. "/generic") +local graphics +graphics = love.graphics +local text +do + local _class_0 + local _parent_0 = generic + local _base_0 = { + draw = function(self) end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, element, parent) + if element == nil then + element = { } + end + return _class_0.__parent.__init(self, element, parent) + end, + __base = _base_0, + __name = "text", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + text = _class_0 + return _class_0 +end diff --git a/prototypes/scp-clicker/slab/types/text.moon b/prototypes/scp-clicker/slab/types/text.moon new file mode 100644 index 0000000..952093b --- /dev/null +++ b/prototypes/scp-clicker/slab/types/text.moon @@ -0,0 +1,15 @@ +path = (...)\sub 1, -("text"\len! + 2) +generic = require "#{path}/generic" + +import graphics from love + +class text extends generic + new: (element={}, parent) => + super element, parent + + -- @font = graphics.newFont 14 + + -- slab = require "#{path\sub 1, -7}/slab" + + draw: => + -- TODO diff --git a/prototypes/slab.lua b/prototypes/slab.lua new file mode 100644 index 0000000..f8b0264 --- /dev/null +++ b/prototypes/slab.lua @@ -0,0 +1,35 @@ +local props = { + "toolbar", + "window", + "tabs", + "area", + "grid", + "spinner", + "dial", + "slider", + "title", + "text", + "checkbox", + "radio", + "class", + "align", + "clicked", + "hovered", + "wheelmoved", + "mousepressed", + "mousereleased", + "textinput", + "keypressed", + "keyreleased", + "margin", + "padding", + "border", + "width", + "height", + "size", + "background", + "color", + "tooltip" +} +local slab = { } +return slab