ref: d9e2c6c7cf64dab43e65c2f4ec2c13c301297f48
author: Ori Bernstein <ori@eigenstate.org>
date: Mon Sep 22 18:23:45 EDT 2014
It compiles itself!
--- /dev/null
+++ b/bldfile
@@ -1,0 +1,2 @@
+# comment
+bin mbld2 = main.myr build.myr parse.myr deps.myr config.myr opts.myr; # comment
--- /dev/null
+++ b/build.myr
@@ -1,0 +1,225 @@
+use std
+
+use "config.use"
+use "deps.use"
+use "opts.use"
+use "parse.use"
+
+pkg bld =
+ const build : (p : parser# -> bool)
+;;
+
+const build = {p
+ var dg
+
+ for t in p.targs
+ match t
+ | `Bin (b, leaves):
+ if !myrdeps(&dg, b, leaves, false)
+ std.fatal(1, "Could not load dependencies for %s\n", b)
+ ;;
+ buildbin(&dg, b, leaves)
+ | `Lib (l, leaves):
+ if !myrdeps(&dg, l, leaves, true)
+ std.fatal(1, "Could not load dependencies for %s\n", l)
+ ;;
+ buildlib(&dg, l, leaves)
+ | `Sub sub:
+ dosub(sub)
+ | `Man m:
+ /* nothing needed */
+ ;;
+ ;;
+ -> true
+}
+
+const buildbin = {dg, b, inputs
+ match std.htget(dg.deps, b)
+ | `std.Some deps:
+ for d in deps
+ builddep(dg, d, b)
+ ;;
+ linkbin(dg, b, inputs, "")
+ | `std.None:
+ ;;
+}
+
+const buildlib = {dt, b, dep
+ std.put("building %s:\n", b)
+ for d in dep
+ std.put("\t%s\n", d)
+ ;;
+}
+
+const dosub = {subs
+ for s in subs
+ std.put("subdir %s\n", s)
+ ;;
+}
+
+const builddep = {dg, dep, out
+ match std.htget(dg.deps, dep)
+ | `std.Some deps:
+ for d in deps
+ builddep(dg, d, dep)
+ ;;
+ | `std.None:
+ ;;
+
+ if isfresh(dep, out)
+ ->
+ ;;
+ if std.hassuffix(dep, ".myr")
+ run(["6m", dep][:])
+ ;;
+}
+
+const linkbin = {dg, binname, srcfiles, ldscript
+ var cmd
+ var obj
+
+ cmd = [][:]
+
+ /* ld -o bin */
+ cmd = std.slpush(cmd, std.sldup("ld"))
+ cmd = std.slpush(cmd, std.sldup("-o"))
+ cmd = std.slpush(cmd, std.sldup(binname))
+
+ /* [-T script] */
+ if ldscript.len > 0
+ cmd = std.slpush(cmd, std.sldup("-T"))
+ cmd = std.slpush(cmd, std.sldup(ldscript))
+ ;;
+
+ cmd = std.slpush(cmd, std.fmt("%s/%s", config.Instroot, "/lib/myr/_myrrt.o"))
+
+ /* input.o list.o... */
+ for f in srcfiles
+ if std.hassuffix(f, ".myr")
+ obj = swapsuffix(f, ".myr", ".o")
+ elif std.hassuffix(f, ".s")
+ obj = swapsuffix(f, ".s", ".o")
+ else
+ std.fatal(1, "unknown file type for %s\n", f)
+ ;;
+ cmd = std.slpush(cmd, obj)
+ ;;
+
+ /* -l lib... */
+ cmd = addlibs(cmd, dg.libs)
+
+ /* -L incpath... */
+ for inc in opt_incpaths
+ cmd = std.slpush(cmd, std.fmt("-L%s", inc))
+ ;;
+ cmd = std.slpush(cmd, std.fmt("-L%s%s", config.Instroot, "/lib/myr"))
+
+ /*
+ /* special for OSX: it warns if we don't add this */
+ if std.sleq(opt_sysname, "Darwin")
+ cmd = std.slpush(cmd, std.sldup("-macosx_version_min"))
+ cmd = std.slpush(cmd, std.sldup("10.6"))
+ ;;
+ */
+
+ run(cmd)
+ strlistfree(cmd)
+}
+
+
+const addlibs = {cmd, libgraph
+ var looped : std.htab(byte[:], bool)#
+ var marked : std.htab(byte[:], bool)#
+ var libs
+ var head
+
+ libs = std.htkeys(libgraph)
+ looped = std.mkht(std.strhash, std.streq)
+ marked = std.mkht(std.strhash, std.streq)
+ head = cmd.len
+ for lib in libs
+ cmd = visit(cmd, head, libgraph, lib, looped, marked)
+ ;;
+ -> cmd
+}
+
+const visit = {cmd, head, g, lib, looped, marked -> byte[:][:]
+ if std.hthas(looped, lib)
+ std.fatal(1, "cycle in library graph involving \"%s\"\n", lib)
+ elif std.hthas(marked, lib)
+ -> cmd
+ ;;
+
+ std.htput(looped, lib, true)
+ for dep in std.htgetv(g, lib, [][:])
+ cmd = visit(cmd, head, g, dep, looped, marked)
+ ;;
+ std.htdel(looped, lib)
+ std.htput(marked, lib, true)
+ -> std.slput(cmd, head, std.fmt("-l%s", lib))
+}
+const isfresh = {src, dst
+ var srcsb, dstsb
+
+ if std.stat(src, &srcsb) != 0
+ std.fatal(1, "could not stat %s\n", src)
+ ;;
+ if std.stat(dst, &dstsb) != 0
+ -> false
+ ;;
+ if srcsb.mtime.sec != dstsb.mtime.sec
+ -> srcsb.mtime.sec <= dstsb.mtime.sec
+ else
+ -> srcsb.mtime.nsec <= dstsb.mtime.nsec
+ ;;
+}
+
+const run = {cmd
+ var pid
+ var status
+
+ printcmd(cmd)
+ pid = std.fork()
+ if pid == -1
+ std.fatal(1, "could not fork command\n")
+ elif pid == 0
+ if std.execvp(cmd[0], cmd) < 0
+ std.fatal(1, "failed to exec %s\n", cmd[0])
+ ;;
+ else
+ std.waitpid(pid, &status, 0)
+ ;;
+ match std.waitstatus(status)
+ | `std.Waitexit estatus:
+ if estatus != 0
+ std.exit(estatus castto(int))
+ ;;
+ | `std.Waitsig sig:
+ std.fatal(1, "%s exited with signal %i\n", cmd[0], sig)
+ ;;
+}
+
+const printcmd = {lst
+ if lst.len > 0
+ std.put("\t")
+ std.put("%s\t", lst[0])
+ for l in lst[1:]
+ std.put("%s ", l)
+ ;;
+ ;;
+ std.put("\n")
+}
+
+const swapsuffix = {f, suff, newsuff
+ if std.hassuffix(f, suff)
+ f = f[:f.len - suff.len]
+ ;;
+ -> std.fmt("%s%s", f, newsuff)
+}
+
+const strlistfree = {sl
+ for s in sl
+ std.slfree(s)
+ ;;
+ std.slfree(sl)
+}
--- /dev/null
+++ b/config.myr
@@ -1,0 +1,4 @@
+pkg config =
+const Instroot = "/home/ori/bin"
+const Sys = "Linux"
+;;
--- /dev/null
+++ b/deps.myr
@@ -1,0 +1,249 @@
+use std
+use regex
+use bio
+
+use "config.use"
+use "opts.use"
+
+pkg bld =
+ type depgraph = struct
+ roots : byte[:][:]
+ deps : std.htab(byte[:], byte[:][:])#
+ libs : std.htab(byte[:], byte[:][:])#
+ ;;
+
+ const myrdeps : (dg : depgraph#, targ : byte[:], leaves : byte[:][:], islib : bool -> bool)
+;;
+
+type dep = union
+ `Local byte[:]
+ `Lib byte[:]
+;;
+
+var usepat : regex.regex#
+
+const myrdeps = {dg, targ, leaves, islib
+ var seentab, donetab
+ var obj, usefile, out, useout
+
+ match regex.compile("^\\s*use\\s+(([^\"]\\S+[^\"])|(\"(\\S+)\"))")
+ | `std.Ok re: usepat = re
+ | `std.Fail f: std.fatal(1, "Failed to compile use pattern regex\n")
+ ;;
+
+ dg.deps = std.mkht(std.strhash, std.streq)
+ dg.libs = std.mkht(std.strhash, std.streq)
+ seentab = std.mkht(std.strhash, std.streq)
+ donetab = std.mkht(std.strhash, std.streq)
+ /* direct dependencies of binary */
+ if islib
+ out = std.fmt("lib%s.a", targ)
+ useout = std.sldup(targ)
+ else
+ out = std.sldup(targ)
+ useout = ""
+ ;;
+ for l in leaves
+ obj = srcswapsuffix(l, ".o")
+ pushdep(dg, obj, out)
+ pushdep(dg, l, obj)
+ if islib
+ usefile = srcswapsuffix(l, ".use")
+ pushdep(dg, usefile, useout)
+ pushdep(dg, l, usefile)
+ ;;
+ srcdeps(dg, seentab, donetab, l)
+ ;;
+ dumpgraph(dg)
+ std.htfree(seentab)
+ std.htfree(donetab)
+ -> true
+}
+
+const dumpgraph = {dg
+ var keys
+
+ keys = std.htkeys(dg.deps)
+ std.put("digraph dg {\n")
+ for k in keys
+ for v in std.htgetv(dg.deps, k, ["WTFUNKNOWN!"][:])
+ std.put("\t%s -> %s;\n", k, v)
+ ;;
+ ;;
+ std.put("}\n")
+}
+
+const srcdeps = {g, seen, done, path
+ var deps
+
+ if std.hthas(done, path)
+ std.put("already know about %s\n", path)
+ ->
+ elif std.htgetv(seen, path, false)
+ std.fatal(1, "dependency loop involving %s\n", path)
+ ;;
+ deps = getdeps(path)
+ std.htput(seen, path, true)
+ for d in deps
+ match d
+ | `Lib lib:
+ scrapelibs(g, lib)
+ | `Local l:
+ if !std.hassuffix(l, ".use")
+ std.fatal(1, "usefile dependency \"%s\" of \"%s\" is not a usefile\n", l, path)
+ ;;
+ pushdep(g, l, path)
+ addusedep(g, seen, done, l)
+ ;;
+ ;;
+ std.htput(seen, path, false)
+ std.htput(done, path, true)
+}
+
+const addusedep = {g, seen, done, usefile
+ var src
+
+ if std.hthas(done, usefile)
+ std.put("already know about %s\n", usefile)
+ ->
+ ;;
+ std.put("adding use dep for %s\n", usefile)
+ src = swapsuffix(usefile, ".use", ".myr")
+ pushdep(g, src, usefile)
+ srcdeps(g, seen, done, src)
+ std.htput(done, usefile, true)
+}
+
+const getdeps = {path
+ var f
+ var deps : dep[:]
+
+ deps = [][:]
+ match bio.open(path, bio.Rd)
+ | `std.Some fd: f = fd
+ | `std.None: std.fatal(1, "could not open %s\n", path)
+ ;;
+
+ while true
+ match bio.readln(f)
+ | `std.Some ln:
+ deps = depname(deps, ln)
+ std.slfree(ln)
+ | `std.None:
+ bio.close(f)
+ -> deps
+ ;;
+ ;;
+}
+
+const scrapelibs = {dg, lib
+ var deps, d
+ var f
+ var done
+
+ if std.hthas(dg.libs, lib)
+ ->
+ ;;
+
+ deps = [][:]
+ f = openlib(lib)
+ match bio.getc(f)
+ | `std.Some 'U': /* nothing */
+ | `std.Some _: std.fatal(1, "library %s is not usefile\n", lib)
+ | `std.None: std.fatal(1, "library %s is not usefile\n", lib)
+ ;;
+ std.slfree(rdstr(f))
+ done = false
+ while !done
+ match bio.getc(f)
+ | `std.Some 'L':
+ d = rdstr(f)
+ deps = std.slpush(deps, d)
+ | `std.Some _: done = true
+ | `std.None: done = true
+ ;;
+ ;;
+ bio.close(f)
+ std.htput(dg.libs, lib, deps)
+ for dep in deps
+ scrapelibs(dg, dep)
+ ;;
+}
+
+const openlib = {lib
+ var path
+
+ for p in opt_incpaths
+ path = std.fmt("%s/%s/%s", p, "/lib/myr", lib)
+ match bio.open(path, bio.Rd)
+ | `std.Some file:
+ -> file
+ ;;
+ ;;
+ path = std.fmt("%s/%s/%s", config.Instroot, "/lib/myr", lib)
+ match bio.open(path, bio.Rd)
+ | `std.Some file:
+ -> file
+ ;;
+ std.fatal(1, "could not find library %s in %s\n", lib, path)
+}
+
+const depname = {deps, ln
+ /*
+ the regex pattern does some contortions to either grab
+ an unquoted path and put it into uses[4], or a quoted
+ path, and put it (minus the quotes) into uses[2]
+ */
+ match regex.exec(usepat, ln)
+ | `std.Some uses:
+ if uses[2].len > 0
+ deps = std.slpush(deps, `Local std.sldup(uses[2]))
+ else
+ deps = std.slpush(deps, `Lib std.sldup(uses[4]))
+ ;;
+ ;;
+ -> deps
+}
+
+
+/* pushes a dep into the dependency list */
+const pushdep = {dg, to, from
+ var sl
+
+ std.put("%s ==> %s\n", from, to)
+ sl = std.htgetv(dg.deps, from, [][:])
+ sl = std.slpush(sl, to)
+ std.htput(dg.deps, from, sl)
+}
+
+const srcswapsuffix = {s, new
+ if std.hassuffix(s, ".myr")
+ -> swapsuffix(s, ".myr", new)
+ elif std.hassuffix(s, ".s")
+ -> swapsuffix(s, ".s", new)
+ else
+ std.fatal(1, "unrecognized source %s\n", s)
+ ;;
+}
+
+const swapsuffix = {s, suffix, new
+ if !std.hassuffix(s, suffix)
+ std.die("swapping suffix on string without appropriate suffix\n")
+ ;;
+ s = s[:s.len - suffix.len]
+ -> std.strcat(s, new)
+}
+
+const rdstr = {f
+ var len : uint32
+ var sl
+
+ match bio.getbe(f)
+ | `std.Some l:
+ len = l
+ sl = std.slalloc(len castto(std.size))
+ | `std.None: std.die("string length not readable")
+ ;;
+ bio.read(f, sl)
+ -> sl
+}
--- /dev/null
+++ b/main.myr
@@ -1,0 +1,19 @@
+use std
+use "parse.use"
+use "build.use"
+
+const main = {args : byte[:][:]
+ var p : bld.parser#
+
+ p = std.alloc()
+ p.line = 1
+ p.fname = "bldfile"
+ match std.slurp("bldfile")
+ | `std.Ok d: p.data = d
+ | `std.Fail _: std.fatal(1, "could not open file 'bldfile'\n")
+ ;;
+ p.rest = p.data
+
+ bld.parse(p)
+ bld.build(p)
+}
--- /dev/null
+++ b/opts.myr
@@ -1,0 +1,11 @@
+pkg bld =
+ var opt_arch : byte[:]
+ var opt_sys : byte[:]
+ var opt_targ : byte[:]
+ var opt_incpaths : byte[:][:]
+;;
+
+var opt_arch : byte[:]
+var opt_sys : byte[:]
+var opt_targ : byte[:]
+var opt_incpaths : byte[:][:]
--- /dev/null
+++ b/parse.myr
@@ -1,0 +1,216 @@
+use std
+
+pkg bld =
+ type parser = struct
+ /* parse input */
+ data : byte[:]
+ rest : byte[:]
+ fname : byte[:]
+ line : int
+
+ /* build params */
+ targs : target[:]
+ prefix : byte[:]
+ system : byte[:]
+ arch : byte[:]
+ ;;
+
+ type target = union
+ `Bin (byte[:], byte[:][:])
+ `Lib (byte[:], byte[:][:])
+ `Sub byte[:][:]
+ `Man byte[:][:]
+ ;;
+
+ const parse : (p : parser# -> bool)
+;;
+
+const failparse = {p : parser#, msg, args : ...
+ var buf : byte[1024]
+ var ap
+ var n
+
+ ap = std.vastart(&args)
+ n = std.bfmtv(buf[:], msg, ap)
+ std.fput(1, "%s:%i: %s", p.fname, p.line, buf[:n])
+ std.exit(1)
+}
+
+const parse = {p
+ while true
+ skipspace(p)
+ if !target(p)
+ break
+ ;;
+ ;;
+ skipspace(p)
+ if p.rest.len > 0
+ failparse(p, "junk in file near %s", p.rest[:std.min(p.rest.len, 10)])
+ -> false
+ else
+ -> true
+ ;;
+}
+
+const target = {p : parser#
+ match word(p)
+ | `std.Some "bin": bintarget(p)
+ | `std.Some "lib": libtarget(p)
+ | `std.Some "sub": subtarget(p)
+ | `std.Some "man": mantarget(p)
+ | `std.Some targtype: failparse(p, "unknown targtype type %s\n", targtype)
+ | `std.None: -> false
+ ;;
+ -> true
+}
+
+const bintarget = {p
+ p.targs = std.slpush(p.targs, `Bin nametarget(p, "bin"))
+}
+
+const libtarget = {p
+ p.targs = std.slpush(p.targs, `Lib nametarget(p, "lib"))
+}
+
+const subtarget = {p
+ p.targs = std.slpush(p.targs, `Sub anontarget(p, "sub"))
+}
+
+const mantarget = {p
+ p.targs = std.slpush(p.targs, `Man anontarget(p, "man"))
+
+}
+const nametarget = {p, targ
+ var name, inputs
+
+ match word(p)
+ | `std.Some n: name = n
+ | `std.None: failparse(p, "expected target name after '%s'\n", targ)
+ ;;
+
+ skipspace(p)
+ if !matchc(p, '=')
+ failparse(p, "expected '=' after '%s %s'", targ, name)
+ ;;
+ match wordlist(p)
+ | `std.Some wl: inputs = wl
+ | `std.None: failparse(p, "expected list of file names after %s target\n", targ)
+ ;;
+ skipspace(p)
+ if !matchc(p, ';')
+ failparse(p, "expected ';' terminating input list, got %c\n", peekc(p))
+ ;;
+ -> (name, inputs)
+}
+
+const anontarget = {p, targ
+ var inputs
+
+ match wordlist(p)
+ | `std.None: failparse(p, "expected list of file names after %s target\n", targ)
+ | `std.Some wl: inputs = wl
+ ;;
+ skipspace(p)
+ if !matchc(p, ';')
+ failparse(p, "expected ';' terminating input list\n")
+ ;;
+ -> inputs
+}
+
+const wordlist = {p
+ var wl
+
+ wl = [][:]
+ while true
+ match word(p)
+ | `std.Some w: wl = std.slpush(wl, w)
+ | `std.None: break
+ ;;
+ ;;
+ if wl.len == 0
+ -> `std.None
+ else
+ -> `std.Some wl
+ ;;
+}
+
+const word = {p : parser#
+ var c, r, n
+ var start
+
+ skipspace(p)
+
+ r = p.rest
+ start = r
+ n = 0
+ while r.len > 0
+ c = peekc(p)
+ if wordchar(c)
+ nextc(p)
+ n += std.charlen(c)
+ else
+ break
+ ;;
+ ;;
+ if n > 0
+ -> `std.Some std.sldup(start[:n])
+ else
+ -> `std.None
+ ;;
+}
+
+const wordchar = {c
+ -> std.isalnum(c) || \
+ c == '.' || c == '_' || c == '$' || c == '-' || \
+ c == '/' || c == ':' || c == '!' || c == '~'
+}
+
+const skipspace = {p : parser#
+ var c, r
+
+ r = p.rest
+ while r.len > 0
+ c = peekc(p)
+ match c
+ | ' ': nextc(p)
+ | '\t': nextc(p)
+ | '\n':
+ nextc(p)
+ p.line++
+ | '#':
+ while p.rest.len > 0 && peekc(p) != '\n'
+ nextc(p)
+ ;;
+ | _:
+ break
+ ;;
+ ;;
+}
+
+const matchc = {p, c
+ var chr, s
+
+ if p.rest.len == 0
+ -> false
+ ;;
+ (chr, s) = std.striter(p.rest)
+ if c == chr
+ p.rest = s
+ -> true
+ else
+ -> false
+ ;;
+}
+
+const peekc = {p
+ -> std.decode(p.rest)
+}
+
+const nextc = {p
+ var c, s
+
+ (c, s) = std.striter(p.rest)
+ p.rest = s
+ -> c
+}
+