public: publish selfhost snapshot to public repo (SSOT using + AST merge + JSON VM fixes)
- SSOT using profiles (aliases/packages via nyash.toml), AST prelude merge - Parser/member guards; Builder pin/PHI and instance→function rewrite (dev on) - VM refactors (handlers split) and JSON roundtrip/nested stabilization - CURRENT_TASK.md updated with scope and acceptance criteria Notes: dev-only guards remain togglable via env; no default behavior changes for prod.
This commit is contained in:
@ -20,6 +20,21 @@ pub fn collect_using_and_strip(
|
||||
|
||||
let mut out = String::with_capacity(code.len());
|
||||
let mut prelude_paths: Vec<String> = Vec::new();
|
||||
// Determine if this file is inside a declared package root; if so, allow
|
||||
// internal file-using within the package even when file-using is globally disallowed.
|
||||
let filename_canon = std::fs::canonicalize(filename).ok();
|
||||
let mut inside_pkg = false;
|
||||
if let Some(ref fc) = filename_canon {
|
||||
for (_name, pkg) in &using_ctx.packages {
|
||||
let base = std::path::Path::new(&pkg.path);
|
||||
if let Ok(root) = std::fs::canonicalize(base) {
|
||||
if fc.starts_with(&root) {
|
||||
inside_pkg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for line in code.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("using ") {
|
||||
@ -28,11 +43,22 @@ pub fn collect_using_and_strip(
|
||||
let rest0 = rest0.split('#').next().unwrap_or(rest0).trim();
|
||||
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
|
||||
let (target, _alias) = if let Some(pos) = rest0.find(" as ") {
|
||||
(rest0[..pos].trim().to_string(), Some(rest0[pos + 4..].trim().to_string()))
|
||||
} else { (rest0.to_string(), None) };
|
||||
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||
(
|
||||
rest0[..pos].trim().to_string(),
|
||||
Some(rest0[pos + 4..].trim().to_string()),
|
||||
)
|
||||
} else {
|
||||
(rest0.to_string(), None)
|
||||
};
|
||||
let is_path = target.starts_with('"')
|
||||
|| target.starts_with("./")
|
||||
|| target.starts_with('/')
|
||||
|| target.ends_with(".nyash");
|
||||
if is_path {
|
||||
if prod || !crate::config::env::allow_using_file() {
|
||||
// SSOT: Disallow file-using at top-level; allow only for sources located
|
||||
// under a declared package root (internal package wiring), so that packages
|
||||
// can organize their modules via file paths.
|
||||
if (prod || !crate::config::env::allow_using_file()) && !inside_pkg {
|
||||
return Err(format!(
|
||||
"using: file paths are disallowed in this profile. Add it to nyash.toml [using] (packages/aliases) and reference by name: {}",
|
||||
target
|
||||
@ -42,24 +68,43 @@ pub fn collect_using_and_strip(
|
||||
// Resolve relative to current file dir
|
||||
let mut p = std::path::PathBuf::from(&path);
|
||||
if p.is_relative() {
|
||||
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
||||
if let Some(dir) = ctx_dir {
|
||||
let cand = dir.join(&p);
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
// Also try NYASH_ROOT when available (repo-root relative like "apps/...")
|
||||
if p.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
} else {
|
||||
// Fallback: guess project root from executable path (target/release/nyash)
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
if let Some(root) = exe
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.and_then(|p| p.parent())
|
||||
{
|
||||
let cand = root.join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbose { crate::runner::trace::log(format!("[using/resolve] file '{}' -> '{}'", target, p.display())); }
|
||||
if verbose {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using/resolve] file '{}' -> '{}'",
|
||||
target,
|
||||
p.display()
|
||||
));
|
||||
}
|
||||
prelude_paths.push(p.to_string_lossy().to_string());
|
||||
continue;
|
||||
}
|
||||
@ -87,8 +132,13 @@ pub fn collect_using_and_strip(
|
||||
} else if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(&pkg_name);
|
||||
base.join(format!("{}.nyash", leaf)).to_string_lossy().to_string()
|
||||
let leaf = base
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or(&pkg_name);
|
||||
base.join(format!("{}.nyash", leaf))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
prelude_paths.push(out);
|
||||
}
|
||||
@ -114,26 +164,46 @@ pub fn collect_using_and_strip(
|
||||
) {
|
||||
Ok(value) => {
|
||||
// Only file paths are candidates for AST prelude merge
|
||||
if value.ends_with(".nyash") || value.contains('/') || value.contains('\\') {
|
||||
if value.ends_with(".nyash") || value.contains('/') || value.contains('\\')
|
||||
{
|
||||
// Resolve relative
|
||||
let mut p = std::path::PathBuf::from(&value);
|
||||
if p.is_relative() {
|
||||
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
||||
if let Some(dir) = ctx_dir {
|
||||
let cand = dir.join(&p);
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
if p.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
} else {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
if let Some(root) = exe
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.and_then(|p| p.parent())
|
||||
{
|
||||
let cand = root.join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
if cand.exists() {
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbose { crate::runner::trace::log(format!("[using/resolve] dev-file '{}' -> '{}'", value, p.display())); }
|
||||
if verbose {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using/resolve] dev-file '{}' -> '{}'",
|
||||
value,
|
||||
p.display()
|
||||
));
|
||||
}
|
||||
prelude_paths.push(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
@ -163,7 +233,51 @@ pub fn resolve_prelude_paths_profiled(
|
||||
code: &str,
|
||||
filename: &str,
|
||||
) -> Result<(String, Vec<String>), String> {
|
||||
collect_using_and_strip(runner, code, filename)
|
||||
// First pass: strip using from the main source and collect direct prelude paths
|
||||
let (cleaned, direct) = collect_using_and_strip(runner, code, filename)?;
|
||||
// When AST using is enabled、recursively collect nested preludes in DFS order
|
||||
let ast_on = std::env::var("NYASH_USING_AST").ok().as_deref() == Some("1");
|
||||
if !ast_on {
|
||||
return Ok((cleaned, direct));
|
||||
}
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
fn normalize_path(path: &str) -> (String, String) {
|
||||
use std::path::PathBuf;
|
||||
match PathBuf::from(path).canonicalize() {
|
||||
Ok(canon) => {
|
||||
let s = canon.to_string_lossy().to_string();
|
||||
(s.clone(), s)
|
||||
}
|
||||
Err(_) => {
|
||||
// Fall back to the original path representation.
|
||||
(path.to_string(), path.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn dfs(
|
||||
runner: &NyashRunner,
|
||||
path: &str,
|
||||
out: &mut Vec<String>,
|
||||
seen: &mut std::collections::HashSet<String>,
|
||||
) -> Result<(), String> {
|
||||
let (key, real_path) = normalize_path(path);
|
||||
if !seen.insert(key.clone()) {
|
||||
return Ok(());
|
||||
}
|
||||
let src = std::fs::read_to_string(&real_path)
|
||||
.map_err(|e| format!("using: failed to read '{}': {}", real_path, e))?;
|
||||
let (_cleaned, nested) = collect_using_and_strip(runner, &src, &real_path)?;
|
||||
for n in nested.iter() {
|
||||
dfs(runner, n, out, seen)?;
|
||||
}
|
||||
out.push(real_path);
|
||||
Ok(())
|
||||
}
|
||||
for p in direct.iter() {
|
||||
dfs(runner, p, &mut out, &mut seen)?;
|
||||
}
|
||||
Ok((cleaned, out))
|
||||
}
|
||||
|
||||
/// Pre-expand line-head `@name[: Type] = expr` into `local name[: Type] = expr`.
|
||||
@ -173,21 +287,49 @@ pub fn preexpand_at_local(src: &str) -> String {
|
||||
for line in src.lines() {
|
||||
let bytes = line.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { i += 1; }
|
||||
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
|
||||
i += 1;
|
||||
}
|
||||
if i < bytes.len() && bytes[i] == b'@' {
|
||||
// parse identifier
|
||||
let mut j = i + 1;
|
||||
if j < bytes.len() && ((bytes[j] as char).is_ascii_alphabetic() || bytes[j] == b'_') {
|
||||
j += 1;
|
||||
while j < bytes.len() { let c = bytes[j] as char; if c.is_ascii_alphanumeric() || c == '_' { j += 1; } else { break; } }
|
||||
let mut k = j; while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
if k < bytes.len() && bytes[k] == b':' {
|
||||
k += 1; while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') { k += 1; }
|
||||
if k < bytes.len() && ((bytes[k] as char).is_ascii_alphabetic() || bytes[k] == b'_') {
|
||||
k += 1; while k < bytes.len() { let c = bytes[k] as char; if c.is_ascii_alphanumeric() || c == '_' { k += 1; } else { break; } }
|
||||
while j < bytes.len() {
|
||||
let c = bytes[j] as char;
|
||||
if c.is_ascii_alphanumeric() || c == '_' {
|
||||
j += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut eqp = k; while eqp < bytes.len() && (bytes[eqp] == b' ' || bytes[eqp] == b'\t') { eqp += 1; }
|
||||
let mut k = j;
|
||||
while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') {
|
||||
k += 1;
|
||||
}
|
||||
if k < bytes.len() && bytes[k] == b':' {
|
||||
k += 1;
|
||||
while k < bytes.len() && (bytes[k] == b' ' || bytes[k] == b'\t') {
|
||||
k += 1;
|
||||
}
|
||||
if k < bytes.len()
|
||||
&& ((bytes[k] as char).is_ascii_alphabetic() || bytes[k] == b'_')
|
||||
{
|
||||
k += 1;
|
||||
while k < bytes.len() {
|
||||
let c = bytes[k] as char;
|
||||
if c.is_ascii_alphanumeric() || c == '_' {
|
||||
k += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut eqp = k;
|
||||
while eqp < bytes.len() && (bytes[eqp] == b' ' || bytes[eqp] == b'\t') {
|
||||
eqp += 1;
|
||||
}
|
||||
if eqp < bytes.len() && bytes[eqp] == b'=' {
|
||||
out.push_str(&line[..i]);
|
||||
out.push_str("local ");
|
||||
|
||||
Reference in New Issue
Block a user