Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 70 additions & 21 deletions front/parser/src/import.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ast::ASTNode;
use crate::ast::{ASTNode, StatementNode};
use crate::parse;
use error::error::{WaveError, WaveErrorKind};
use lexer::Lexer;
Expand Down Expand Up @@ -26,11 +26,7 @@ pub fn local_import_unit(
}

if path.starts_with("std::") {
already_imported.insert(path.to_string());
return Ok(ImportedUnit {
abs_path: base_dir.to_path_buf(),
ast: vec![],
});
return std_import_unit(path, already_imported);
}

if path.contains("::") {
Expand Down Expand Up @@ -60,11 +56,72 @@ pub fn local_import_unit(
));
}

parse_wave_file(&found_path, &target_file_name, already_imported)
}

pub fn local_import(
path: &str,
already_imported: &mut HashSet<String>,
base_dir: &Path,
) -> Result<Vec<ASTNode>, WaveError> {
Ok(local_import_unit(path, already_imported, base_dir)?.ast)
}

fn std_import_unit(path: &str, already_imported: &mut HashSet<String>) -> Result<ImportedUnit, WaveError> {
let rel = path.strip_prefix("std::").unwrap();
if rel.trim().is_empty() {
return Err(WaveError::new(
WaveErrorKind::SyntaxError("Empty std import".to_string()),
"std import path cannot be empty (example: import(\"std::io::format\"))",
path,
0,
0,
));
}

let std_root = std_root_dir(path)?;

// std::io::format -> ~/.wave/lib/wave/std/io/format.wave
let rel_path = rel.replace("::", "/");
let found_path = std_root.join(format!("{}.wave", rel_path));

if !found_path.exists() || !found_path.is_file() {
return Err(WaveError::new(
WaveErrorKind::SyntaxError("File not found".to_string()),
format!("Could not find std import target '{}'", found_path.display()),
path,
0,
0,
));
}

parse_wave_file(&found_path, path, already_imported)
}

fn std_root_dir(import_path: &str) -> Result<PathBuf, WaveError> {
let home = std::env::var("HOME").map_err(|_| {
WaveError::new(
WaveErrorKind::SyntaxError("std not installed".to_string()),
"HOME env not set; cannot locate std at ~/.wave/lib/wave/std",
import_path,
0,
0,
)
})?;

Ok(PathBuf::from(home).join(".wave/lib/wave/std"))
}

