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/// Detect the primary human language used for keywords in a Ling source file.
92pub fn detect_language(source: &str) -> &'static str {
93    let languages: &[(&[&str], &str)] = &[
94        (
95            &[
96                "令", "灵符", "执", "函", "核", "若", "否则", "历", "于", "配", "归", "印", "格式",
97            ],
98            "Chinese (中文)",
99        ),
100        (
101            &[
102                "束縛",
103                "実行",
104                "もし",
105                "一方",
106                "ために",
107                "試す",
108                "待つ",
109                "帰る",
110            ],
111            "Japanese (日本語)",
112        ),
113        (
114            &["바인드", "만약", "동안", "출력", "시작"],
115            "Korean (한국어)",
116        ),
117        (
118            &[
119                "связать",
120                "сделать",
121                "если",
122                "иначе",
123                "пока",
124                "для",
125                "вернуть",
126                "вывести",
127            ],
128            "Russian (русский)",
129        ),
130        (
131            &[
132                "ผูก",
133                "ทำ",
134                "ถ้า",
135                "มิฉะนั้น",
136                "สำหรับ",
137                "คืน",
138                "พิมพ์",
139                "รูปแบบ",
140                "เริ่ม",
141            ],
142            "Thai (ภาษาไทย)",
143        ),
144        (
145            &["बाँधो", "करो", "अगर", "जबकि", "वापस", "सत्य"],
146            "Hindi (हिन्दी)",
147        ),
148        (
149            &["ربط", "افعل", "إذا", "وإلا", "بينما", "أعد"],
150            "Arabic (العربية)",
151        ),
152        (
153            &["enlazar", "hacer", "mientras", "retornar", "verdadero"],
154            "Spanish (Español)",
155        ),
156        (
157            &["lier", "faire", "sinon", "tantque", "retourner", "vrai"],
158            "French (Français)",
159        ),
160        (
161            &["binden", "machen", "wenn", "solange", "zurück", "wahr"],
162            "German (Deutsch)",
163        ),
164        (
165            &["ligar", "fazer", "enquanto", "retornar", "verdadeiro"],
166            "Portuguese (Português)",
167        ),
168    ];
169
170    let best = languages
171        .iter()
172        .map(|(keywords, lang)| {
173            let count = keywords.iter().filter(|&&k| source.contains(k)).count();
174            (count, *lang)
175        })
176        .max_by_key(|&(count, _)| count);
177
178    match best {
179        Some((count, lang)) if count > 0 => lang,
180        _ => "English",
181    }
182}