use crate::bytecode;
use crate::cli::CliRunner;
use crate::interpreter::{ErrorHandler, Instr, Interpreter, compile_block};
use crate::lexer::Lexer;
use crate::parser::{Expr, Parser};
use crate::types::*;
use std::collections::{HashMap, HashSet};
pub struct Lang {
name: String,
tokens: Vec<String>,
actions: HashMap<String, Action>,
args_formats: HashMap<String, String>,
mut_keyword: Option<String>,
immut_keyword: Option<String>,
fn_keyword: Option<String>,
return_keyword: Option<String>,
if_keyword: Option<String>,
loop_keyword: Option<String>,
for_keyword: Option<String>,
in_keyword: Option<String>,
try_keyword: Option<String>,
catch_keyword: Option<String>,
break_keyword: Option<String>,
import_keyword: Option<String>,
while_keyword: Option<String>,
switch_keyword: Option<String>,
case_keyword: Option<String>,
default_keyword: Option<String>,
assign_op: Option<String>,
null_keyword: Option<String>,
typing: Typing,
implicit_mutability: ImplicitMutability,
blocks: Blocks,
semicolon: Semicolon,
strings: Strings,
mode: Mode,
dist: Dist,
compat: Compat,
debug_mode: Debug,
safe_mode: SafeMode,
ops: Vec<String>,
compare_ops: Vec<String>,
logical_ops: Vec<String>,
comment_line: String,
comment_block: (String, String),
interpolation: Option<String>,
range_op: Option<String>,
array_syntax: Option<String>,
struct_keyword: Option<String>,
enum_keyword: Option<String>,
extension: String,
version_str: String,
license: String,
doc_comment: String,
package_manager: Option<String>,
stdlib_modules: Vec<String>,
crate_modules: Vec<String>,
ffi_libs: Vec<String>,
custom_modules: Vec<Module>,
custom_module_names: Vec<String>,
config_source: ConfigSource,
build_dir: Option<String>,
libs_mode: LibsMode,
base_dir: Option<std::path::PathBuf>,
name_set: bool,
extension_set: bool,
build_dir_set: bool,
libs_mode_set: bool,
stdlib_set: bool,
crates_set: bool,
ffi_set: bool,
typing_set: bool,
mode_set: bool,
implicit_mutability_set: bool,
cli_commands: Vec<(String, Cli)>,
debug_enabled: bool,
error_handler: Option<ErrorHandler>,
cache_enabled: bool,
cached_ast: HashMap<u64, Vec<Expr>>,
cached_bc: HashMap<u64, Vec<Instr>>,
}
impl Lang {
pub fn new() -> Self {
Self {
name: "mylang".to_string(),
tokens: vec![],
actions: HashMap::new(),
args_formats: HashMap::new(),
mut_keyword: None,
immut_keyword: None,
fn_keyword: None,
return_keyword: None,
if_keyword: None,
loop_keyword: None,
for_keyword: None,
in_keyword: None,
try_keyword: None,
catch_keyword: None,
break_keyword: None,
import_keyword: None,
while_keyword: None,
switch_keyword: None,
case_keyword: None,
default_keyword: None,
assign_op: Some("=".to_string()),
null_keyword: Some("null".to_string()),
typing: Typing::Dynamic,
implicit_mutability: ImplicitMutability::Mutable,
blocks: Blocks::Braces,
semicolon: Semicolon::Optional,
strings: Strings::Both,
mode: Mode::Interpreter,
dist: Dist::Interpreted,
compat: Compat::Loose,
debug_mode: Debug::StackTrace,
safe_mode: SafeMode::Full,
ops: ["+", "-", "*", "/", "%"]
.iter()
.map(|s| s.to_string())
.collect(),
compare_ops: ["==", "!=", "<", ">", "<=", ">="]
.iter()
.map(|s| s.to_string())
.collect(),
logical_ops: ["and", "or", "not"].iter().map(|s| s.to_string()).collect(),
comment_line: "//".to_string(),
comment_block: ("/*".to_string(), "*/".to_string()),
interpolation: None,
range_op: None,
array_syntax: Some("[]".to_string()),
struct_keyword: None,
enum_keyword: None,
extension: ".lang".to_string(),
version_str: "1.0.0".to_string(),
license: "MIT".to_string(),
doc_comment: "///".to_string(),
package_manager: None,
stdlib_modules: vec![],
crate_modules: vec![],
ffi_libs: vec![],
custom_modules: vec![],
custom_module_names: vec![],
config_source: ConfigSource::None,
build_dir: None,
libs_mode: LibsMode::Cargo,
base_dir: None,
name_set: false,
extension_set: false,
build_dir_set: false,
libs_mode_set: false,
stdlib_set: false,
crates_set: false,
ffi_set: false,
typing_set: false,
mode_set: false,
implicit_mutability_set: false,
cli_commands: vec![],
debug_enabled: false,
error_handler: None,
cache_enabled: true,
cached_ast: HashMap::new(),
cached_bc: HashMap::new(),
}
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = name.to_string();
self.name_set = true;
self
}
pub fn extension(&mut self, ext: &str) -> &mut Self {
self.extension = ext.to_string();
self.extension_set = true;
self
}
pub fn version(&mut self, v: &str) -> &mut Self {
self.version_str = v.to_string();
self
}
pub fn mode(&mut self, mode: Mode) -> &mut Self {
self.mode = mode;
self.mode_set = true;
self
}
pub fn interpreter(&mut self) -> &mut Self {
self.mode(Mode::Interpreter)
}
pub fn compiler(&mut self) -> &mut Self {
self.mode(Mode::Compiler)
}
pub fn both(&mut self) -> &mut Self {
self.mode(Mode::Both)
}
pub fn safe_mode(&mut self, mode: SafeMode) -> &mut Self {
self.safe_mode = mode;
self
}
pub fn license(&mut self, l: &str) -> &mut Self {
self.license = l.to_string();
self
}
pub fn doc_comment(&mut self, prefix: &str) -> &mut Self {
self.doc_comment = prefix.to_string();
self
}
pub fn package_manager(&mut self, name: &str) -> &mut Self {
self.package_manager = Some(name.to_string());
self
}
pub fn config_toml(&mut self, path: &str) -> &mut Self {
self.config_source = ConfigSource::Toml(path.to_string());
self
}
pub fn config_inline(&mut self) -> &mut Self {
self.config_source = ConfigSource::Inline;
self
}
pub fn config_none(&mut self) -> &mut Self {
self.config_source = ConfigSource::None;
self
}
pub fn build_dir(&mut self, dir: &str) -> &mut Self {
self.build_dir = Some(dir.to_string());
self.build_dir_set = true;
self
}
pub fn libs_mode(&mut self, mode: LibsMode) -> &mut Self {
self.libs_mode = mode;
self.libs_mode_set = true;
self
}
pub fn libs_dir(&mut self, dir: &str) -> &mut Self {
self.libs_mode = LibsMode::Local(dir.to_string());
self.libs_mode_set = true;
self
}
pub fn defaults(&mut self) -> &mut Self {
self.var("let")
.assign("=")
.typing(Typing::Dynamic)
.implicit_mutability(ImplicitMutability::Mutable)
.condition("if", "{}")
.loop_("loop", "{}")
.for_in("for", "in")
.func("fn", "{}")
.return_("return")
.break_("break")
.import("import")
}
pub fn token(&mut self, name: &str) -> &mut Self {
if !self.tokens.contains(&name.to_string()) {
self.tokens.push(name.to_string());
}
self
}
pub fn action(&mut self, keyword: &str, f: Action) -> &mut Self {
self.token(keyword);
self.actions.insert(keyword.to_string(), f);
self
}
pub fn args(&mut self, keyword: &str, format: &str) -> &mut Self {
self.args_formats
.insert(keyword.to_string(), format.to_string());
self
}
pub fn var(&mut self, keyword: &str) -> &mut Self {
self.immut_kw(keyword);
self
}
pub fn mut_kw(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.mut_keyword = Some(keyword.to_string());
self
}
pub fn immut_kw(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.immut_keyword = Some(keyword.to_string());
self
}
pub fn typing(&mut self, t: Typing) -> &mut Self {
self.typing = t;
self.typing_set = true;
self
}
pub fn implicit_mutability(&mut self, mode: ImplicitMutability) -> &mut Self {
self.implicit_mutability = mode;
self.implicit_mutability_set = true;
self
}
pub fn ops(&mut self, ops: &[&str]) -> &mut Self {
self.ops = ops.iter().map(|s| s.to_string()).collect();
self
}
pub fn compare(&mut self, ops: &[&str]) -> &mut Self {
self.compare_ops = ops.iter().map(|s| s.to_string()).collect();
self
}
pub fn logical(&mut self, ops: &[&str]) -> &mut Self {
self.logical_ops = ops.iter().map(|s| s.to_string()).collect();
self
}
pub fn blocks(&mut self, b: Blocks) -> &mut Self {
self.blocks = b;
self
}
pub fn semicolon(&mut self, s: Semicolon) -> &mut Self {
self.semicolon = s;
self
}
pub fn strings(&mut self, s: Strings) -> &mut Self {
self.strings = s;
self
}
pub fn interpolation(&mut self, syntax: &str) -> &mut Self {
self.interpolation = Some(syntax.to_string());
self
}
pub fn range(&mut self, op: &str) -> &mut Self {
self.range_op = Some(op.to_string());
self
}
pub fn null(&mut self, keyword: &str) -> &mut Self {
self.null_keyword = Some(keyword.to_string());
self.token(keyword);
self
}
pub fn array(&mut self, syntax: &str) -> &mut Self {
self.array_syntax = Some(syntax.to_string());
self
}
pub fn comment(&mut self, line: &str, block: &str) -> &mut Self {
self.comment_line = line.to_string();
if block.len() >= 4 {
let mid = block.len() / 2;
self.comment_block = (block[..mid].to_string(), block[mid..].to_string());
}
self
}
pub fn assign(&mut self, op: &str) -> &mut Self {
self.assign_op = Some(op.to_string());
self
}
pub fn condition(&mut self, keyword: &str, _block: &str) -> &mut Self {
self.token(keyword);
self.token("else");
self.if_keyword = Some(keyword.to_string());
self
}
pub fn loop_(&mut self, keyword: &str, _block: &str) -> &mut Self {
self.token(keyword);
self.loop_keyword = Some(keyword.to_string());
self
}
pub fn for_in(&mut self, for_kw: &str, in_kw: &str) -> &mut Self {
self.token(for_kw);
self.token(in_kw);
self.for_keyword = Some(for_kw.to_string());
self.in_keyword = Some(in_kw.to_string());
self
}
pub fn func(&mut self, keyword: &str, _block: &str) -> &mut Self {
self.token(keyword);
self.fn_keyword = Some(keyword.to_string());
self
}
pub fn return_(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.return_keyword = Some(keyword.to_string());
self
}
pub fn break_(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.break_keyword = Some(keyword.to_string());
self
}
pub fn while_(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.while_keyword = Some(keyword.to_string());
self
}
pub fn switch_(&mut self, keyword: &str, case_kw: &str, default_kw: &str) -> &mut Self {
self.token(keyword);
self.token(case_kw);
self.token(default_kw);
self.switch_keyword = Some(keyword.to_string());
self.case_keyword = Some(case_kw.to_string());
self.default_keyword = Some(default_kw.to_string());
self
}
pub fn error(&mut self, try_kw: &str, catch_kw: &str, _block: &str) -> &mut Self {
self.token(try_kw);
self.token(catch_kw);
self.try_keyword = Some(try_kw.to_string());
self.catch_keyword = Some(catch_kw.to_string());
self
}
pub fn debug(&mut self, d: Debug) -> &mut Self {
self.debug_mode = d;
self.debug_enabled = true;
self
}
pub fn cache(&mut self, enabled: bool) -> &mut Self {
self.cache_enabled = enabled;
if !enabled {
self.cached_ast.clear();
self.cached_bc.clear();
}
self
}
pub fn on_error(&mut self, handler: ErrorHandler) -> &mut Self {
self.error_handler = Some(handler);
self
}
pub fn repl(&mut self) {
use std::io::{self, BufRead, Write};
println!("{} REPL v{}", self.name, self.version_str);
println!("Type 'exit' or 'quit' to exit\n");
let stdin = io::stdin();
for line in stdin.lock().lines() {
match line {
Ok(input) => {
let trimmed = input.trim();
if trimmed.is_empty() {
continue;
}
if trimmed == "exit" || trimmed == "quit" {
println!("Goodbye!");
break;
}
match self.run(trimmed) {
Ok(val) => {
if val != crate::types::Value::Null {
println!("{}", val);
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
Err(_) => break,
}
print!("{}> ", self.name);
io::stdout().flush().ok();
}
}
pub fn import(&mut self, keyword: &str) -> &mut Self {
self.token(keyword);
self.import_keyword = Some(keyword.to_string());
self
}
pub fn use_crate(&mut self, name: &str) -> &mut Self {
if !self.crate_modules.contains(&name.to_string()) {
self.crate_modules.push(name.to_string());
}
self.crates_set = true;
self
}
pub fn ffi(&mut self, lib_path: &str) -> &mut Self {
if !self.ffi_libs.contains(&lib_path.to_string()) {
self.ffi_libs.push(lib_path.to_string());
}
self.ffi_set = true;
self
}
pub fn stdlib(&mut self, modules: &[&str]) -> &mut Self {
self.stdlib_modules = modules.iter().map(|s| s.to_string()).collect();
self.stdlib_set = true;
self
}
pub fn lib(&mut self, name: &str) -> &mut Self {
if name.eq_ignore_ascii_case("base") {
self.stdlib(&["math", "io", "fs", "net", "time", "http"]);
} else if name.eq_ignore_ascii_case("full") {
self.stdlib(&["math", "io", "fs", "net", "time", "http"]);
self.use_crate("rand");
}
self
}
pub fn module(&mut self, module: Module) -> &mut Self {
if !self.custom_module_names.contains(&module.name) {
self.custom_module_names.push(module.name.clone());
self.custom_modules.push(module);
}
self
}
pub fn builtin(&mut self, name: &str, action: crate::types::Action) {
self.actions.insert(name.to_string(), action);
}
pub fn builtin_simple(
&mut self,
name: &str,
func: fn(crate::types::Value) -> crate::types::Value,
) {
self.actions.insert(
name.to_string(),
Box::new(move |_interp, args| {
args.first()
.cloned()
.map(|v| func(v))
.unwrap_or(crate::types::Value::Null)
}),
);
}
pub fn run_file(&mut self, path: &str) -> Result<crate::types::Value, String> {
match std::fs::read_to_string(path) {
Ok(code) => self.run(&code),
Err(e) => Err(format!("Cannot read file '{}': {}", path, e)),
}
}
pub fn run_files(&mut self, paths: &[&str]) -> Result<crate::types::Value, String> {
let mut last = crate::types::Value::Null;
for path in paths {
last = self.run_file(path)?;
}
Ok(last)
}
pub fn struct_(&mut self, keyword: &str, _block: &str) {
self.token(keyword);
self.struct_keyword = Some(keyword.to_string());
}
pub fn enum_(&mut self, keyword: &str, _block: &str) {
self.token(keyword);
self.enum_keyword = Some(keyword.to_string());
}
pub fn dist(&mut self, d: Dist) -> &mut Self {
self.dist = d;
self
}
pub fn compat(&mut self, c: Compat) -> &mut Self {
self.compat = c;
self
}
pub fn cli(&mut self, name: &str, cmd: Cli) {
self.cli_commands.push((name.to_string(), cmd));
}
pub fn run(&mut self, source: &str) -> Result<Value, String> {
self.execute(source)
}
pub fn run_path(&mut self, path: &str) -> Result<Value, String> {
let path = std::path::Path::new(path);
if path.extension().and_then(|s| s.to_str()) == Some("lbc") {
return self.run_bytecode_file(path.to_string_lossy().as_ref());
}
let source = std::fs::read_to_string(path)
.map_err(|e| format!("Cannot read file '{}': {}", path.display(), e))?;
self.base_dir = path.parent().map(|p| p.to_path_buf());
let (source, _) = self.prepare_source(&source, Some(path))?;
if let Some(bc_path) = self.find_cached_bytecode(path) {
if let Ok(res) = self.run_bytecode_file(bc_path.to_string_lossy().as_ref()) {
return Ok(res);
}
}
self.execute(&source)
}
pub fn check_path(&mut self, path: &str) -> Result<(), String> {
let path = std::path::Path::new(path);
let source = std::fs::read_to_string(path)
.map_err(|e| format!("Cannot read file '{}': {}", path.display(), e))?;
self.base_dir = path.parent().map(|p| p.to_path_buf());
let (source, _) = self.prepare_source(&source, Some(path))?;
self.compile_source(&source).map(|_| ())
}
pub fn build_path(&mut self, path: &str) -> Result<String, String> {
let path = std::path::Path::new(path);
let source = std::fs::read_to_string(path)
.map_err(|e| format!("Cannot read file '{}': {}", path.display(), e))?;
self.base_dir = path.parent().map(|p| p.to_path_buf());
let (source, _) = self.prepare_source(&source, Some(path))?;
let out = self.resolve_bytecode_path(path);
if let Some(parent) = std::path::Path::new(&out).parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("Cannot create build dir: {}", e))?;
}
if let LibsMode::Local(dir) = &self.libs_mode {
let base = self
.base_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let libs_dir = base.join(dir);
let _ = std::fs::create_dir_all(libs_dir);
}
self.save_bytecode(&source, &out)?;
Ok(out)
}
pub fn build_exe_path(&mut self, path: &str) -> Result<String, String> {
let path = std::path::Path::new(path);
let source = std::fs::read_to_string(path)
.map_err(|e| format!("Cannot read file '{}': {}", path.display(), e))?;
self.base_dir = path.parent().map(|p| p.to_path_buf());
let (source, _) = self.prepare_source(&source, Some(path))?;
let bc_path = std::path::PathBuf::from(self.resolve_bytecode_path(path));
if let Some(parent) = bc_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("Cannot create build dir: {}", e))?;
}
self.save_bytecode(&source, bc_path.to_string_lossy().as_ref())?;
let exe_path = std::path::PathBuf::from(self.resolve_exe_path(path));
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("app");
let project_name = sanitize_crate_name(&format!("{}_exe", stem));
let build_root = exe_path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let proj_dir = build_root.join(&project_name);
let src_dir = proj_dir.join("src");
std::fs::create_dir_all(&src_dir)
.map_err(|e| format!("Cannot create build project: {}", e))?;
let crate_version = env!("CARGO_PKG_VERSION");
let cargo_toml = format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = "2024"
[dependencies]
langkit = "{}"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
panic = "abort"
"#,
project_name, crate_version
);
std::fs::write(proj_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Cannot write Cargo.toml: {}", e))?;
let main_rs = self.render_runtime_setup(&bc_path);
std::fs::write(src_dir.join("main.rs"), main_rs)
.map_err(|e| format!("Cannot write build main.rs: {}", e))?;
let status = std::process::Command::new("cargo")
.arg("build")
.arg("--release")
.current_dir(&proj_dir)
.status()
.map_err(|e| format!("Failed to run cargo: {}", e))?;
if !status.success() {
return Err("Cargo build failed".to_string());
}
let built = proj_dir
.join("target")
.join("release")
.join(format!("{}{}", project_name, std::env::consts::EXE_SUFFIX));
std::fs::copy(&built, &exe_path)
.map_err(|e| format!("Cannot copy executable: {}", e))?;
try_strip_exe(&exe_path);
Ok(exe_path.to_string_lossy().to_string())
}
pub fn analyze_source(&mut self, path: Option<&str>, source: &str) -> Result<(), String> {
let path = path.map(std::path::Path::new);
if let Some(p) = path {
self.base_dir = p.parent().map(|p| p.to_path_buf());
}
let (source, _) = self.prepare_source(source, path)?;
self.compile_source_plain(&source).map(|_| ())
}
pub fn compile(&mut self, source: &str) -> Result<Vec<Instr>, String> {
let (source, _) = self.prepare_source(source, None)?;
self.compile_source(&source)
}
pub fn save_bytecode(&mut self, source: &str, path: &str) -> Result<(), String> {
let bc = self.compile(source)?;
bytecode::write_file(path, &bc)
}
pub fn run_bytecode_file(&mut self, path: &str) -> Result<Value, String> {
let bc = bytecode::read_file(path)?;
self.run_bytecode(&bc)
}
pub fn run_bytecode(&mut self, instrs: &[Instr]) -> Result<Value, String> {
let mut interp = self.prepare_interpreter();
let signal = interp.run_vm_block(instrs).map_err(|e| match self.debug_mode {
Debug::Full => format!("{}\nVars: {:?}", e, interp.snapshot_vars()),
Debug::StackTrace => e,
});
self.restore_from_interpreter(interp);
let signal = signal?;
match signal {
crate::interpreter::Signal::Return(v) => Ok(v),
crate::interpreter::Signal::Break => Ok(Value::Null),
}
}
pub fn run_bytecode_bytes(&mut self, bytes: &[u8]) -> Result<Value, String> {
let bc = bytecode::decode(bytes)?;
self.run_bytecode(&bc)
}
pub fn launch(&mut self) {
let args: Vec<String> = std::env::args().collect();
let name = self.name.clone();
let ext = self.extension.clone();
let commands = std::mem::take(&mut self.cli_commands);
let mut cli_runner = CliRunner::new(&name, &ext);
for (n, c) in commands {
cli_runner.register(&n, c);
}
let lang_ptr = self as *mut Lang;
cli_runner.run_with_args_ex(
args,
&|src| unsafe { (*lang_ptr).execute(src).map(|_| ()) },
&|path| unsafe { (*lang_ptr).run_path(path).map(|_| ()) },
&|path| unsafe { (*lang_ptr).check_path(path) },
&|path| unsafe { (*lang_ptr).build_path(path) },
Some(&|path| unsafe { (*lang_ptr).build_exe_path(path) }),
&|path, text| unsafe { (*lang_ptr).analyze_source(path, text) },
);
}
fn execute(&mut self, source: &str) -> Result<Value, String> {
let (source, _) = self.prepare_source(source, None)?;
let cacheable = self.cache_enabled && !self.source_has_imports(&source);
let key = if cacheable { Some(self.cache_key(&source)) } else { None };
let mut ast = if let Some(k) = key {
if let Some(cached) = self.cached_ast.get(&k) {
cached.clone()
} else {
let parsed = self.parse_source_to_ast(&source, "<main>", true)?;
self.cached_ast.insert(k, parsed.clone());
parsed
}
} else {
self.parse_source_to_ast(&source, "<main>", true)?
};
let base = self
.base_dir
.clone()
.or_else(|| std::env::current_dir().ok());
ast = self.expand_imports(ast, base.as_deref(), true)?;
let mut interp = self.prepare_interpreter();
let signal = match self.mode {
Mode::Interpreter => interp.eval_block(&ast),
Mode::Compiler => {
let bc = if let Some(k) = key {
if let Some(cached) = self.cached_bc.get(&k) {
cached.clone()
} else {
let compiled = compile_block(&ast)?;
self.cached_bc.insert(k, compiled.clone());
compiled
}
} else {
compile_block(&ast)?
};
interp.run_vm_block(&bc)
}
Mode::Both => match compile_block(&ast) {
Ok(bc) => interp
.run_vm_block(&bc)
.or_else(|_| interp.eval_block(&ast)),
Err(_) => interp.eval_block(&ast),
},
};
let signal = signal.map_err(|e| match self.debug_mode {
Debug::Full => format!("{}\nVars: {:?}", e, interp.snapshot_vars()),
Debug::StackTrace => e,
});
let warnings = interp.warnings.clone();
self.restore_from_interpreter(interp);
if !warnings.is_empty() {
for w in warnings {
eprintln!("Warning: {}", w);
}
}
let signal = signal?;
match signal {
crate::interpreter::Signal::Return(v) => Ok(v),
crate::interpreter::Signal::Break => Ok(Value::Null),
}
}
}
impl Default for Lang {
fn default() -> Self {
Self::new()
}
}
impl Lang {
fn collect_keywords(&self) -> Vec<String> {
let mut all_keywords = self.tokens.clone();
for k in [
&self.mut_keyword,
&self.immut_keyword,
&self.fn_keyword,
&self.return_keyword,
&self.if_keyword,
&self.loop_keyword,
&self.for_keyword,
&self.in_keyword,
&self.try_keyword,
&self.catch_keyword,
&self.break_keyword,
&self.import_keyword,
&self.while_keyword,
&self.switch_keyword,
&self.case_keyword,
&self.default_keyword,
&self.struct_keyword,
&self.enum_keyword,
]
.into_iter()
.flatten()
{
if !all_keywords.contains(k) {
all_keywords.push(k.clone());
}
}
for kw in ["else", "in"] {
if !all_keywords.contains(&kw.to_string()) {
all_keywords.push(kw.to_string());
}
}
all_keywords
}
fn source_has_imports(&self, source: &str) -> bool {
let kw = self
.import_keyword
.clone()
.unwrap_or_else(|| "import".to_string());
source.contains(&format!("{} ", kw)) || source.contains(&format!("{}\"", kw))
}
fn cache_key(&self, source: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
source.hash(&mut hasher);
self.collect_keywords().hash(&mut hasher);
self.comment_line.hash(&mut hasher);
self.comment_block.0.hash(&mut hasher);
self.comment_block.1.hash(&mut hasher);
let strings_tag = match self.strings {
Strings::Double => 0u8,
Strings::Single => 1u8,
Strings::Both => 2u8,
};
strings_tag.hash(&mut hasher);
self.array_syntax.hash(&mut hasher);
self.range_op.hash(&mut hasher);
hasher.finish()
}
fn compile_source(&mut self, source: &str) -> Result<Vec<Instr>, String> {
self.compile_source_inner(source, true)
}
fn compile_source_plain(&mut self, source: &str) -> Result<Vec<Instr>, String> {
self.compile_source_inner(source, false)
}
fn compile_source_inner(
&mut self,
source: &str,
with_caret: bool,
) -> Result<Vec<Instr>, String> {
let cacheable = self.cache_enabled && !self.source_has_imports(source);
let key = if cacheable { Some(self.cache_key(source)) } else { None };
if let Some(k) = key {
if let Some(cached) = self.cached_bc.get(&k) {
return Ok(cached.clone());
}
}
let ast = if let Some(k) = key {
if let Some(cached) = self.cached_ast.get(&k) {
cached.clone()
} else {
let parsed = self.parse_source_to_ast(source, "<main>", with_caret)?;
self.cached_ast.insert(k, parsed.clone());
parsed
}
} else {
self.parse_source_to_ast(source, "<main>", with_caret)?
};
let base = self
.base_dir
.clone()
.or_else(|| std::env::current_dir().ok());
let ast = self.expand_imports(ast, base.as_deref(), with_caret)?;
let bc = compile_block(&ast)?;
if let Some(k) = key {
self.cached_bc.insert(k, bc.clone());
}
Ok(bc)
}
fn parse_source_to_ast(
&self,
source: &str,
file_name: &str,
with_caret: bool,
) -> Result<Vec<Expr>, String> {
let all_keywords = self.collect_keywords();
let mut lexer = Lexer::new(
source,
all_keywords.clone(),
self.comment_line.clone(),
self.comment_block.0.clone(),
self.comment_block.1.clone(),
self.strings.clone(),
);
let tokens = lexer.tokenize().map_err(|e| {
if with_caret {
format!("Lexer error: {}", self.with_caret(source, &e))
} else {
format!("Lexer error: {}", e)
}
})?;
let mut parser = Parser::new(tokens, file_name.to_string())
.with_keywords(all_keywords)
.with_mut(self.mut_keyword.clone())
.with_immut(self.immut_keyword.clone())
.with_fn(self.fn_keyword.clone())
.with_return(self.return_keyword.clone())
.with_if(self.if_keyword.clone())
.with_loop(self.loop_keyword.clone())
.with_for(self.for_keyword.clone())
.with_in(self.in_keyword.clone())
.with_try(self.try_keyword.clone())
.with_catch(self.catch_keyword.clone())
.with_break(self.break_keyword.clone())
.with_import(self.import_keyword.clone())
.with_while(self.while_keyword.clone())
.with_switch(self.switch_keyword.clone())
.with_case(self.case_keyword.clone())
.with_default(self.default_keyword.clone())
.with_struct(self.struct_keyword.clone());
parser.parse().map_err(|e| {
if with_caret {
format!("Parse error: {}", self.with_caret(source, &e))
} else {
format!("Parse error: {}", e)
}
})
}
fn expand_imports(
&mut self,
ast: Vec<Expr>,
base_dir: Option<&std::path::Path>,
with_caret: bool,
) -> Result<Vec<Expr>, String> {
let mut loaded = HashSet::new();
let mut in_progress = HashSet::new();
self.expand_imports_inner(ast, base_dir, with_caret, &mut loaded, &mut in_progress)
}
fn expand_imports_inner(
&mut self,
ast: Vec<Expr>,
base_dir: Option<&std::path::Path>,
with_caret: bool,
loaded: &mut HashSet<std::path::PathBuf>,
in_progress: &mut HashSet<std::path::PathBuf>,
) -> Result<Vec<Expr>, String> {
let mut out = Vec::new();
for expr in ast {
if let Expr::Import(path) = expr {
match self.resolve_import_path(&path, base_dir) {
Ok(import_path) => {
if in_progress.contains(&import_path) {
return Err(format!(
"Import cycle detected: module '{}' is already being imported.",
import_path.display()
));
}
if loaded.contains(&import_path) {
continue;
}
loaded.insert(import_path.clone());
in_progress.insert(import_path.clone());
let src = std::fs::read_to_string(&import_path).map_err(|e| {
format!("Cannot read import '{}': {}", import_path.display(), e)
})?;
let (src, _) = self.prepare_source(&src, Some(&import_path))?;
let module_ast = self.parse_source_to_ast(
&src,
&import_path.to_string_lossy(),
with_caret,
)?;
let module_ast = self.expand_imports_inner(
module_ast,
import_path.parent(),
with_caret,
loaded,
in_progress,
)?;
in_progress.remove(&import_path);
let module_name = import_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("module");
let prefixed = self.prefix_module_ast(module_name, module_ast);
out.extend(prefixed);
continue;
}
Err(candidates) => {
let paths = candidates
.iter()
.map(|c| format!(" - {}", c.display()))
.collect::<Vec<_>>()
.join("\n");
return Err(format!(
"Cannot resolve import '{}'.\nSearched paths:\n{}",
path, paths
));
}
}
}
out.push(expr);
}
Ok(out)
}
fn resolve_import_path(
&self,
raw: &str,
base_dir: Option<&std::path::Path>,
) -> Result<std::path::PathBuf, Vec<std::path::PathBuf>> {
let input = std::path::Path::new(raw);
let has_ext = input.extension().is_some();
let mut candidates = Vec::new();
if input.is_absolute() {
candidates.push(input.to_path_buf());
} else if let Some(base) = base_dir {
candidates.push(base.join(input));
if !has_ext {
candidates.push(base.join(format!("{}{}", raw, self.extension)));
}
}
if let LibsMode::Local(dir) = &self.libs_mode {
let base = base_dir
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let libs_base = base.join(dir);
candidates.push(libs_base.join(input));
if !has_ext {
candidates.push(libs_base.join(format!("{}{}", raw, self.extension)));
}
}
if !has_ext {
candidates.push(std::path::PathBuf::from(format!(
"{}{}",
raw, self.extension
)));
}
for c in &candidates {
if c.exists() && c.is_file() {
return Ok(c.clone());
}
}
Err(candidates)
}
fn prefix_module_ast(&self, module: &str, ast: Vec<Expr>) -> Vec<Expr> {
let defs = self.collect_module_defs(&ast);
ast.into_iter()
.map(|e| self.prefix_expr(e, module, &defs))
.collect()
}
fn collect_module_defs(&self, ast: &[Expr]) -> HashSet<String> {
let mut defs = HashSet::new();
for expr in ast {
match expr {
Expr::VarDecl { name, .. } => {
defs.insert(name.clone());
}
Expr::FnDef { name, .. } => {
defs.insert(name.clone());
}
Expr::StructDef { name, .. } => {
defs.insert(name.clone());
}
_ => {}
}
}
defs
}
fn prefix_expr(&self, expr: Expr, module: &str, defs: &HashSet<String>) -> Expr {
match expr {
Expr::Ident(name) => {
if defs.contains(&name) {
Expr::Ident(format!("{}.{}", module, name))
} else {
Expr::Ident(name)
}
}
Expr::Assign { name, value } => {
let new_name = if defs.contains(&name) {
format!("{}.{}", module, name)
} else {
name
};
Expr::Assign {
name: new_name,
value: Box::new(self.prefix_expr(*value, module, defs)),
}
}
Expr::VarDecl {
name,
type_hint,
value,
is_mut,
} => {
let new_name = if defs.contains(&name) {
format!("{}.{}", module, name)
} else {
name
};
Expr::VarDecl {
name: new_name,
type_hint,
value: Box::new(self.prefix_expr(*value, module, defs)),
is_mut,
}
}
Expr::Call {
keyword,
args,
line,
file,
} => {
let key = if defs.contains(&keyword) {
format!("{}.{}", module, keyword)
} else {
keyword
};
Expr::Call {
keyword: key,
args: args
.into_iter()
.map(|a| self.prefix_expr(a, module, defs))
.collect(),
line,
file,
}
}
Expr::BinOp { left, op, right } => Expr::BinOp {
left: Box::new(self.prefix_expr(*left, module, defs)),
op,
right: Box::new(self.prefix_expr(*right, module, defs)),
},
Expr::Array(items) => Expr::Array(
items
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
),
Expr::Range(a, b) => Expr::Range(
Box::new(self.prefix_expr(*a, module, defs)),
Box::new(self.prefix_expr(*b, module, defs)),
),
Expr::If {
cond,
then_block,
else_block,
} => Expr::If {
cond: Box::new(self.prefix_expr(*cond, module, defs)),
then_block: then_block
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
else_block: else_block.map(|b| {
b.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect()
}),
},
Expr::Loop { body } => Expr::Loop {
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
},
Expr::While { cond, body } => Expr::While {
cond: Box::new(self.prefix_expr(*cond, module, defs)),
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
},
Expr::For { var, iter, body } => {
let new_var = if defs.contains(&var) {
format!("{}.{}", module, var)
} else {
var
};
Expr::For {
var: new_var,
iter: Box::new(self.prefix_expr(*iter, module, defs)),
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
}
}
Expr::FnDef { name, params, body } => {
let new_name = if defs.contains(&name) {
format!("{}.{}", module, name)
} else {
name
};
Expr::FnDef {
name: new_name,
params,
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
}
}
Expr::StructDef { name, fields } => {
let new_name = if defs.contains(&name) {
format!("{}.{}", module, name)
} else {
name
};
Expr::StructDef {
name: new_name,
fields,
}
}
Expr::Return(val) => Expr::Return(Box::new(self.prefix_expr(*val, module, defs))),
Expr::Break => Expr::Break,
Expr::Switch {
value,
cases,
default,
} => Expr::Switch {
value: Box::new(self.prefix_expr(*value, module, defs)),
cases: cases
.into_iter()
.map(|(c, b)| {
(
self.prefix_expr(c, module, defs),
b.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
)
})
.collect(),
default: default.map(|b| {
b.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect()
}),
},
Expr::Lambda { params, body } => Expr::Lambda {
params,
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
},
Expr::Try { body, catch_body } => Expr::Try {
body: body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
catch_body: catch_body
.into_iter()
.map(|e| self.prefix_expr(e, module, defs))
.collect(),
},
Expr::Import(path) => Expr::Import(path),
Expr::Interpolated(parts) => Expr::Interpolated(
parts
.into_iter()
.map(|p| match p {
crate::parser::InterpolPart::Text(t) => {
crate::parser::InterpolPart::Text(t)
}
crate::parser::InterpolPart::Expr(e) => {
crate::parser::InterpolPart::Expr(self.prefix_expr(e, module, defs))
}
})
.collect(),
),
Expr::Literal(v) => Expr::Literal(v),
}
}
fn expand_macros(
&self,
source: &str,
base_dir: Option<&std::path::Path>,
) -> Result<String, String> {
let mut out = source.to_string();
loop {
let Some(idx) = out.find("@include(") else {
break;
};
let mut result = String::new();
result.push_str(&out[..idx]);
let rest = &out[idx + "@include(".len()..];
let close = rest
.find(')')
.ok_or_else(|| "Unclosed @include".to_string())?;
let arg = rest[..close].trim();
let path = arg.trim_matches('"').trim_matches('\'').to_string();
let base = base_dir
.map(|p| p.to_path_buf())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let full = if std::path::Path::new(&path).is_absolute() {
std::path::PathBuf::from(&path)
} else {
base.join(&path)
};
let content = std::fs::read_to_string(&full)
.map_err(|e| format!("Cannot read include '{}': {}", full.display(), e))?;
result.push_str(&content);
result.push_str(&rest[close + 1..]);
out = result;
}
loop {
let Some(idx) = out.find("@repeat(") else {
break;
};
let mut result = String::new();
result.push_str(&out[..idx]);
let rest = &out[idx + "@repeat(".len()..];
let close = rest
.find(')')
.ok_or_else(|| "Unclosed @repeat".to_string())?;
let count_str = rest[..close].trim();
let count: usize = count_str
.parse()
.map_err(|_| "Invalid repeat count".to_string())?;
let after = rest[close + 1..].trim_start();
if !after.starts_with('{') {
return Err("Expected '{' after @repeat(n)".to_string());
}
let mut depth = 0usize;
let mut end_idx = None;
for (i, ch) in after.char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth = depth.saturating_sub(1);
if depth == 0 {
end_idx = Some(i);
break;
}
}
_ => {}
}
}
let end = end_idx.ok_or_else(|| "Unclosed @repeat block".to_string())?;
let block = &after[1..end];
for _ in 0..count {
result.push_str(block);
result.push('\n');
}
result.push_str(&after[end + 1..]);
out = result;
}
Ok(out)
}
fn prepare_source(
&mut self,
source: &str,
path: Option<&std::path::Path>,
) -> Result<(String, bool), String> {
let mut changed = false;
if let ConfigSource::Toml(path_str) = &self.config_source {
let cfg_path = if std::path::Path::new(path_str).is_absolute() {
std::path::PathBuf::from(path_str)
} else if let Some(p) = path.and_then(|p| p.parent()) {
p.join(path_str)
} else {
std::path::PathBuf::from(path_str)
};
let cfg = std::fs::read_to_string(&cfg_path)
.map_err(|e| format!("Cannot read config '{}': {}", cfg_path.display(), e))?;
let map = parse_config_kv(&cfg);
self.apply_config_map(&map);
changed = true;
}
if matches!(self.config_source, ConfigSource::Inline) {
let (map, stripped) = parse_inline_config(source);
if let Some(map) = map {
self.apply_config_map(&map);
changed = true;
return Ok((stripped, changed));
}
}
let base_dir = path
.and_then(|p| p.parent())
.map(|p| p.to_path_buf())
.or_else(|| self.base_dir.clone());
let expanded = self.expand_macros(source, base_dir.as_deref())?;
if expanded != source {
changed = true;
}
Ok((expanded, changed))
}
fn apply_config_map(&mut self, map: &std::collections::HashMap<String, String>) {
if let Some(name) = map.get("name") {
if !self.name_set {
self.name = name.clone();
}
}
if let Some(ext) = map.get("extension") {
if !self.extension_set {
self.extension = normalize_extension(ext);
}
}
if let Some(dir) = map.get("build_dir") {
if !self.build_dir_set {
self.build_dir = Some(dir.clone());
}
}
if let Some(mode) = map.get("mode") {
if !self.mode_set {
if let Some(m) = parse_mode(mode) {
self.mode = m;
}
}
}
if let Some(t) = map.get("typing") {
if !self.typing_set {
if let Some(v) = parse_typing(t) {
self.typing = v;
}
}
}
if let Some(m) = map.get("implicit_mutability") {
if !self.implicit_mutability_set {
if let Some(v) = parse_implicit_mutability(m) {
self.implicit_mutability = v;
}
}
}
if let Some(stdlib) = map.get("stdlib") {
if !self.stdlib_set {
self.stdlib_modules = parse_list(stdlib);
}
}
if let Some(lib) = map.get("lib").or_else(|| map.get("libs")) {
if !self.stdlib_set {
self.lib(lib);
}
}
if let Some(crates) = map.get("crates") {
if !self.crates_set {
self.crate_modules = parse_list(crates);
}
}
if let Some(ffi) = map.get("ffi") {
if !self.ffi_set {
self.ffi_libs = parse_list(ffi);
}
}
if let Some(pm) = map.get("package_manager") {
if self.package_manager.is_none() {
self.package_manager = Some(pm.clone());
}
}
if let Some(safe) = map.get("safe").or_else(|| map.get("sandbox")) {
if safe.eq_ignore_ascii_case("true") || safe.eq_ignore_ascii_case("safe") {
self.safe_mode = SafeMode::Safe;
} else if safe.eq_ignore_ascii_case("false") || safe.eq_ignore_ascii_case("full") {
self.safe_mode = SafeMode::Full;
}
}
if let Some(libs_mode) = map.get("libs_mode") {
if !self.libs_mode_set {
self.libs_mode = if libs_mode.eq_ignore_ascii_case("cargo") {
LibsMode::Cargo
} else {
let dir = map
.get("libs_dir")
.cloned()
.unwrap_or_else(|| "libs".to_string());
LibsMode::Local(dir)
};
}
} else if let Some(libs_dir) = map.get("libs_dir") {
if !self.libs_mode_set {
self.libs_mode = LibsMode::Local(libs_dir.clone());
}
}
}
fn resolve_bytecode_path(&self, source_path: &std::path::Path) -> String {
let stem = source_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("out");
let file_name = format!("{}.lbc", stem);
if let Some(build_dir) = &self.build_dir {
let build_dir = std::path::Path::new(build_dir);
let base = if build_dir.is_absolute() {
build_dir.to_path_buf()
} else if let Some(base) = source_path.parent() {
base.join(build_dir)
} else {
build_dir.to_path_buf()
};
return base.join(file_name).to_string_lossy().to_string();
}
if let Some(parent) = source_path.parent() {
return parent.join(file_name).to_string_lossy().to_string();
}
file_name
}
fn resolve_exe_path(&self, source_path: &std::path::Path) -> String {
let stem = source_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("out");
let file_name = format!("{}{}", stem, std::env::consts::EXE_SUFFIX);
if let Some(build_dir) = &self.build_dir {
let build_dir = std::path::Path::new(build_dir);
let base = if build_dir.is_absolute() {
build_dir.to_path_buf()
} else if let Some(base) = source_path.parent() {
base.join(build_dir)
} else {
build_dir.to_path_buf()
};
return base.join(file_name).to_string_lossy().to_string();
}
if let Some(parent) = source_path.parent() {
return parent.join(file_name).to_string_lossy().to_string();
}
file_name
}
fn find_cached_bytecode(&self, source_path: &std::path::Path) -> Option<std::path::PathBuf> {
let bc_path = std::path::PathBuf::from(self.resolve_bytecode_path(source_path));
let bc_meta = std::fs::metadata(&bc_path).ok()?;
let src_meta = std::fs::metadata(source_path).ok()?;
let bc_time = bc_meta.modified().ok()?;
let src_time = src_meta.modified().ok()?;
if bc_time > src_time {
Some(bc_path)
} else {
None
}
}
fn resolve_ffi_libs(&self) -> Vec<String> {
let mut out = Vec::new();
for lib in &self.ffi_libs {
let lib_path = std::path::Path::new(lib);
if lib_path.is_absolute() || lib_path.exists() {
out.push(lib.clone());
continue;
}
if let LibsMode::Local(dir) = &self.libs_mode {
let base = self
.base_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let candidate = base.join(dir).join(lib);
out.push(candidate.to_string_lossy().to_string());
} else {
out.push(lib.clone());
}
}
out
}
fn with_caret(&self, source: &str, err: &str) -> String {
if let Some((line, col, msg)) = parse_line_col(err) {
let lines: Vec<&str> = source.lines().collect();
if line == 0 || line > lines.len() {
return format!("Line {}:{}: {}", line, col, msg);
}
let text = lines[line - 1];
let pad = format!("{}", line);
let caret_pos = col.saturating_sub(1);
let mut caret = String::new();
caret.push_str(&" ".repeat(caret_pos));
caret.push('^');
return format!(
"Line {}:{}: {}\n {} | {}\n {} | {}",
line,
col,
msg,
pad,
text,
" ".repeat(pad.len()),
caret
);
}
err.to_string()
}
fn prepare_interpreter(&mut self) -> Interpreter {
let mut interp = Interpreter::new();
interp.set_typing(self.typing.clone());
interp.set_implicit_mutability(self.implicit_mutability.clone());
interp.set_safe_mode(self.safe_mode.clone());
interp.debug = self.debug_enabled;
for module in build_all_modules(&self.stdlib_modules, &self.crate_modules, &self.safe_mode) {
interp.register_module(module);
}
let custom_modules = std::mem::take(&mut self.custom_modules);
for module in custom_modules {
interp.register_module(module);
}
if self.safe_mode == SafeMode::Full {
interp.register_ffi_libs(self.resolve_ffi_libs());
}
if let Some(handler) = self.error_handler.take() {
interp.set_error_handler(handler);
}
for (name, action) in self.actions.drain() {
interp.register_action(&name, action);
}
interp
}
fn restore_from_interpreter(&mut self, mut interp: Interpreter) {
self.error_handler = Some(interp.error_handler);
self.actions = interp.actions;
let mut restored = Vec::new();
for name in &self.custom_module_names {
if let Some(module) = interp.modules.remove(name) {
restored.push(module);
}
}
self.custom_modules = restored;
}
fn render_runtime_setup(&self, bc_path: &std::path::Path) -> String {
let mut out = String::new();
out.push_str("use langkit::{Lang, Typing, ImplicitMutability, SafeMode};\n");
out.push_str("use langkit::bytecode;\n\n");
out.push_str("fn main() {\n");
out.push_str(" let mut lang = Lang::new();\n");
out.push_str(&format!(" lang.name({:?});\n", self.name));
out.push_str(&format!(" lang.extension({:?});\n", self.extension));
out.push_str(&format!(
" lang.typing(Typing::{});\n",
match self.typing {
Typing::Static => "Static",
Typing::Dynamic => "Dynamic",
Typing::Optional => "Optional",
}
));
out.push_str(&format!(
" lang.implicit_mutability(ImplicitMutability::{});\n",
match self.implicit_mutability {
ImplicitMutability::Mutable => "Mutable",
ImplicitMutability::Immutable => "Immutable",
ImplicitMutability::Infer => "Infer",
}
));
if self.safe_mode == SafeMode::Safe {
out.push_str(" lang.safe_mode(SafeMode::Safe);\n");
}
if !self.stdlib_modules.is_empty() {
let stds = self
.stdlib_modules
.iter()
.map(|s| format!("{:?}", s))
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(" lang.stdlib(&[{}]);\n", stds));
}
for c in &self.crate_modules {
out.push_str(&format!(" lang.use_crate({:?});\n", c));
}
if self.debug_enabled {
out.push_str(" lang.debug(langkit::Debug::StackTrace);\n");
}
let bc_literal = format!("{:?}", bc_path.to_string_lossy());
out.push_str(&format!(
" let bytes: &[u8] = include_bytes!({});\n",
bc_literal
));
out.push_str(" let instrs = bytecode::decode(bytes).expect(\"Bad bytecode\");\n");
out.push_str(" if let Err(e) = lang.run_bytecode(&instrs) {\n");
out.push_str(" eprintln!(\"Runtime error: {}\", e);\n");
out.push_str(" std::process::exit(1);\n");
out.push_str(" }\n");
out.push_str("}\n");
out
}
}
fn sanitize_crate_name(name: &str) -> String {
let mut out = String::new();
for (i, ch) in name.chars().enumerate() {
if (ch.is_ascii_alphanumeric() || ch == '_') && !(i == 0 && ch.is_ascii_digit()) {
out.push(ch.to_ascii_lowercase());
} else {
out.push('_');
}
}
if out.is_empty() {
"_langkit_exe".to_string()
} else {
out
}
}
fn try_strip_exe(path: &std::path::Path) {
let tools = ["llvm-strip", "strip"];
for tool in tools {
let status = std::process::Command::new(tool)
.arg(path)
.status();
if let Ok(status) = status {
if status.success() {
break;
}
}
}
}
fn parse_config_kv(input: &str) -> std::collections::HashMap<String, String> {
let mut map = std::collections::HashMap::new();
for raw in input.lines() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with("//") {
continue;
}
let (key, val) = if let Some(idx) = line.find('=') {
(&line[..idx], &line[idx + 1..])
} else if let Some(idx) = line.find(':') {
(&line[..idx], &line[idx + 1..])
} else {
continue;
};
let key = key.trim().to_lowercase();
let mut val = val.trim().to_string();
if (val.starts_with('"') && val.ends_with('"'))
|| (val.starts_with('\'') && val.ends_with('\''))
{
val = val[1..val.len() - 1].to_string();
}
map.insert(key, val);
}
map
}
fn parse_inline_config(
source: &str,
) -> (Option<std::collections::HashMap<String, String>>, String) {
let mut lines = source.lines();
let mut first_non_empty = None;
let mut preview = Vec::new();
for _ in 0..5 {
if let Some(line) = lines.next() {
preview.push(line.to_string());
if first_non_empty.is_none() && !line.trim().is_empty() {
first_non_empty = Some(line.trim().to_string());
}
} else {
break;
}
}
if first_non_empty.as_deref() != Some("#langkit") {
return (None, source.to_string());
}
let mut map_text = String::new();
let mut output_lines: Vec<String> = Vec::new();
let mut in_block = false;
for (i, raw) in source.lines().enumerate() {
let line = raw.trim();
if i == 0 && line == "#langkit" {
in_block = true;
output_lines.push(String::new());
continue;
}
if in_block && line == "#end" {
in_block = false;
output_lines.push(String::new());
continue;
}
if in_block {
map_text.push_str(raw);
map_text.push('\n');
output_lines.push(String::new());
} else {
output_lines.push(raw.to_string());
}
}
let map = parse_config_kv(&map_text);
(Some(map), output_lines.join("\n"))
}
fn normalize_extension(ext: &str) -> String {
let trimmed = ext.trim();
if trimmed.starts_with('.') {
trimmed.to_string()
} else {
format!(".{}", trimmed)
}
}
fn parse_list(raw: &str) -> Vec<String> {
let mut s = raw.trim();
if s.starts_with('[') && s.ends_with(']') && s.len() >= 2 {
s = &s[1..s.len() - 1];
}
s.split(',')
.map(|p| p.trim().trim_matches('"').trim_matches('\'').to_string())
.filter(|p| !p.is_empty())
.collect()
}
fn parse_mode(raw: &str) -> Option<Mode> {
match raw.trim().to_lowercase().as_str() {
"interpreter" => Some(Mode::Interpreter),
"compiler" => Some(Mode::Compiler),
"both" => Some(Mode::Both),
_ => None,
}
}
fn parse_typing(raw: &str) -> Option<Typing> {
match raw.trim().to_lowercase().as_str() {
"static" => Some(Typing::Static),
"dynamic" => Some(Typing::Dynamic),
"optional" => Some(Typing::Optional),
_ => None,
}
}
fn parse_implicit_mutability(raw: &str) -> Option<ImplicitMutability> {
match raw.trim().to_lowercase().as_str() {
"mutable" => Some(ImplicitMutability::Mutable),
"immutable" => Some(ImplicitMutability::Immutable),
"infer" => Some(ImplicitMutability::Infer),
_ => None,
}
}
fn parse_line_col(err: &str) -> Option<(usize, usize, String)> {
let idx = err.find("Line ")?;
let rest = &err[idx + 5..];
let mut parts = rest.splitn(2, ':');
let line_str = parts.next()?.trim();
let mut col = 0usize;
let line = line_str.parse::<usize>().ok()?;
if let Some(after_line) = parts.next() {
let mut col_parts = after_line.splitn(2, ':');
if let Some(col_str) = col_parts.next() {
col = col_str.trim().parse::<usize>().unwrap_or(0);
}
let msg = col_parts.next().unwrap_or(after_line).trim().to_string();
return Some((line, col, msg));
}
None
}
fn build_all_modules(stdlib: &[String], crates: &[String], safe_mode: &SafeMode) -> Vec<Module> {
let mut modules = Vec::new();
for name in stdlib {
if let Some(module) = build_stdlib_module(name, safe_mode) {
modules.push(module);
}
}
if *safe_mode == SafeMode::Full {
for name in crates {
if let Some(module) = build_crate_module(name) {
modules.push(module);
}
}
}
modules
}
fn build_stdlib_module(name: &str, safe_mode: &SafeMode) -> Option<Module> {
if *safe_mode == SafeMode::Safe {
match name {
"fs" | "net" | "http" => return None,
_ => {}
}
}
match name {
"math" => Some(build_math_module()),
"io" => Some(build_io_module()),
"fs" => Some(build_fs_module()),
"net" => Some(build_net_module()),
"time" => Some(build_time_module()),
"http" => Some(build_http_module()),
_ => None,
}
}
fn build_crate_module(name: &str) -> Option<Module> {
match name {
"rand" => Some(build_rand_module()),
_ => None,
}
}
fn build_math_module() -> Module {
let mut module = Module::new("math");
module.action(
"sin",
Box::new(|_interp, args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.sin()),
Some(Value::Int(i)) => Value::Float((*i as f64).sin()),
_ => Value::Null,
}),
);
module.action(
"cos",
Box::new(|_interp, args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.cos()),
Some(Value::Int(i)) => Value::Float((*i as f64).cos()),
_ => Value::Null,
}),
);
module.action(
"sqrt",
Box::new(|_interp, args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.sqrt()),
Some(Value::Int(i)) => Value::Float((*i as f64).sqrt()),
_ => Value::Null,
}),
);
module.action(
"abs",
Box::new(|_interp, args| match args.first() {
Some(Value::Int(i)) => Value::Int(i.abs()),
Some(Value::Float(f)) => Value::Float(f.abs()),
_ => Value::Null,
}),
);
module.action(
"min",
Box::new(|_interp, args| match (args.first(), args.get(1)) {
(Some(Value::Int(a)), Some(Value::Int(b))) => Value::Int((*a).min(*b)),
(Some(Value::Float(a)), Some(Value::Float(b))) => Value::Float(a.min(*b)),
_ => Value::Null,
}),
);
module.action(
"max",
Box::new(|_interp, args| match (args.first(), args.get(1)) {
(Some(Value::Int(a)), Some(Value::Int(b))) => Value::Int((*a).max(*b)),
(Some(Value::Float(a)), Some(Value::Float(b))) => Value::Float(a.max(*b)),
_ => Value::Null,
}),
);
module
}
fn build_io_module() -> Module {
let mut module = Module::new("io");
module.action(
"println",
Box::new(|_interp, args| {
let s: Vec<String> = args.iter().map(|a| a.to_string()).collect();
println!("{}", s.join(" "));
Value::Null
}),
);
module.action(
"print",
Box::new(|_interp, args| {
use std::io::{self, Write};
let s: Vec<String> = args.iter().map(|a| a.to_string()).collect();
print!("{}", s.join(" "));
io::stdout().flush().ok();
Value::Null
}),
);
module.action(
"input",
Box::new(|_interp, args| {
use std::io::{self, Write};
if let Some(Value::Str(p)) = args.first() {
print!("{}", p);
io::stdout().flush().ok();
}
let mut line = String::new();
io::stdin().read_line(&mut line).ok();
Value::Str(line.trim().to_string())
}),
);
module
}
fn build_fs_module() -> Module {
let mut module = Module::new("fs");
module.action(
"read",
Box::new(|_interp, args| match args.first() {
Some(Value::Str(path)) => match std::fs::read_to_string(path) {
Ok(content) => Value::Str(content),
Err(_) => Value::Null,
},
_ => Value::Null,
}),
);
module.action(
"write",
Box::new(|_interp, args| match (args.first(), args.get(1)) {
(Some(Value::Str(path)), Some(Value::Str(content))) => {
Value::Bool(std::fs::write(path, content).is_ok())
}
_ => Value::Bool(false),
}),
);
module.action(
"append",
Box::new(|_interp, args| match (args.first(), args.get(1)) {
(Some(Value::Str(path)), Some(Value::Str(content))) => {
use std::fs::OpenOptions;
use std::io::Write;
let res = OpenOptions::new()
.append(true)
.create(true)
.open(path)
.and_then(|mut f| f.write_all(content.as_bytes()));
Value::Bool(res.is_ok())
}
_ => Value::Bool(false),
}),
);
module.action(
"exists",
Box::new(|_interp, args| match args.first() {
Some(Value::Str(path)) => Value::Bool(std::path::Path::new(path).exists()),
_ => Value::Bool(false),
}),
);
module.action(
"delete",
Box::new(|_interp, args| match args.first() {
Some(Value::Str(path)) => Value::Bool(std::fs::remove_file(path).is_ok()),
_ => Value::Bool(false),
}),
);
module
}
fn build_net_module() -> Module {
let mut module = Module::new("net");
module.action("ping", Box::new(|_interp, _args| Value::Bool(false)));
module
}
fn build_http_module() -> Module {
let mut module = Module::new("http");
module.action(
"get",
Box::new(|_interp, args| {
let url = match args.first() {
Some(Value::Str(s)) => s,
_ => return Value::Null,
};
match ureq::get(url).call() {
Ok(resp) => resp.into_string().map(Value::Str).unwrap_or(Value::Null),
Err(_) => Value::Null,
}
}),
);
module
}
fn build_time_module() -> Module {
let mut module = Module::new("time");
module.action(
"now_ms",
Box::new(|_interp, _args| {
use std::time::{SystemTime, UNIX_EPOCH};
let ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0);
Value::Int(ms)
}),
);
module.action(
"sleep_ms",
Box::new(|_interp, args| {
if let Some(Value::Int(ms)) = args.first() {
let ms = (*ms as u64).min(60_000);
std::thread::sleep(std::time::Duration::from_millis(ms));
}
Value::Null
}),
);
module
}
fn build_rand_module() -> Module {
let mut module = Module::new("rand");
module.action(
"rand_int",
Box::new(|_interp, args| {
let max = args
.first()
.and_then(|v| match v {
Value::Int(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
_ => None,
})
.unwrap_or(32768)
.max(1);
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let val = (seed.wrapping_mul(1103515245).wrapping_add(12345)) >> 16;
Value::Int((val % max as u64) as i64)
}),
);
module
}