aboutsummaryrefslogtreecommitdiff
path: root/nomsu.moon
blob: 780e8bcb42f0da05ba2063d6382b2bb666b8f149 (plain)
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
#!/usr/bin/env moon
-- This file contains the command-line Nomsu runner.
EXIT_SUCCESS, EXIT_FAILURE = 0, 1
usage = [=[
Nomsu Compiler

Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-v] [-c] [-f] [-s] [--help] [-p print_file] file1 file2... [-- nomsu args...]

OPTIONS
    -i Run the compiler in interactive mode (REPL)
    -O Run the compiler in optimized mode (use precompiled .lua versions of Nomsu files, when available)
    -v Verbose: print compiled lua code
    -c Compile .nom files into .lua files
    -f Auto-format the given Nomsu file and print the result.
    -s Check the program for syntax errors.
    -h/--help Print this message.
    -p <file> Print to the specified file instead of stdout.
    <input> Input file can be "-" to use stdin.
]=]

ok, _ = pcall ->
    export lpeg, re
    lpeg = require 'lpeg'
    re = require 're'
if not ok
    print("Error: unable to find the 'lpeg' Lua module. Please install LPEG either from http://www.inf.puc-rio.br/~roberto/lpeg/re.html or, if you use luarocks: `luarocks install lpeg`")
    os.exit(EXIT_FAILURE)
Errhand = require "error_handling"
NomsuCompiler = require "nomsu_compiler"
{:NomsuCode, :LuaCode, :Source} = require "code_obj"
STDIN, STDOUT, STDERR = "/dev/fd/0", "/dev/fd/1", "/dev/fd/2"

-- If this file was reached via require(), then just return the Nomsu compiler
if not arg or debug.getinfo(2).func == require
    return NomsuCompiler

parser = re.compile([[
    args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} {:nomsu_args: {| ("--;" ({[^;]*} ";")*)? |} :} ";"? |} !.
    flag <-
        {:interactive: ("-i" -> true) :}
      / {:optimized: ("-O" -> true) :}
      / {:format: ("-f" -> true) :}
      / {:syntax: ("-s" -> true) :}
      / {:print_file: "-p" ";" {file} :}
      / {:compile: ("-c" -> true) :}
      / {:verbose: ("-v" -> true) :}
      / {:help: (("-h" / "--help") -> true) :}
    file <- "-" / [^;]+
]], {true: -> true})
args = table.concat(arg, ";")..";"
args = parser\match(args)
if not args or args.help
    print usage
    os.exit(EXIT_FAILURE)

nomsu = NomsuCompiler
nomsu.arg = args.nomsu_args

export FILE_CACHE
-- FILE_CACHE is a map from filename (string) -> string of file contents
FILE_CACHE = setmetatable {}, {
    __index: (filename)=>
        file = io.open(filename)
        return nil unless file
        contents = file\read("*a")
        file\close!
        self[filename] = contents
        return contents
}

export all_files
{:match, :sub, :rep, :gsub, :format, :byte, :match, :find} = string
iterate_single = (item, prev) -> if item == prev then nil else item
ok, lfs = pcall(require, "lfs")
if ok
    all_files = (path)->
        browse = (filename)->
            file_type = lfs.attributes(filename, 'mode')
            if file_type == 'file'
                if match(filename, "%.nom$") or match(filename, "%.lua$")
                    coroutine.yield filename
                    return true
            elseif file_type == 'directory'
                for subfile in lfs.dir(filename)
                    unless subfile == "." or subfile == ".."
                        browse(filename.."/"..subfile)
                return true
            elseif file_type == 'char device'
                coroutine.yield(filename)
                return true
            return false
        return coroutine.wrap ->
            if not browse(path) and package.nomsupath
                browse(package.nomsupath.."/"..path)
            return nil
