// dep_tree.nyash — Build dependency info for a Nyash script (include + using/module) static box DepTree { init { visited } // Public API: build tree for entry path build(entry_path) { me.visited = new MapBox() return me.node(entry_path) } // Read entire file as string via FileBox read_file(path) { local fb = new FileBox() local ok = fb.open(path, "r") if ok == false { return null } local content = fb.read() fb.close() return content } // Extract include paths from source: include "./path.nyash" extract_includes(src) { local out = new ArrayBox() if src == null { return out } local i = 0 local n = src.length() loop(i < n) { local j = src.indexOf("include \"", i) if j < 0 { break } local k = j + 9 // after include " local q = src.indexOf("\"", k) if q < 0 { break } out.push(src.substring(k, q)) i = q + 1 } return out } // Extract using directives (script lines): // using ns // using ns as Alias // using "path" as Name // and comment form: // @using ns[ as Alias] extract_usings(src) { local out = new ArrayBox() if src == null { return out } local lines = me.split_lines(src) local i = 0 loop(i < lines.length()) { local line = lines.get(i).trim() local t = line if t.startsWith("// @using ") { t = t.substring(10, t.length()).trim() } else if t.startsWith("using ") { t = t.substring(6, t.length()).trim() } else { i = i + 1; continue } // optional trailing semicolon if t.endsWith(";") { t = t.substring(0, t.length()-1).trim() } local rec = new MapBox() // Split alias local as_pos = t.indexOf(" as ") local target = t local alias = null if as_pos >= 0 { target = t.substring(0, as_pos).trim() alias = t.substring(as_pos+4, t.length()).trim() } rec.set("target", target) if alias != null { rec.set("alias", alias) } // classify if target.startsWith("\"") || target.startsWith("./") || target.startsWith("/") || target.endsWith(".nyash") { rec.set("kind", "path") // strip quotes if target.startsWith("\"") { rec.set("target", target.substring(1, target.length()-1)) } } else { rec.set("kind", "ns") } out.push(rec) i = i + 1 } return out } // Extract modules mapping from // @module ns=path extract_modules(src) { local out = new ArrayBox() if src != null { local lines = me.split_lines(src) local i = 0 loop(i < lines.length()) { local line = lines.get(i).trim() if line.startsWith("// @module ") { local rest = line.substring(11, line.length()).trim() local eq = rest.indexOf("=") if eq > 0 { local ns = rest.substring(0, eq).trim() local path = rest.substring(eq+1, rest.length()).trim() path = me.strip_quotes(path) local m = new MapBox(); m.set("ns", ns); m.set("path", path); out.push(m) } } i = i + 1 } } return out } // Build a node: { path, includes:[...], using:[...], modules:[...], children:[...] } node(path) { if me.visited.has(path) { return me.leaf(path) } me.visited.set(path, true) local m = new MapBox(); m.set("path", path) local src = me.read_file(path) if src == null { m.set("error", "read_fail"); me.ensure_arrays(m); return m } local base_dir = me.dirname(path) // includes local incs = me.extract_includes(src) m.set("includes", incs) // usings local us = me.extract_usings(src) m.set("using", us) // modules mapping (script-level) local mods = me.extract_modules(src) m.set("modules", mods) // children = includes + resolved using(path) + resolved using(ns via search paths) local children = new ArrayBox() // include children local i = 0 loop(i < incs.length()) { local p = incs.get(i) local child_path = me.resolve_path(base_dir, p) children.push(me.node(child_path)) i = i + 1 } // using(path) children i = 0 loop(i < us.length()) { local u = us.get(i) if u.get("kind") == "path" { local p = u.get("target") local child_path = me.resolve_path(base_dir, p) children.push(me.node(child_path)) } i = i + 1 } // using(ns) children resolved via search paths local search = me.default_using_paths() i = 0 loop(i < us.length()) { local u = us.get(i) if u.get("kind") == "ns" { local ns = u.get("target") local rel = ns.replace(".", "/") + ".nyash" local found = me.search_in_paths(base_dir, search, rel) if found != null { children.push(me.node(found)) } else { // annotate unresolved u.set("unresolved", true) u.set("hint", rel) } } i = i + 1 } m.set("children", children) return m } // Helpers ensure_arrays(m) { m.set("includes", new ArrayBox()); m.set("using", new ArrayBox()); m.set("modules", new ArrayBox()); m.set("children", new ArrayBox()) } default_using_paths() { // Best-effort defaults prioritized for selfhost local arr = new ArrayBox() arr.push("apps/selfhost"); arr.push("apps"); arr.push("lib"); arr.push(".") return arr } split_lines(src) { local out = new ArrayBox() local i = 0; local n = src.length(); local start = 0 loop(i <= n) { if i == n || src.substring(i, i+1) == "\n" { out.push(src.substring(start, i)) start = i + 1 } i = i + 1 } return out } strip_quotes(s) { if s == null { return null } if s.length() >= 2 && s.substring(0,1) == "\"" && s.substring(s.length()-1, s.length()) == "\"" { return s.substring(1, s.length()-1) } return s } dirname(path) { local pb = new PathBox(); local d = pb.dirname(path) if d != null { return d } local i = path.lastIndexOf("/") if i < 0 { return "." } return path.substring(0, i) } resolve_path(base, rel) { if rel.indexOf("/") == 0 { return rel } if rel.startsWith("./") || rel.startsWith("../") { local pb = new PathBox(); local j = pb.join(base, rel) if j != null { return j } return base + "/" + rel } return rel } search_in_paths(base, paths, rel) { // try relative to file first local pb = new PathBox(); local j = pb.join(base, rel) if me.file_exists(j) { return j } // then search list local i = 0 loop(i < paths.length()) { local p = paths.get(i) local cand = pb.join(p, rel) if me.file_exists(cand) { return cand } i = i + 1 } return null } file_exists(path) { // Use FileBox.open in read mode as exists check local fb = new FileBox(); local ok = fb.open(path, "r") if ok == false { return false } fb.close(); return true } leaf(path) { local m = new MapBox(); m.set("path", path); m.set("children", new ArrayBox()); m.set("note", "visited") return m } }