Skip to main content

ling/
lib.rs

1// src/lib.rs - Public API entry point
2// Logo + favicon for the docs.rs page (the crate's public API docs).
3#![doc(
4    html_logo_url = "https://ling-lang.org/images/logo.svg",
5    html_favicon_url = "https://ling-lang.org/images/logo.svg"
6)]
7
8pub mod astviz;
9pub mod borrowck;
10pub mod codegen;
11#[cfg(not(target_arch = "wasm32"))]
12pub mod convert;
13pub mod core;
14pub mod diag;
15pub mod entry;
16pub mod gfx;
17pub mod lexer;
18pub mod lexicon;
19pub mod mir;
20pub mod parser;
21pub mod polyglot;
22pub mod runtime;
23pub mod semantic;
24pub mod utils;
25pub mod visualize;
26
27#[cfg(not(target_arch = "wasm32"))]
28pub use ling_audio;
29
30// Re-exports
31pub use core::{CompilerConfig, LingCompiler, OptimizationLevel};
32pub use lexicon::{CanonicalToken, Lexicon, LexiconRegistry};
33pub use polyglot::{normalize_source, ScriptDetector};
34
35// Version constant
36pub const VERSION: &str = env!("CARGO_PKG_VERSION");
37
38/// Run a Ling source string through the interpreter.
39/// Lexes → parses → executes the `start` binding.
40pub fn run(source: &str) -> Result<(), String> {
41    run_named(source, None, None)
42}
43
44/// Self-extract resources packed into the executable by `ling build --pack`.
45///
46/// Each `(relative_path, bytes)` is written under a per-app temp directory, then
47/// the process's current directory is switched there so the app's relative asset
48/// paths (e.g. `music/song.wav`) resolve against the extracted files. A no-op for
49/// an empty table.
50pub fn unpack_resources(app: &str, resources: &[(&str, &[u8])]) {
51    if resources.is_empty() {
52        return;
53    }
54    let base = std::env::temp_dir().join(format!("ling-pack-{app}"));
55    for (rel, bytes) in resources {
56        let dst = base.join(rel);
57        if let Some(parent) = dst.parent() {
58            let _ = std::fs::create_dir_all(parent);
59        }
60        let _ = std::fs::write(&dst, bytes);
61    }
62    let _ = std::env::set_current_dir(&base);
63}
64
65/// Run with an optional source directory for relative `use` imports.
66pub fn run_file(source: &str, source_dir: Option<std::path::PathBuf>) -> Result<(), String> {
67    run_named(source, source_dir, None)
68}
69
70/// Run with an optional source directory and the source file name (used to label
71/// diagnostics). Parse and runtime errors are returned as fully-rendered,
72/// colored, localized diagnostics (see [`diag`]).
73pub fn run_named(
74    source: &str,
75    source_dir: Option<std::path::PathBuf>,
76    file: Option<&str>,
77) -> Result<(), String> {
78    let lang = diag::OutputLang::from_env();
79    let program = parser::parse(source).map_err(|e| diag::render_parse(&e, source, file, lang))?;
80    let mut interp = runtime::Interpreter::new();
81    interp.source_dir = source_dir;
82    match interp.run_program(&program) {
83        Ok(()) => Ok(()),
84        Err(msg) => {
85            let trace = interp.take_error_trace();
86            Err(diag::render_runtime(&msg, source, file, &trace, lang))
87        },
88    }
89}
90
91/// Run concatenated `source` on the Cranelift JIT backend, resolving `use`
92/// imports against `source_dir`. This is the interpreter-free entry point for
93/// embedders (e.g. the game launcher). Cranelift-skipped oversized functions
94/// still execute through the primed fallback interpreter, so behaviour matches
95/// the tree-walker. A program with no entry, a parse error, or a pre-execution
96/// codegen failure falls back to [`run_named`]; a mid-run failure is returned as
97/// a rendered diagnostic without retrying (output may already be on screen).
98#[cfg(not(target_arch = "wasm32"))]
99pub fn run_jit(
100    source: &str,
101    source_dir: Option<std::path::PathBuf>,
102    file: Option<&str>,
103) -> Result<(), String> {
104    let lang = diag::OutputLang::from_env();
105    let has_entry = parser::parse(source)
106        .map(|p| entry::entry_name(&p.items).is_some())
107        .unwrap_or(true);
108    if !has_entry {
109        return run_named(source, source_dir, file);
110    }
111    let compiler = LingCompiler::new(CompilerConfig::default());
112    match compiler.compile_and_run_jit_source(source, source_dir.clone()) {
113        Ok(()) => Ok(()),
114        Err(core::LingError::Parse(m)) => Err(diag::render_parse(&m, source, file, lang)),
115        Err(core::LingError::Mir(m)) => Err(m),
116        Err(_) => run_named(source, source_dir, file),
117    }
118}
119
120/// Detect the primary human language used for keywords in a Ling source file.
121pub fn detect_language(source: &str) -> &'static str {
122    let languages: &[(&[&str], &str)] = &[
123        (
124            &[
125                "令", "灵符", "执", "函", "核", "若", "否则", "历", "于", "配", "归", "印", "格式",
126            ],
127            "Chinese (中文)",
128        ),
129        (
130            &[
131                "束縛",
132                "実行",
133                "もし",
134                "一方",
135                "ために",
136                "試す",
137                "待つ",
138                "帰る",
139            ],
140            "Japanese (日本語)",
141        ),
142        (
143            &["바인드", "만약", "동안", "출력", "시작"],
144            "Korean (한국어)",
145        ),
146        (
147            &[
148                "связать",
149                "сделать",
150                "если",
151                "иначе",
152                "пока",
153                "для",
154                "вернуть",
155                "вывести",
156            ],
157            "Russian (русский)",
158        ),
159        (
160            &[
161                "ผูก",
162                "ทำ",
163                "ถ้า",
164                "มิฉะนั้น",
165                "สำหรับ",
166                "คืน",
167                "พิมพ์",
168                "รูปแบบ",
169                "เริ่ม",
170            ],
171            "Thai (ภาษาไทย)",
172        ),
173        (
174            &["बाँधो", "करो", "अगर", "जबकि", "वापस", "सत्य"],
175            "Hindi (हिन्दी)",
176        ),
177        (
178            &["ربط", "افعل", "إذا", "وإلا", "بينما", "أعد"],
179            "Arabic (العربية)",
180        ),
181        (
182            &["enlazar", "hacer", "mientras", "retornar", "verdadero"],
183            "Spanish (Español)",
184        ),
185        (
186            &["lier", "faire", "sinon", "tantque", "retourner", "vrai"],
187            "French (Français)",
188        ),
189        (
190            &["binden", "machen", "wenn", "solange", "zurück", "wahr"],
191            "German (Deutsch)",
192        ),
193        (
194            &["ligar", "fazer", "enquanto", "retornar", "verdadeiro"],
195            "Portuguese (Português)",
196        ),
197    ];
198
199    let best = languages
200        .iter()
201        .map(|(keywords, lang)| {
202            let count = keywords.iter().filter(|&&k| source.contains(k)).count();
203            (count, *lang)
204        })
205        .max_by_key(|&(count, _)| count);
206
207    match best {
208        Some((count, lang)) if count > 0 => lang,
209        _ => "English",
210    }
211}