use crate::bytecode;
use crate::cli::CliRunner;
use crate::interpreter::{ErrorHandler, Instr, Interpreter, compile_block};
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::types::*;
use std::collections::HashMap;
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,
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>,
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>,
}
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,
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-beta.1".to_string(),
license: "MIT".to_string(),
doc_comment: "///".to_string(),
package_manager: None,
stdlib_modules: vec![],
crate_modules: vec![],
ffi_libs: 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,
}
}
pub fn name(&mut self, name: &str) {
self.name = name.to_string();
self.name_set = true;
}
pub fn extension(&mut self, ext: &str) {
self.extension = ext.to_string();
self.extension_set = true;
}
pub fn version(&mut self, v: &str) {
self.version_str = v.to_string();
}
pub fn license(&mut self, l: &str) {
self.license = l.to_string();
}
pub fn doc_comment(&mut self, prefix: &str) {
self.doc_comment = prefix.to_string();
}
pub fn package_manager(&mut self, name: &str) {
self.package_manager = Some(name.to_string());
}
pub fn config_toml(&mut self, path: &str) {
self.config_source = ConfigSource::Toml(path.to_string());
}
pub fn config_inline(&mut self) {
self.config_source = ConfigSource::Inline;
}
pub fn config_none(&mut self) {
self.config_source = ConfigSource::None;
}
pub fn build_dir(&mut self, dir: &str) {
self.build_dir = Some(dir.to_string());
self.build_dir_set = true;
}
pub fn libs_mode(&mut self, mode: LibsMode) {
self.libs_mode = mode;
self.libs_mode_set = true;
}
pub fn libs_dir(&mut self, dir: &str) {
self.libs_mode = LibsMode::Local(dir.to_string());
self.libs_mode_set = true;
}
pub fn token(&mut self, name: &str) {
if !self.tokens.contains(&name.to_string()) {
self.tokens.push(name.to_string());
}
}
pub fn action(&mut self, keyword: &str, f: Action) {
self.token(keyword);
self.actions.insert(keyword.to_string(), f);
}
pub fn args(&mut self, keyword: &str, format: &str) {
self.args_formats
.insert(keyword.to_string(), format.to_string());
}
pub fn var(&mut self, keyword: &str) {
self.immut_kw(keyword);
}
pub fn mut_kw(&mut self, keyword: &str) {
self.token(keyword);
self.mut_keyword = Some(keyword.to_string());
}
pub fn immut_kw(&mut self, keyword: &str) {
self.token(keyword);
self.immut_keyword = Some(keyword.to_string());
}
pub fn typing(&mut self, t: Typing) {
self.typing = t;
self.typing_set = true;
}
pub fn implicit_mutability(&mut self, mode: ImplicitMutability) {
self.implicit_mutability = mode;
self.implicit_mutability_set = true;
}
pub fn ops(&mut self, ops: &[&str]) {
self.ops = ops.iter().map(|s| s.to_string()).collect();
}
pub fn compare(&mut self, ops: &[&str]) {
self.compare_ops = ops.iter().map(|s| s.to_string()).collect();
}
pub fn logical(&mut self, ops: &[&str]) {
self.logical_ops = ops.iter().map(|s| s.to_string()).collect();
}
pub fn blocks(&mut self, b: Blocks) {
self.blocks = b;
}
pub fn semicolon(&mut self, s: Semicolon) {
self.semicolon = s;
}
pub fn strings(&mut self, s: Strings) {
self.strings = s;
}
pub fn interpolation(&mut self, syntax: &str) {
self.interpolation = Some(syntax.to_string());
}
pub fn range(&mut self, op: &str) {
self.range_op = Some(op.to_string());
}
pub fn null(&mut self, keyword: &str) {
self.null_keyword = Some(keyword.to_string());
self.token(keyword);
}
pub fn array(&mut self, syntax: &str) {
self.array_syntax = Some(syntax.to_string());
}
pub fn comment(&mut self, line: &str, block: &str) {
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());
}
}
pub fn assign(&mut self, op: &str) {
self.assign_op = Some(op.to_string());
}
pub fn condition(&mut self, keyword: &str, _block: &str) {
self.token(keyword);
self.token("else");
self.if_keyword = Some(keyword.to_string());
}
pub fn loop_(&mut self, keyword: &str, _block: &str) {
self.token(keyword);
self.loop_keyword = Some(keyword.to_string());
}
pub fn for_in(&mut self, for_kw: &str, in_kw: &str) {
self.token(for_kw);
self.token(in_kw);
self.for_keyword = Some(for_kw.to_string());
self.in_keyword = Some(in_kw.to_string());
}
pub fn func(&mut self, keyword: &str, _block: &str) {
self.token(keyword);
self.fn_keyword = Some(keyword.to_string());
}
pub fn return_(&mut self, keyword: &str) {
self.token(keyword);
self.return_keyword = Some(keyword.to_string());
}
pub fn break_(&mut self, keyword: &str) {
self.token(keyword);
self.break_keyword = Some(keyword.to_string());
}
pub fn while_(&mut self, keyword: &str) {
self.token(keyword);
self.while_keyword = Some(keyword.to_string());
}
pub fn switch_(&mut self, keyword: &str, case_kw: &str, default_kw: &str) {
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());
}
pub fn error(&mut self, try_kw: &str, catch_kw: &str, _block: &str) {
self.token(try_kw);
self.token(catch_kw);
self.try_keyword = Some(try_kw.to_string());
self.catch_keyword = Some(catch_kw.to_string());
}
pub fn debug(&mut self, d: Debug) {
self.debug_mode = d;
self.debug_enabled = true;
}
pub fn on_error(&mut self, handler: ErrorHandler) {
self.error_handler = Some(handler);
}
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) {
self.token(keyword);
self.import_keyword = Some(keyword.to_string());
}
pub fn use_crate(&mut self, name: &str) {
if !self.crate_modules.contains(&name.to_string()) {
self.crate_modules.push(name.to_string());
}
self.crates_set = true;
}
pub fn ffi(&mut self, lib_path: &str) {
if !self.ffi_libs.contains(&lib_path.to_string()) {
self.ffi_libs.push(lib_path.to_string());
}
self.ffi_set = true;
}
pub fn stdlib(&mut self, modules: &[&str]) {
self.stdlib_modules = modules.iter().map(|s| s.to_string()).collect();
self.stdlib_set = true;
}
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 |args| {
args.first()
.cloned()
.map(&func)
.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 mode(&mut self, m: Mode) {
self.mode = m;
self.mode_set = true;
}
pub fn dist(&mut self, d: Dist) {
self.dist = d;
}
pub fn compat(&mut self, c: Compat) {
self.compat = c;
}
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 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)?;
let mut interp = self.prepare_interpreter();
let signal = interp
.run_vm_block(&bc)
.map_err(|e| match self.debug_mode {
Debug::Full => format!("{}\nVars: {:?}", e, interp.snapshot_vars()),
Debug::StackTrace => e,
})?;
self.restore_from_interpreter(interp);
match signal {
crate::interpreter::Signal::Return(v) => Ok(v),
crate::interpreter::Signal::Break => Ok(Value::Null),
}
}
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(
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) },
&|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 all_keywords = self.collect_keywords();
let mut lexer = Lexer::new(
source.as_str(),
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| format!("Lexer error: {}", self.with_caret(&source, &e)))?;
let mut parser = Parser::new(tokens)
.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());
let ast = parser
.parse()
.map_err(|e| format!("Parse error: {}", self.with_caret(&source, &e)))?;
let mut interp = self.prepare_interpreter();
let signal = match self.mode {
Mode::Interpreter => interp.eval_block(&ast),
Mode::Compiler => {
let bc = 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),
},
}
.map_err(|e| match self.debug_mode {
Debug::Full => format!("{}\nVars: {:?}", e, interp.snapshot_vars()),
Debug::StackTrace => e,
})?;
self.restore_from_interpreter(interp);
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,
]
.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 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 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)
.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());
let ast = parser.parse().map_err(|e| {
if with_caret {
format!("Parse error: {}", self.with_caret(source, &e))
} else {
format!("Parse error: {}", e)
}
})?;
compile_block(&ast)
}
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));
}
}
Ok((source.to_string(), 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(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(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 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.debug = self.debug_enabled;
for module in build_all_modules(&self.stdlib_modules, &self.crate_modules) {
interp.register_module(module);
}
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, interp: Interpreter) {
self.error_handler = Some(interp.error_handler);
self.actions = interp.actions;
}
}
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]) -> Vec<Module> {
let mut modules = Vec::new();
for name in stdlib {
if let Some(module) = build_stdlib_module(name) {
modules.push(module);
}
}
for name in crates {
if let Some(module) = build_crate_module(name) {
modules.push(module);
}
}
modules
}
fn build_stdlib_module(name: &str) -> Option<Module> {
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()),
_ => 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(|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(|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(|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(|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(|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(|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(
"input",
Box::new(|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_file",
Box::new(|args| match args.first() {
Some(Value::Str(path)) => match std::fs::read_to_string(path) {
Ok(content) => Value::Str(content),
Err(e) => Value::Str(format!("Error: {}", e)),
},
_ => Value::Null,
}),
);
module.action(
"write_file",
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Str(path)), Some(Value::Str(content))) => {
match std::fs::write(path, content) {
Ok(()) => Value::Bool(true),
Err(_) => Value::Bool(false),
}
}
_ => Value::Bool(false),
}),
);
module.action(
"file_exists",
Box::new(|args| match args.first() {
Some(Value::Str(path)) => Value::Bool(std::path::Path::new(path).exists()),
_ => Value::Bool(false),
}),
);
module.action(
"delete_file",
Box::new(|args| match args.first() {
Some(Value::Str(path)) => match std::fs::remove_file(path) {
Ok(()) => Value::Bool(true),
Err(_) => Value::Bool(false),
},
_ => Value::Bool(false),
}),
);
module
}
fn build_net_module() -> Module {
let mut module = Module::new("net");
module.action("ping", Box::new(|_args| Value::Bool(false)));
module
}
fn build_time_module() -> Module {
let mut module = Module::new("time");
module.action(
"now_ms",
Box::new(|_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(|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(|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
}