else
    ret = os.execute('find . -maxdepth 0')
    unless ret == true or ret == 0
        error "Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0

    all_files = (path)->
        -- Sanitize path
        if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$")
            return iterate_single, path
        -- TODO: improve sanitization
        path = gsub(path,"\\","\\\\")
        path = gsub(path,"`","")
        path = gsub(path,'"','\\"')
        path = gsub(path,"$","")
        return coroutine.wrap ->
            f = io.popen('find -L "'..path..'" -not -path "*/\\.*" -type f -name "*.nom"')
            found = false
            for line in f\lines!
                found = true
                coroutine.yield(line)
            if not found and package.nomsupath
                f\close!
                f = io.popen('find -L "'..package.nomsupath..'/'..path..'" -not -path "*/\\.*" -type f -name "*.nom"')
                for line in f\lines!
                    coroutine.yield(line)
            success = f\close!
            unless success
                error("Invalid file path: "..tostring(path))


run = ->
    for i,input in ipairs args.inputs
        if input == "-" then args.inputs[i] = STDIN

    if #args.inputs == 0 and not args.interactive
        args.inputs = {"core"}
        args.interactive = true

    print_file = if args.print_file == "-" then io.stdout
    elseif args.print_file then io.open(args.print_file, 'w')
    else io.stdout

    if print_file == nil
        nomsu.print = ->
    elseif print_file != io.stdout
        nomsu.print = (...)->
            N = select("#",...)
            if N > 0
                print_file\write(tostring(select(1,...)))
                for i=2,N
                    print_file\write('\t',tostring(select(1,...)))
            print_file\write('\n')
            print_file\flush!

    input_files = {}
    to_run = {}
    for input in *args.inputs
        for f in all_files(input)
            input_files[#input_files+1] = f
            to_run[f] = true

    nomsu.skip_precompiled = to_run

    if args.compile or args.verbose
        nomsu.on_compile = (code, from_file)->
            return unless to_run[from_file]
            if args.verbose
                io.write(tostring(code), "\n")
            if args.compile and from_file\match("%.nom$")
                output_filename = from_file\gsub("%.nom$", ".lua")
                output_file = io.open(output_filename, 'w')
                output_file\write(tostring(code))
                output_file\flush!
                print ("Compiled %-25s -> %s")\format(from_file, output_filename)
                output_file\close!

    parse_errs = {}
    for filename in *input_files
        if args.syntax
            -- Check syntax:
            file_contents = io.open(filename)\read('*a')
            ok,err = pcall nomsu.parse, nomsu, file_contents, Source(filename, 1, #file_contents)
            if not ok
                table.insert parse_errs, err
            elseif print_file
                print_file\write("Parse succeeded: #{filename}\n")
                print_file\flush!
        elseif args.format
            -- Auto-format
            file = FILE_CACHE[filename]
            if not file
                error("File does not exist: #{filename}", 0)
            tree = nomsu\parse(file, Source(filename,1,#file))
            formatted = tostring(nomsu\tree_to_nomsu(tree))
            if print_file
                print_file\write(formatted, "\n")
                print_file\flush!
        elseif filename == STDIN
            file = io.input!\read("*a")
            FILE_CACHE.stdin = file
            nomsu\run(file, Source('stdin',1,#file))
        else
            nomsu\run_file(filename)

    if #parse_errs > 0
        io.stderr\write table.concat(parse_errs, "\n\n")
        io.stderr\flush!
        os.exit(EXIT_FAILURE)
    elseif args.syntax
        os.exit(EXIT_SUCCESS)

    if args.interactive
        -- REPL
        for repl_line=1,math.huge
            io.write(colored.bright colored.yellow ">> ")
            buff = {}
            while true
                line = io.read("*L")
                if line == "\n" or not line
                    if #buff > 0
                        io.write("\027[1A\027[2K")
                    break -- Run buffer
                line = line\gsub("\t", "    ")
                table.insert buff, line
                io.write(colored.dim colored.yellow ".. ")
            if #buff == 0
                break -- Exit
            
            buff = table.concat(buff)
            pseudo_filename = "user input #"..repl_line
            FILE_CACHE[pseudo_filename] = buff
            err_hand = (error_message)->
                Errhand.print_error error_message
            ok, ret = xpcall(nomsu.run, err_hand, nomsu, buff, Source(pseudo_filename, 1, #buff))
            if ok and ret != nil
                print "= "..repr(ret)
            elseif not ok
                Errhand.print_error ret

has_ldt, ldt = pcall(require,'ldt')
if has_ldt
    ldt.guard(run)
else
    Errhand.run_safely(run)