Something went wrong. Try again.
A human-friendly DSL for ATProto Lexicons forked from stavola.xyz/mlf
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376use serde::{Deserialize, Serialize};use wasm_bindgen::prelude::*;
#[derive(Serialize, Deserialize)]pub struct ParseResult { pub success: bool, pub error: Option<String>,}
#[derive(Serialize, Deserialize)]pub struct CheckResult { pub success: bool, pub errors: Vec<String>,}
#[derive(Serialize, Deserialize)]pub struct GenerateResult { pub success: bool, pub lexicon: Option<String>, pub error: Option<String>,}
#[derive(Serialize, Deserialize)]pub struct ValidateResult { pub success: bool, pub errors: Vec<String>,}
/// Parse MLF source code and return whether it's valid#[wasm_bindgen]pub fn parse(source: &str) -> JsValue { let result = match mlf_lang::parse_lexicon(source) { Ok(_) => ParseResult { success: true, error: None, }, Err(e) => ParseResult { success: false, error: Some(format!("{:?}", e)), }, };
serde_wasm_bindgen::to_value(&result).unwrap()}
/// Check MLF source for errors (parse + validate)#[wasm_bindgen]pub fn check(source: &str) -> JsValue { let mut errors = Vec::new();
// Parse the lexicon let lexicon = match mlf_lang::parse_lexicon(source) { Ok(lex) => lex, Err(e) => { errors.push(format!("Parse error: {:?}", e)); let result = CheckResult { success: false, errors, }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Create workspace and validate let mut workspace = match mlf_lang::Workspace::with_std() { Ok(ws) => ws, Err(e) => { errors.push(format!("Failed to load standard library: {:?}", e)); let result = CheckResult { success: false, errors, }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
if let Err(e) = workspace.add_module("main".to_string(), lexicon) { errors.push(format!("Validation error: {:?}", e)); }
if let Err(e) = workspace.resolve() { errors.push(format!("Resolution error: {:?}", e)); }
let result = CheckResult { success: errors.is_empty(), errors, };
serde_wasm_bindgen::to_value(&result).unwrap()}
/// Generate JSON lexicon from MLF source#[wasm_bindgen]pub fn generate_lexicon(source: &str, namespace: &str) -> JsValue { // Parse the lexicon let lexicon = match mlf_lang::parse_lexicon(source) { Ok(lex) => lex, Err(e) => { let result = GenerateResult { success: false, lexicon: None, error: Some(format!("Parse error: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Create workspace with standard library for type resolution let mut workspace = match mlf_lang::Workspace::with_std() { Ok(ws) => ws, Err(e) => { let result = GenerateResult { success: false, lexicon: None, error: Some(format!("Failed to load standard library: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Add the module to workspace for resolution if let Err(e) = workspace.add_module(namespace.to_string(), lexicon.clone()) { let result = GenerateResult { success: false, lexicon: None, error: Some(format!("Failed to add module: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); }
// Resolve types (expands inline types from standard library) if let Err(e) = workspace.resolve() { let result = GenerateResult { success: false, lexicon: None, error: Some(format!("Type resolution error: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); }
// Generate JSON lexicon. Codegen warnings are dropped on the floor // in the wasm surface today — the playground doesn't have a UI for // them yet; when it does, they flow through `output.warnings`. let output = mlf_codegen::generate_lexicon(namespace, &lexicon, &workspace);
match serde_json::to_string_pretty(&output.json) { Ok(json_str) => { let result = GenerateResult { success: true, lexicon: Some(json_str), error: None, }; serde_wasm_bindgen::to_value(&result).unwrap() } Err(e) => { let result = GenerateResult { success: false, lexicon: None, error: Some(format!("JSON serialization error: {}", e)), }; serde_wasm_bindgen::to_value(&result).unwrap() } }}
/// Validate a JSON record against an MLF lexicon#[wasm_bindgen]pub fn validate_record(lexicon_source: &str, record_json: &str) -> JsValue { let mut errors = Vec::new();
// Parse the lexicon let lexicon = match mlf_lang::parse_lexicon(lexicon_source) { Ok(lex) => lex, Err(e) => { errors.push(format!("Lexicon parse error: {:?}", e)); let result = ValidateResult { success: false, errors, }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Parse the JSON record let record: serde_json::Value = match serde_json::from_str(record_json) { Ok(rec) => rec, Err(e) => { errors.push(format!("JSON parse error: {}", e)); let result = ValidateResult { success: false, errors, }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Validate the record let validator = mlf_validation::RecordValidator::new(&lexicon); match validator.validate_record(&record) { Ok(()) => { let result = ValidateResult { success: true, errors: vec![], }; serde_wasm_bindgen::to_value(&result).unwrap() } Err(validation_errors) => { for err in validation_errors { errors.push(err.to_string()); } let result = ValidateResult { success: false, errors, }; serde_wasm_bindgen::to_value(&result).unwrap() } }}
#[derive(Serialize, Deserialize)]pub struct GeneratorInfo { pub name: String, pub description: String, pub file_extension: String,}
#[derive(Serialize, Deserialize)]pub struct ListGeneratorsResult { pub generators: Vec<GeneratorInfo>,}
#[derive(Serialize, Deserialize)]pub struct GenerateCodeResult { pub success: bool, pub code: Option<String>, pub error: Option<String>,}
/// List all available code generators#[wasm_bindgen]pub fn list_generators() -> JsValue { let generators = mlf_codegen::plugin::generators();
// Debug: log how many generators we found #[cfg(target_arch = "wasm32")] web_sys::console::log_1(&format!("Found {} generators", generators.len()).into());
let generator_infos: Vec<GeneratorInfo> = generators .iter() .map(|generator| { #[cfg(target_arch = "wasm32")] web_sys::console::log_1(&format!(" - {}", generator.name()).into());
GeneratorInfo { name: generator.name().to_string(), description: generator.description().to_string(), file_extension: generator.file_extension().to_string(), } }) .collect();
let result = ListGeneratorsResult { generators: generator_infos, };
serde_wasm_bindgen::to_value(&result).unwrap()}
/// Generate code using a specific generator#[wasm_bindgen]pub fn generate_code(source: &str, namespace: &str, generator_name: &str) -> JsValue { // Load standard library let mut workspace = match mlf_lang::Workspace::with_std() { Ok(ws) => ws, Err(e) => { let result = GenerateCodeResult { success: false, code: None, error: Some(format!("Failed to load standard library: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Parse the source let lexicon = match mlf_lang::parse_lexicon(source) { Ok(lex) => lex, Err(e) => { let result = GenerateCodeResult { success: false, code: None, error: Some(format!("Parse error: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Add the module to workspace for resolution if let Err(e) = workspace.add_module(namespace.to_string(), lexicon.clone()) { let result = GenerateCodeResult { success: false, code: None, error: Some(format!("Failed to add module: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); }
// Resolve types if let Err(e) = workspace.resolve() { let result = GenerateCodeResult { success: false, code: None, error: Some(format!("Type resolution error: {:?}", e)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); }
// Find the generator let generator = match mlf_codegen::plugin::find_generator(generator_name) { Some(g) => g, None => { let result = GenerateCodeResult { success: false, code: None, error: Some(format!("Generator '{}' not found", generator_name)), }; return serde_wasm_bindgen::to_value(&result).unwrap(); } };
// Generate code let ctx = mlf_codegen::GeneratorContext { namespace, lexicon: &lexicon, workspace: &workspace, };
match generator.generate(&ctx) { Ok(code) => { let result = GenerateCodeResult { success: true, code: Some(code), error: None, }; serde_wasm_bindgen::to_value(&result).unwrap() } Err(e) => { let result = GenerateCodeResult { success: false, code: None, error: Some(e), }; serde_wasm_bindgen::to_value(&result).unwrap() } }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_parse_valid() { let source = r#" record post { text: string, }; "#;
let result = parse(source); // Basic test that it doesn't panic assert!(!result.is_undefined()); }}