Add readline, color, http_post_auth builtins; fix import resolution in build

Engram can now power the Neuron CLI:

- readline(prompt) -> String: interactive terminal input via stdin
- http_post_auth(url, token, body) -> String: authenticated POST for daemon API
- color_cyan/green/red/yellow/bold/dim(s) -> String: ANSI color output
  All registered in el-types type checker

- el build now resolves import "file.el" directives recursively (was only
  done for el run-file and el check; project builds failed silently)

- Add .gitignore (target/, *.elc, *.sealed, *.map.json)
This commit is contained in:
Will Anderson
2026-04-28 13:46:22 -05:00
parent 094ca39b15
commit b62df85969
4 changed files with 354 additions and 6 deletions
+50 -3
View File
@@ -161,8 +161,9 @@ impl BuildSystem {
});
}
// Read source
let source = std::fs::read_to_string(&entry)?;
// Read source (resolving imports recursively)
let source = resolve_imports_recursive(&entry)
.map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
// Build seal config for prod builds
let seal_config = self.build_seal_config()?;
@@ -296,7 +297,8 @@ impl BuildSystem {
return Err(BuildError::EntryNotFound(entry.display().to_string()));
}
let source = std::fs::read_to_string(&entry)?;
let source = resolve_imports_recursive(&entry)
.map_err(|e| BuildError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
let tokens = el_lexer::tokenize(&source)
.map_err(el_compiler::CompileError::Lex)?;
let program = el_parser::parse(tokens, source.clone())
@@ -340,6 +342,51 @@ impl BuildSystem {
}
}
/// Resolve `import "path.el"` directives by reading and concatenating source files.
/// Imports are resolved relative to the directory of the importing file.
/// Circular imports are detected via a visited set.
fn resolve_imports_recursive(file: &std::path::Path) -> Result<String, String> {
let mut visited = std::collections::HashSet::new();
resolve_imports_inner(file, &mut visited)
}
fn resolve_imports_inner(
file: &std::path::Path,
visited: &mut std::collections::HashSet<std::path::PathBuf>,
) -> Result<String, String> {
let canonical = file.canonicalize().unwrap_or_else(|_| file.to_path_buf());
if visited.contains(&canonical) {
return Ok(String::new()); // circular — skip
}
visited.insert(canonical.clone());
let dir = file.parent().unwrap_or(std::path::Path::new("."));
let source = std::fs::read_to_string(file)
.map_err(|e| format!("cannot read {}: {e}", file.display()))?;
let mut out = String::new();
for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("import ") {
let rest = rest.trim();
if rest.starts_with('"') && rest.ends_with('"') {
let import_path_str = &rest[1..rest.len() - 1];
let import_path = dir.join(import_path_str);
let imported = resolve_imports_inner(&import_path, visited)?;
out.push_str(&imported);
out.push('\n');
} else {
out.push_str(line);
out.push('\n');
}
} else {
out.push_str(line);
out.push('\n');
}
}
Ok(out)
}
fn artifact_name(
pkg_name: &str,
build_target: &BuildTarget,