code / nomsu

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