-
Notifications
You must be signed in to change notification settings - Fork 0
/
nomsu_environment.moon
281 lines (263 loc) · 11.5 KB
/
nomsu_environment.moon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
-- This file defines the environment in which Nomsu code runs, including some
-- basic bootstrapping functionality.
{:NomsuCode, :LuaCode, :Source} = require "code_obj"
{:List, :Dict} = require 'containers'
Text = require 'text'
SyntaxTree = require "syntax_tree"
Files = require "files"
Errhand = require "error_handling"
C = require "colors"
make_parser = require("parser")
pretty_error = require("pretty_errors")
make_tree = (tree, userdata)->
tree.source = Source(userdata.filename, tree.start, tree.stop)
tree.start, tree.stop = nil, nil
tree = SyntaxTree(tree)
return tree
Parsers = {}
for version=1,NOMSU_VERSION[1]
local peg_file
if package.nomsupath
pegpath = package.nomsupath\gsub("lib/%?%.nom", "?.peg")\gsub("lib/%?%.lua", "?.peg")
if path = package.searchpath("nomsu.#{version}", pegpath, "/")
peg_file = io.open(path)
else
peg_file = io.open("nomsu.#{version}.peg")
assert peg_file, "No PEG file found for Nomsu version #{version}"
peg_contents = peg_file\read('*a')
peg_file\close!
Parsers[version] = make_parser(peg_contents, make_tree)
{:tree_to_nomsu, :tree_to_inline_nomsu} = require "nomsu_decompiler"
{:compile, :fail_at} = require('nomsu_compiler')
_module_imports = {}
_importer_mt = {__index: (k)=> _module_imports[@][k]}
Importer = (t, imports)->
_module_imports[t] = imports or {}
t._IMPORTS = _module_imports[t]
return setmetatable(t, _importer_mt)
_1_as_text = (x)->
if x == true then return "yes"
if x == false then return "no"
return tostring(x)
nomsu_pairs = =>
mt = getmetatable(@)
if mt and mt.__next
return mt.__next, @, nil
else
return next, @, nil
inext = if _VERSION == "Lua 5.2" or _VERSION == "Lua 5.1"
inext = (i)=>
value = @[i+1]
return i+1, value if value != nil
else ipairs{}
nomsu_ipairs = =>
mt = getmetatable(@)
if mt and mt.__inext
return mt.__inext, @, 0
else
return inext, @, 0
local nomsu_environment
nomsu_environment = Importer{
-- Lua stuff:
:next, :inext, unpack: unpack or table.unpack, :setmetatable, :rawequal, :getmetatable, :pcall,
yield:coroutine.yield, resume:coroutine.resume, coroutine_status_of:coroutine.status,
coroutine_wrap:coroutine.wrap, coroutine_from: coroutine.create,
:error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall,
:print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen,
:table, :assert, :dofile, :loadstring, lua_type_of:type, :select, :math, :io, :load,
pairs:nomsu_pairs, :ipairs, _ipairs:nomsu_ipairs, :jit,
:_VERSION, LUA_VERSION: (jit and jit.version or _VERSION),
LUA_API: _VERSION, Bit: (jit or _VERSION == "Lua 5.2") and require('bitops') or nil
-- Nomsu types:
a_List:List, a_Dict:Dict, Text:Text,
-- Utilities and misc.
lpeg:lpeg, re:re, Files:Files,
:SyntaxTree, TESTS: Dict({}), globals: Dict({}),
:LuaCode, :NomsuCode, :Source
LuaCode_from: ((src, ...)-> LuaCode\from(src, ...)),
NomsuCode_from: ((src, ...)-> NomsuCode\from(src, ...)),
enhance_error: Errhand.enhance_error
SOURCE_MAP: {},
getfenv:getfenv,
-- Nomsu functions:
_1_as_nomsu:tree_to_nomsu, _1_as_inline_nomsu:tree_to_inline_nomsu,
compile: compile, at_1_fail:fail_at, :_1_as_text, :_1_as_an_iterable,
exit:os.exit, quit:os.exit,
_1_parsed: (nomsu_code, syntax_version)->
if type(nomsu_code) == 'string'
filename = Files.spoof(nomsu_code)
nomsu_code = NomsuCode\from(Source(filename, 1, #nomsu_code), nomsu_code)
source = nomsu_code.source
nomsu_code = tostring(nomsu_code)
version = nomsu_code\match("^#![^\n]*nomsu[ ]+-V[ ]*([0-9.]+)")
if syntax_version
syntax_version = tonumber(syntax_version\match("^[0-9]+"))
syntax_version or= version and tonumber(version\match("^[0-9]+")) or NOMSU_VERSION[1]
parse = Parsers[syntax_version]
assert parse, "No parser found for Nomsu syntax version #{syntax_version}"
tree = parse(nomsu_code, source.filename)
if tree.shebang
shebang_version = tree.shebang\match("nomsu %-V[ ]*([%d.]+)")
if shebang_version and shebang_version != ""
tree.version or= List[tonumber(v) for v in shebang_version\gmatch("%d+")]
--if tree.version and tree.version < NOMSU_VERSION\up_to(#tree.version) and not package.nomsuloaded["compatibility"]
-- export loading_compat
-- unless loading_compat
-- loading_compat = true
-- nomsu_environment\export "compatibility"
-- loading_compat = false
return tree
Module: (package_name)=>
-- This *must* be the first local
local path
if package_name\match("%.nom$") or package_name\match("%.lua")
path = package_name
else
path, err = package.searchpath(package_name, package.nomsupath, "/")
if not path then error(err)
path = path\gsub("^%./", "")
if ret = package.nomsuloaded[package_name] or package.nomsuloaded[path]
return ret
-- Traverse up the callstack to look for import loops
-- This is more reliable than keeping a list of running files, since
-- that gets messed up when errors occur.
currently_running = {}
for i=2,999
info = debug.getinfo(i, 'f')
break unless info.func
if info.func == @Module
n, upper_path = debug.getlocal(i, 3) -- 3 means "path"
table.insert(currently_running, upper_path)
assert(n == "path")
if upper_path == path
--circle = "\n "..table.concat(currently_running, "\n..imports ")
circle = table.concat(currently_running, "', which imports '")
err_i = 2
info = debug.getinfo(err_i)
while info and (info.func == @Module or info.func == @use or info.func == @export)
err_i += 1
info = debug.getinfo(err_i)
fail_at (info or debug.getinfo(2)), "Circular import: File '#{path}' imports '"..circle.."'"
mod = @new_environment!
mod.MODULE_NAME = package_name
code = Files.read(path)
if path\match("%.lua$")
code = LuaCode\from(Source(path, 1, #code), code)
else
code = NomsuCode\from(Source(path, 1, #code), code)
ret = mod\run(code)
if ret != nil
mod = ret
package.nomsuloaded[package_name] = mod
package.nomsuloaded[path] = mod
return mod
use: (package_name)=>
mod = @Module(package_name)
imports = assert _module_imports[@]
for k,v in pairs(mod)
imports[k] = v
cr_imports = assert _module_imports[@COMPILE_RULES]
if mod.COMPILE_RULES
for k,v in pairs(mod.COMPILE_RULES)
cr_imports[k] = v
return mod
export: (package_name)=>
mod = @Module(package_name)
imports = assert _module_imports[@]
for k,v in pairs(_module_imports[mod])
if rawget(imports, k) == nil
imports[k] = v
for k,v in pairs(mod)
if rawget(@, k) == nil
@[k] = v
cr_imports = assert _module_imports[@COMPILE_RULES]
if mod.COMPILE_RULES
for k,v in pairs(_module_imports[mod.COMPILE_RULES])
if rawget(cr_imports, k) == nil
cr_imports[k] = v
for k,v in pairs(mod.COMPILE_RULES)
if rawget(@COMPILE_RULES, k) == nil
@COMPILE_RULES[k] = v
return mod
run: (to_run)=>
if not to_run
error("Need both something to run and an environment")
if type(to_run) == 'string'
filename = Files.spoof(to_run)
to_run = NomsuCode\from(Source(filename, 1, #to_run), to_run)
return @run(to_run)
elseif NomsuCode\is_instance(to_run)
tree = @._1_parsed(to_run)
return nil if tree == nil
return @run(tree)
elseif SyntaxTree\is_instance(to_run)
filename = to_run.source.filename\gsub("\n.*", "...")
version = to_run.version
if to_run.type != "FileChunks"
to_run = {to_run}
-- Each chunk's compilation is affected by the code in the previous chunks
-- (typically), so each chunk needs to compile and run before the next one
-- compiles.
ret = nil
for chunk_no, chunk in ipairs to_run
chunk.version = version
lua = @compile(chunk)
lua\declare_locals!
lua\prepend "-- File: #{filename} chunk ##{chunk_no}\n"
ret = @run(lua)
return ret
elseif LuaCode\is_instance(to_run)
source = to_run.source
lua_string = to_run\text!
-- For some reason, Lua doesn't strip shebangs from Lua files
lua_string = lua_string\gsub("^#![^\n]*\n","")
-- If you replace tostring(source) with "nil", source mapping won't happen
run_lua_fn, err = load(lua_string, tostring(source), "t", @)
if not run_lua_fn
lines =[("%3d|%s")\format(i,line) for i, line in ipairs lua_string\lines!]
line_numbered_lua = table.concat(lines, "\n")
error("Failed to compile generated code:\n#{C("bright blue", line_numbered_lua)}\n\n#{err}", 0)
source_key = tostring(source)
unless @SOURCE_MAP[source_key] or @OPTIMIZATION >= 2
map = {}
file = Files.read(source.filename)
if not file and NOMSU_PREFIX
file = Files.read("#{NOMSU_PREFIX}/share/nomsu/#{table.concat NOMSU_VERSION, "."}/#{source.filename}")
if not file
error "Failed to find file: #{source.filename}"
nomsu_str = file\sub(source.start, source.stop)
lua_line = 1
nomsu_line = Files.get_line_number(file, source.start)
map_sources = (s)->
if type(s) == 'string'
for nl in s\gmatch("\n")
map[lua_line] or= nomsu_line
lua_line += 1
else
if s.source and s.source.filename == source.filename
nomsu_line = Files.get_line_number(file, s.source.start)
for b in *s.bits do map_sources(b)
map_sources(to_run)
map[lua_line] or= nomsu_line
map[0] = 0
-- Mapping from lua line number to nomsu line numbers
@SOURCE_MAP[source_key] = map
return run_lua_fn!
else
error("Attempt to run unknown thing: ".._1_as_lua(to_run))
new_environment: ->
env = Importer({}, {k,v for k,v in pairs(nomsu_environment)})
env._ENV = env
env._G = env
env.TESTS = Dict{}
env.COMPILE_RULES = Importer({}, {k,v for k,v in pairs(nomsu_environment.COMPILE_RULES)})
return env
}
nomsu_environment._ENV = nomsu_environment
nomsu_environment._G = nomsu_environment
nomsu_environment.COMPILE_RULES = Importer(require('bootstrap'))
nomsu_environment.MODULE_NAME = "nomsu"
-- Hacky use of globals:
export SOURCE_MAP
SOURCE_MAP = nomsu_environment.SOURCE_MAP
return nomsu_environment