Skip to main content

ling/
lib.rs

1// src/lib.rs - Public API entry point
2pub mod core;
3pub mod lexer;
4pub mod parser;
5pub mod semantic;
6pub mod borrowck;
7pub mod mir;
8pub mod codegen;
9pub mod lexicon;
10pub mod polyglot;
11pub mod gfx;
12pub mod runtime;
13pub mod diag;
14pub mod utils;
15pub mod visualize;
16pub mod astviz;
17#[cfg(not(target_arch = "wasm32"))]
18pub mod convert;
19
20#[cfg(not(target_arch = "wasm32"))]
21pub use ling_audio;
22
23// Re-exports
24pub use core::{LingCompiler, CompilerConfig, OptimizationLevel};
25pub use lexicon::{CanonicalToken, Lexicon, LexiconRegistry};
26pub use polyglot::{normalize_source, ScriptDetector};
27
28// Version constant
29pub const VERSION: &str = env!("CARGO_PKG_VERSION");
30
31/// Run a Ling source string through the interpreter.
32/// Lexes → parses → executes the `start` binding.
33pub fn run(source: &str) -> Result<(), String> {
34    run_named(source, None, None)
35}
36
37/// Self-extract resources packed into the executable by `ling build --pack`.
38///
39/// Each `(relative_path, bytes)` is written under a per-app temp directory, then
40/// the process's current directory is switched there so the app's relative asset
41/// paths (e.g. `music/song.wav`) resolve against the extracted files. A no-op for
42/// an empty table.
43pub fn unpack_resources(app: &str, resources: &[(&str, &[u8])]) {
44    if resources.is_empty() {
45        return;
46    }
47    let base = std::env::temp_dir().join(format!("ling-pack-{app}"));
48    for (rel, bytes) in resources {
49        let dst = base.join(rel);
50        if let Some(parent) = dst.parent() {
51            let _ = std::fs::create_dir_all(parent);
52        }
53        let _ = std::fs::write(&dst, bytes);
54    }
55    let _ = std::env::set_current_dir(&base);
56}
57
58/// Run with an optional source directory for relative `use` imports.
59pub fn run_file(source: &str, source_dir: Option<std::path::PathBuf>) -> Result<(), String> {
60    run_named(source, source_dir, None)
61}
62
63/// Run with an optional source directory and the source file name (used to label
64/// diagnostics). Parse and runtime errors are returned as fully-rendered,
65/// colored, localized diagnostics (see [`diag`]).
66pub fn run_named(
67    source: &str,
68    source_dir: Option<std::path::PathBuf>,
69    file: Option<&str>,
70) -> Result<(), String> {
71    let lang = diag::OutputLang::from_env();
72    let program = parser::parse(source)
73        .map_err(|e| diag::render_parse(&e, source, file, lang))?;
74    let mut interp = runtime::Interpreter::new();
75    interp.source_dir = source_dir;
76    match interp.run_program(&program) {
77        Ok(()) => Ok(()),
78        Err(msg) => {
79            let trace = interp.take_error_trace();
80            Err(diag::render_runtime(&msg, source, file, &trace, lang))
81        }
82    }
83}
84
85/// Detect the primary human language used for keywords in a Ling source file.
86pub fn detect_language(source: &str) -> &'static str {
87    let languages: &[(&[&str], &str)] = &[
88        (&["令", "灵符", "执", "函", "核", "若", "否则", "历", "于", "配", "归", "印", "格式"], "Chinese (中文)"),
89        (&["束縛", "実行", "もし", "一方", "ために", "試す", "待つ", "帰る"], "Japanese (日本語)"),
90        (&["바인드", "만약", "동안", "출력", "시작"], "Korean (한국어)"),
91        (&["связать", "сделать", "если", "иначе", "пока", "для", "вернуть", "вывести"], "Russian (русский)"),
92        (&["ผูก", "ทำ", "ถ้า", "มิฉะนั้น", "สำหรับ", "คืน", "พิมพ์", "รูปแบบ", "เริ่ม"], "Thai (ภาษาไทย)"),
93        (&["बाँधो", "करो", "अगर", "जबकि", "वापस", "सत्य"], "Hindi (हिन्दी)"),
94        (&["ربط", "افعل", "إذا", "وإلا", "بينما", "أعد"], "Arabic (العربية)"),
95        (&["enlazar", "hacer", "mientras", "retornar", "verdadero"], "Spanish (Español)"),
96        (&["lier", "faire", "sinon", "tantque", "retourner", "vrai"], "French (Français)"),
97        (&["binden", "machen", "wenn", "solange", "zurück", "wahr"], "German (Deutsch)"),
98        (&["ligar", "fazer", "enquanto", "retornar", "verdadeiro"], "Portuguese (Português)"),
99    ];
100
101    let best = languages.iter()
102        .map(|(keywords, lang)| {
103            let count = keywords.iter().filter(|&&k| source.contains(k)).count();
104            (count, *lang)
105        })
106        .max_by_key(|&(count, _)| count);
107
108    match best {
109        Some((count, lang)) if count > 0 => lang,
110        _ => "English",
111    }
112}