254 lines
7.0 KiB
Plaintext
254 lines
7.0 KiB
Plaintext
// 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
|
|
}
|
|
}
|