ref: b9cf124f492b69a0c76c9cb89d07a046c0e6c7e8
dir: /mbld/parse.myr/
use std use "types" use "util" use "opts" use "syssel" pkg bld = const load : (b : build# -> bool) $noret const failparse : (p : parser#, msg : byte[:], args : ... -> void) ;; type parser = struct /* parse input */ data : byte[:] rest : byte[:] fname : byte[:] fdir : byte[:] /* directory relative to base */ basedir : byte[:] line : int /* all targets */ targs : targ[:] /* default parameter values */ incpath : byte[:][:] libdeps : byte[:][:] tstdeps : byte[:][:] runtime : byte[:] ldscript: byte[:] install : bool /* extracted data for further parsing */ subdirs : byte[:][:] targsel : syssel((byte[:], targ))# ;; const load = {b var ok, sel var targs sel = mksyssel(b, "mbld", 0, "mbld") ok = loadall(b, "bld.proj", "", sel) targs = sysselfin(sel) for (name, targ) : targs std.slpush(&b.all, name) std.htput(b.targs, name, targ) ;; if ok ok = sortdeps(b) ;; std.slfree(targs) -> ok } const sortdeps = {b var looped var marked var all all = [][:] looped = std.mkht() marked = std.mkht() for dep : b.all match gettarg(b.targs, dep) | `Bin _: all = visit(all, b, "all", dep, looped, marked) | `Lib _: all = visit(all, b, "all", dep, looped, marked) | targ: std.slpush(&all, dep) ;; ;; std.htfree(marked) std.htfree(looped) std.slfree(b.all) b.all = all -> true } const visit = {all, b, parent, targ, looped, marked if std.hthas(looped, targ) std.fatal("cycle in build depgraph involving {}\n", targ) elif std.hthas(marked, targ) -> all ;; std.htput(looped, targ, true) for (dir, lib, dep) : getdeps(b, parent, targ) all = visit(all, b, targ, dep, looped, marked) ;; std.htdel(looped, targ) std.htput(marked, targ, true) -> std.slpush(&all, targ) } const getdeps = {b, parent, targname match std.htget(b.targs, targname) | `std.Some targ: match targ | `Bin t: -> t.libdeps | `Lib t: -> t.libdeps | _: std.fatal("{} depends on non-library target", parent) ;; | `std.None: std.fatal("{}: could not find dependency {}\n", parent, targname) ;; } /* recursively load all bld.{proj,sub} files in project */ const loadall = {b, path, dir, sel var p : parser# var subbld, subproj, ok var curbase p = mkparser(path, dir, b.basedir, sel) ok = bld.parse(b, p, "") for targ : p.targs match targ | `Bin t: setmyropt(p, t) | `Lib t: setmyropt(p, t) | _: /* only myr targets have settable defaults so far */ ;; ;; for sub : p.subdirs subbld = std.pathcat(sub, "bld.sub") subproj = std.pathcat(sub, "bld.proj") /* bld.sub is a subbuild. It doesn't change the build root. */ if std.fexists(subbld) loadall(b, subbld, sub, sel) /* bld.proj reroots the project -- @/ is relative to the most recent bld.proj in the heirarchy. */ elif std.fexists(sub) curbase = b.basedir b.basedir = sub loadall(b, subproj, sub, sel) b.basedir = curbase else std.fatal("could not open {} or {} \n", subbld, subproj) ;; std.slfree(subbld) std.slfree(subproj) ;; freeparser(p) -> ok } const setmyropt = {p, t var libdep, tstdep t.install = t.install && p.install if t.runtime.len == 0 t.runtime = p.runtime ;; if t.ldscript.len == 0 t.ldscript = p.ldscript ;; std.sljoin(&t.incpath, p.incpath) for l : p.libdeps libdep = libpath(p, l) std.slpush(&t.libdeps, libdep) ;; for l : p.tstdeps tstdep = libpath(p, l) std.slpush(&t.tstdeps, tstdep) ;; } const parse = {b, p, path while true if !target(b, p) break ;; ;; skipspace(p) if p.rest.len > 0 failparse(p, "junk in file near {}\n", p.rest[:std.min(p.rest.len, 10)]) -> false ;; -> true } const target = {b, p match word(p) /* targets */ | `std.Some "bin": bintarget(b, p) | `std.Some "lib": libtarget(b, p) | `std.Some "test": testtarget(b, p) | `std.Some "bench": benchtarget(b, p) | `std.Some "gen": cmdtarget(b, p, "gen", false) | `std.Some "cmd": cmdtarget(b, p, "cmd", true) | `std.Some "data": datatarget(b, p, "data") | `std.Some "man": mantarget(b, p) | `std.Some "sub": subtarget(b, p) /* global attributes */ | `std.Some "incpath": incpath(b, p, &p.incpath, "incpath") | `std.Some "libdeps": incpath(b, p, &p.libdeps, "libdeps") | `std.Some "testdeps": incpath(b, p, &p.tstdeps, "testdeps") | `std.Some "runtime": p.runtime = expectword(b, p, "runtime") | `std.Some "ldscript": p.runtime = expectword(b, p, "ldscript") | `std.Some "noinst": p.install = false /* no word */ | `std.Some targtype: failparse(p, "unknown keyword {}\n", targtype) | `std.None: -> false ;; -> true } const mkparser = {path, dir, basedir, sel var p p = std.mk([ .line = 1, .fname = std.sldup(path), .fdir = std.sldup(dir), .basedir = std.sldup(basedir), .targsel = sel, .incpath = [][:], .libdeps = [][:], .tstdeps = [][:], .runtime = "", .ldscript = "", .install = true, ]) match std.slurp(path) | `std.Ok d: p.data = d | `std.Err _: std.fatal("could not open '{}'\n", path) ;; p.rest = p.data -> p } const freeparser = {p std.slfree(p.fname) std.slfree(p.fdir) std.slfree(p.basedir) std.slfree(p.subdirs) std.slfree(p.data) std.free(p) } /* <name>: '=' wordlist ';;' */ const incpath = {b, p, words, name skipspace(p) if !matchc(p, '=') failparse(p, "expected '=' after {}\n", name) ;; match wordlist(p) | `std.Some path: words# = path | `std.None: ;; skipspace(p) if !matchc(p, ';') || !matchc(p, ';') failparse(p, "expected ';;' after {} list\n", name) ;; } const expectword = {b, p, attr match word(p) | `std.Some w: -> w | `std.None: failparse(p, "expected word after {}\n", attr) ;; } /* bintarget: myrtarget */ const bintarget = {b, p var t t = myrtarget(b, p, "bin") addtarg(p, b, t.name, t.tags, `Bin t) } /* libtarget: myrtarget */ const libtarget = {b, p var t t = myrtarget(b, p, "lib") t.islib = true addtarg(p, b, t.name, t.tags, `Lib t) } /* testtarget: myrtarget */ const testtarget = {b, p var t t = myrtarget(b, p, "test") t.istest = true t.install = false addtarg(p, b, t.name, t.tags, `Bin t) } /* benchtarget: myrtarget */ const benchtarget = {b, p var t t = myrtarget(b, p, "bench") t.isbench = true t.install = false addtarg(p, b, t.name, t.tags, `Bin t) } /* mantarget: anontarget */ const mantarget = {b, p var targ targ = std.mk([ .dir=std.sldup(p.fdir), .pages=anontarget(p, "man") ]) addtarg(p, b, "__man__", [][:], `Man targ) } /* subtarget : anontarget */ const subtarget = {b, p var subs subs = anontarget(p, "sub") for s : subs std.slpush(&p.subdirs, std.pathcat(p.fdir, s)) ;; } /* gentarget: wordlist {attrs} = wordlist ;; */ const cmdtarget = {b, p, cmd, iscmd var outlist, deplist, cmdlist, tags var durable, istest, isbench var attrs var gt match wordlist(p) | `std.None: failparse(p, "{} target missing output files\n", cmd) | `std.Some out: if iscmd && out.len != 1 failparse(p, "{} target takes one name\n", cmd) ;; outlist = out ;; match attrlist(p) | `std.Some al: attrs = al | `std.None: failparse(p, "invalid attr list for {}\n", outlist[0]) ;; skipspace(p) if !matchc(p, '=') failparse(p, "expected '=' after '{} {}'\n", cmd, outlist[outlist.len-1]) ;; match wordlist(p) | `std.None: failparse(p, "{} target missing command\n", cmd) | `std.Some cl: cmdlist = cl ;; if !matchc(p, ';') || !matchc(p, ';') failparse(p, "expected ';;' terminating genfile command, got {}\n", peekc(p)) ;; durable = false istest = false isbench = false deplist = [][:] tags = [][:] for elt : attrs match elt | ("durable", ""): durable = true | ("test", ""): istest = true | ("bench", ""): isbench = true | ("notest", ""): istest = false | ("dep", depname): std.slpush(&deplist, depname) | ("tag", tag): std.slpush(&tags, tag) | (attr, ""): failparse(p, "attribute {} not valid on {} targets\n", attr, cmd) | (attr, val): failparse(p, "attribute {}={} not valid on {} targets\n", attr, val, cmd) ;; ;; gt = std.mk([ .dir=std.sldup(p.fdir), .gen=outlist, .durable=durable, .istest=istest, .isbench=isbench, .deps=deplist, .cmd=cmdlist, .tags=tags, ]) for o : outlist if iscmd addtarg(p, b, o, gt.tags, `Cmd gt) else addtarg(p, b, o, gt.tags, `Gen gt) ;; ;; } /* myrtarget: name '=' inputlist ';;' | name attrlist = inputlist ';;' */ const myrtarget = {b, p, targ var ldscript, runtime, install, incpath, tags var libdeps, name, inputs, attrs var istest, isbench var fsel match word(p) | `std.Some n: name = n | `std.None: failparse(p, "expected target name after '{}'\n", targ) ;; match attrlist(p) | `std.Some al: attrs = al | `std.None: failparse(p, "invalid attr list for {} {}\n", targ, name) ;; skipspace(p) if !matchc(p, '=') failparse(p, "expected '=' after '{} {}'\n", targ, name) ;; fsel = mksyssel(b, p.fname, p.line, name) match inputlist(p) | `std.Some (wl, libs): libdeps = libs for w : wl sysseladd(fsel, w) ;; inputs = sysselfin(fsel) std.slfree(wl) | `std.None: failparse(p, "expected list of file names after '{} {}'\n", targ, name) ;; skipspace(p) if !matchc(p, ';') || !matchc(p, ';') failparse(p, "expected ';;' terminating input list, got {}\n", peekc(p)) ;; install = true ldscript = "" runtime = "" incpath = [][:] tags = [][:] istest = false isbench = false for elt : attrs match elt | ("ldscript", lds): ldscript = std.sldup(lds) | ("runtime", rt): runtime = std.sldup(rt) | ("inc", path): std.slpush(&incpath, std.sldup(path)) | ("tag", tag): std.slpush(&tags, tag) | ("inst", ""): install = true | ("noinst", ""): install = false | ("test", ""): istest = true | ("bench", ""): isbench = true | ("notest", ""): istest = false | (invalid, ""): std.fatal("{}: invalid attr '{}'\n", targ, invalid) | (invalid, attr): std.fatal("{}: invalid attr '{} = {}'\n", targ, invalid, attr) ;; ;; std.sljoin(&incpath, bld.opt_incpaths) -> std.mk([ /* target */ .dir=std.sldup(p.fdir), .name=name, .inputs=inputs, .libdeps=libdeps, .islib=false, .istest=istest, .isbench=isbench, .isdyn=false, /* attrs */ .tags=tags, .install=install, .ldscript=ldscript, .runtime=runtime, .incpath=incpath, ]) } const datatarget = {b, p, targ var name, attrs, blobs var tags, path match word(p) | `std.Some n: name = n | `std.None: failparse(p, "expected target name after '{}'\n", targ) ;; skipspace(p) attrs = [][:] match attrlist(p) | `std.Some al: attrs = al | `std.None: failparse(p, "invalid attr list for {} {}\n", targ, name) ;; skipspace(p) if !matchc(p, '=') failparse(p, "expected '=' after '{} {}'\n", targ, name) ;; match wordlist(p) | `std.Some wl: blobs = wl | `std.None: failparse(p, "expected file list after '{} {}'\n", targ, name) ;; skipspace(p) if !matchc(p, ';') || !matchc(p, ';') failparse(p, "expected ';;' after input list, got {}\n", peekc(p)) ;; tags = [][:] for elt : attrs match elt | ("tag", tag): std.slpush(&tags, tag) | ("path", pathdir): path = pathdir | (tag, ""): std.fatal("{}: invalid attr '{}'\n", targ, tag) | (tag, attr): std.fatal("{}: invalid attr '{}={}'\n", targ, tag, attr) ;; ;; addtarg(p, b, name, tags, `Data std.mk([ .dir=std.sldup(p.fdir), .name=name, .tags=tags, .path=path, .blobs=blobs, ])) } /* anontarget: '=' wordlist ';;' */ const anontarget = {p, targ var inputs inputs = [][:] skipspace(p) if !matchc(p, '=') failparse(p, "expected '=' after '{}' target\n", targ) ;; match wordlist(p) | `std.Some wl: inputs = wl | `std.None: failparse(p, "expected file list after '{}' target\n", targ) ;; skipspace(p) if !matchc(p, ';') || !matchc(p, ';') failparse(p, "expected ';;' terminating input list\n") ;; -> inputs } /* attrlist: EMPTY |'{' attrs '}' attrs : EMPTY | attrs attr attr : name | name '=' name */ const attrlist = {p var al al = [][:] skipspace(p) if !matchc(p, '{') -> `std.Some al ;; while true match word(p) | `std.Some k: if matchc(p, '=') match word(p) | `std.Some v: std.slpush(&al, (k, v)) | `std.None: failparse(p, "invalid attr in attribute list\n") ;; else std.slpush(&al, (k, [][:])) ;; if !matchc(p, ',') break ;; | `std.None: break ;; ;; if !matchc(p, '}') failparse(p, "expected '}' at end of attr list, got '{}'\n", peekc(p)) ;; if al.len == 0 -> `std.None else -> `std.Some al ;; } /* inputlist: EMPTY | inputlist input input : word | "lib" word */ const inputlist = {p var dir, lib, targ var wl, libs wl = [][:] libs = [][:] while true match word(p) | `std.Some "lib": match word(p) | `std.Some l: (dir, lib, targ) = libpath(p, l) std.slpush(&libs, (dir, lib, targ)) | `std.None: failparse(p, "expected lib name after 'lib'\n") ;; | `std.Some w: std.slpush(&wl, w) | `std.None: break ;; ;; if wl.len == 0 -> `std.None else -> `std.Some (wl, libs) ;; } /* wordlist: EMPTY | wordlist word */ const wordlist = {p var wl wl = [][:] while true match word(p) | `std.Some w: std.slpush(&wl, w) | `std.None: break ;; ;; if wl.len == 0 -> `std.None else -> `std.Some wl ;; } /* word: /wordchar*/ /* word: /wordchar*/ const word = {p var c, n var start skipspace(p) c = peekc(p) if c == '"' n = 0 getc(p) start = p.rest while p.rest.len > 0 c = peekc(p) if c == '"' getc(p) goto done elif c == '\\' c = getc(p) ;; getc(p) n += std.charlen(c) ;; failparse(p, "input ended within quoted word\n") else n = 0 start = p.rest while p.rest.len > 0 c = peekc(p) if wordchar(c) getc(p) n += std.charlen(c) else break ;; ;; ;; :done 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 == '~' || \ c == '+' || c == '%' || c == '&' || c == '(' || \ c == ')' || c == '[' || c == ']' || c == ':' } const skipspace = {p var c, r r = p.rest while r.len > 0 c = peekc(p) match c | ' ': getc(p) | '\t': getc(p) | '\n': getc(p) p.line++ | '#': while p.rest.len > 0 && peekc(p) != '\n' getc(p) ;; | _: break ;; ;; } const matchc = {p, c var chr, s /* safe to use at eof: charstep returns (-1, "") */ (chr, s) = std.charstep(p.rest) if c == chr p.rest = s -> true else -> false ;; } const peekc = {p -> std.decode(p.rest) } const getc = {p var c, s (c, s) = std.charstep(p.rest) p.rest = s -> c } const addtarg = {p, b, name, tags, targ var tn tn = std.fmt("{}:{}", p.fdir, name) sysseladdlist(p.targsel, tn, tags, (tn, targ)) std.slpush(&p.targs, targ) } const libpath = {p, libpath var dir, lib, targ match std.strrfind(libpath, ":") | `std.None: dir = std.sldup(p.fdir) lib = std.sldup(libpath) targ = std.fmt("{}:{}", p.fdir, lib) | `std.Some idx: if idx == libpath.len std.fatal("libdep {} missing library after ':'\n") ;; /* absolute path */ if std.hasprefix(libpath, "@/") || std.hasprefix(libpath, "@:") dir = std.sldup(libpath[2:idx]) lib = std.sldup(libpath[idx+1:]) targ = std.sldup(libpath[2:]) /* relative path */ else dir = std.pathcat(p.fdir, libpath[:idx]) lib = std.sldup(libpath[idx+1:]) targ = std.pathcat(p.fdir, libpath) if std.hasprefix(targ, "../") std.fatal("library {} outside of project\n", libpath) ;; ;; ;; -> (dir, lib, targ) } $noret const failparse = {p, msg, args : ... var buf : byte[1024] var ap var sl ap = std.vastart(&args) sl = std.bfmtv(buf[:], msg, &ap) std.fput(1, "{}:{}: {}", p.fname, p.line, sl) std.exit(1) }