fn parse_wave_file(
found_path: &Path,
display_name: &str,
already_imported: &mut HashSet<String>,
) -> Result<ImportedUnit, WaveError> {
let abs_path = found_path.canonicalize().map_err(|e| {
WaveError::new(
WaveErrorKind::SyntaxError("Canonicalization failed".to_string()),
format!("Failed to canonicalize path: {}", e),
target_file_name.clone(),
display_name,
0,
0,
)
Expand All @@ -76,7 +133,7 @@ pub fn local_import_unit(
WaveError::new(
WaveErrorKind::UnexpectedChar('?'),
"Invalid path encoding",
target_file_name.clone(),
display_name,
0,
0,
)
Expand All @@ -88,11 +145,11 @@ pub fn local_import_unit(
}
already_imported.insert(abs_path_str);

let content = std::fs::read_to_string(&found_path).map_err(|e| {
let content = std::fs::read_to_string(&abs_path).map_err(|e| {
WaveError::new(
WaveErrorKind::SyntaxError("Read error".to_string()),
format!("Failed to read '{}': {}", target_file_name, e),
target_file_name.clone(),
format!("Failed to read '{}': {}", abs_path.display(), e),
display_name,
0,
0,
)
Expand All @@ -104,8 +161,8 @@ pub fn local_import_unit(
let ast = parse(&tokens).ok_or_else(|| {
WaveError::new(
WaveErrorKind::SyntaxError("Parse failed".to_string()),
format!("Failed to parse '{}'", target_file_name),
target_file_name.clone(),
format!("Failed to parse '{}'", abs_path.display()),
display_name,
1,
1,
)
Expand All @@ -115,11 +172,3 @@ pub fn local_import_unit(

Ok(ImportedUnit { abs_path, ast })
}

pub fn local_import(
path: &str,
already_imported: &mut HashSet<String>,
base_dir: &Path,
) -> Result<Vec<ASTNode>, WaveError> {
Ok(local_import_unit(path, already_imported, base_dir)?.ast)
}
146 changes: 145 additions & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::errors::CliError;
use std::{env, fs};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{compile_and_img, compile_and_run};
use std::path::Path;

#[derive(Default)]
pub struct DebugFlags {
Expand Down Expand Up @@ -57,3 +60,144 @@ pub fn img_run(file_path: &Path) -> Result<(), CliError> {
}
Ok(())
}

pub fn handle_install_std() -> Result<(), CliError> {
install_or_update_std(false)
}

pub fn handle_update_std() -> Result<(), CliError> {
install_or_update_std(true)
}

fn install_or_update_std(is_update: bool) -> Result<(), CliError> {
let install_dir = resolve_std_install_dir()?;

if install_dir.exists() {
if !is_update {
return Err(CliError::StdAlreadyInstalled { path: install_dir });
}
fs::remove_dir_all(&install_dir)?;
}

fs::create_dir_all(&install_dir)?;

install_std_from_wave_repo_sparse(&install_dir)?;

if is_update {
println!("✅ std updated: {}", install_dir.display());
} else {
println!("✅ std installed: {}", install_dir.display());
}

Ok(())
}

fn install_std_from_wave_repo_sparse(stage_dir: &Path) -> Result<(), CliError> {
if !tool_exists("git") {
return Err(CliError::ExternalToolMissing("git"));
}

let repo = "https://github.com/wavefnd/Wave.git";
let reference = "master";

let tmp = make_tmp_dir("wave-std")?;

run_cmd(
Command::new("git")
.arg("clone")
.arg("--depth").arg("1")
.arg("--filter=blob:none")
.arg("--sparse")
.arg("--branch").arg(reference)
.arg(repo)
.arg(&tmp),
"git clone",
)?;

run_cmd(
Command::new("git")
.arg("-C").arg(&tmp)
.arg("sparse-checkout")
.arg("set")
.arg("std"),
"git sparse-checkout set std",
)?;

let src_std = tmp.join("std");

let manifest_path = src_std.join("manifest.json");
if !manifest_path.exists() {
return Err(CliError::CommandFailed(
"manifest.json not found in repo/std (add std/manifest.json)".to_string(),
));
}

let text = fs::read_to_string(&manifest_path)?;
let manifest = utils::json::parse(&text)
.map_err(|e| CliError::CommandFailed(format!("invalid manifest.json: {}", e)))?;

if manifest.get_str("name") != Some("std") {
return Err(CliError::CommandFailed("manifest.json name != 'std'".to_string()));
}

copy_dir_all(&src_std, stage_dir)?;

fs::write(
stage_dir.join("INSTALL_META"),
format!("repo={}\nref={}\n", repo, reference),
)?;

let _ = fs::remove_dir_all(&tmp);
Ok(())
}

fn resolve_std_install_dir() -> Result<PathBuf, CliError> {
let home = env::var("HOME").map_err(|_| CliError::HomeNotSet)?;
Ok(PathBuf::from(home).join(".wave/lib/wave/std"))
}


fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), CliError> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let from = entry.path();
let to = dst.join(entry.file_name());

if ty.is_dir() {
copy_dir_all(&from, &to)?;
} else if ty.is_file() {
if let Some(parent) = to.parent() {
fs::create_dir_all(parent)?;
}
fs::copy(&from, &to)?;
}
}
Ok(())
}

fn tool_exists(name: &str) -> bool {
Command::new(name).arg("--version").output().is_ok()
}

fn run_cmd(cmd: &mut Command, label: &str) -> Result<(), CliError> {
let out = cmd.output()?;
if out.status.success() {
Ok(())
} else {
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
Err(CliError::CommandFailed(format!(
"{} (status={})\nstdout: {}\nstderr: {}",
label, out.status, stdout, stderr
)))
}
}

fn make_tmp_dir(prefix: &str) -> Result<PathBuf, CliError> {
let t = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
let p = env::temp_dir().join(format!("{}-{}", prefix, t));
fs::create_dir_all(&p)?;
Ok(p)
}
29 changes: 29 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt;
use std::path::PathBuf;

#[derive(Debug)]
pub enum CliError {
Expand All @@ -8,6 +9,18 @@ pub enum CliError {
command: &'static str,
expected: &'static str,
},

// install/update
UnknownInstallTarget(String),
UnknownUpdateTarget(String),
StdAlreadyInstalled { path: PathBuf },
InvalidExecutablePath,
ExternalToolMissing(&'static str),
CommandFailed(String),
HomeNotSet,

// io
Io(std::io::Error),
}

impl fmt::Display for CliError {
Expand All @@ -20,6 +33,22 @@ impl fmt::Display for CliError {
"Error: Missing argument for '{}'. Expected: {}",
command, expected
),

CliError::UnknownInstallTarget(t) => write!(f, "Error: Unknown install target '{}'", t),
CliError::UnknownUpdateTarget(t) => write!(f, "Error: Unknown update target '{}'", t),
CliError::StdAlreadyInstalled { path } => write!(f, "Error: std already installed at '{}'", path.display()),
CliError::InvalidExecutablePath => write!(f, "Error: Invalid executable path"),
CliError::ExternalToolMissing(t) => write!(f, "Error: required tool not found: {}", t),
CliError::CommandFailed(cmd) => write!(f, "Error: command failed: {}", cmd),
CliError::HomeNotSet => write!(f, "Error: HOME environment variable not set"),

CliError::Io(e) => write!(f, "IO Error: {}", e),
}
}
}

impl From<std::io::Error> for CliError {
fn from(e: std::io::Error) -> Self {
CliError::Io(e)
}
}
Loading