A human-friendly DSL for ATProto Lexicons
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Standard library

+395 -18
+20
Cargo.lock
··· 221 221 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 222 222 223 223 [[package]] 224 + name = "include_dir" 225 + version = "0.7.4" 226 + source = "registry+https://github.com/rust-lang/crates.io-index" 227 + checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" 228 + dependencies = [ 229 + "include_dir_macros", 230 + ] 231 + 232 + [[package]] 233 + name = "include_dir_macros" 234 + version = "0.7.4" 235 + source = "registry+https://github.com/rust-lang/crates.io-index" 236 + checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" 237 + dependencies = [ 238 + "proc-macro2", 239 + "quote", 240 + ] 241 + 242 + [[package]] 224 243 name = "indexmap" 225 244 version = "2.11.4" 226 245 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 367 386 name = "mlf-lang" 368 387 version = "0.1.0" 369 388 dependencies = [ 389 + "include_dir", 370 390 "nom", 371 391 ] 372 392
+2 -2
mlf-cli/src/check.rs
··· 74 74 } 75 75 } 76 76 77 - let mut workspace = mlf_lang::Workspace::with_prelude().map_err(|e| { 77 + let mut workspace = mlf_lang::Workspace::with_std().map_err(|e| { 78 78 CheckError::ValidationErrors { 79 - help: Some(format!("Failed to load prelude: {:?}", e)), 79 + help: Some(format!("Failed to load standard library: {:?}", e)), 80 80 } 81 81 })?; 82 82
+1
mlf-lang/Cargo.toml
··· 6 6 7 7 [dependencies] 8 8 nom = { version = "8", default-features = false, features = ["alloc"] } 9 + include_dir = "0.7" 9 10 10 11 [features] 11 12 default = ["std"]
+6 -1
mlf-lang/src/lib.rs
··· 16 16 pub use validate::validate_lexicon; 17 17 pub use workspace::Workspace; 18 18 19 - pub const PRELUDE: &str = include_str!("../../resources/prelude.mlf"); 19 + // Standard library directory 20 + use include_dir::{include_dir, Dir}; 21 + 22 + pub static STD_DIR: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/../std"); 23 + 24 + pub const PRELUDE: &str = include_str!("../../std/prelude.mlf");
+103 -2
mlf-lang/src/workspace.rs
··· 61 61 Ok(ws) 62 62 } 63 63 64 + pub fn with_std() -> Result<Self, ValidationErrors> { 65 + let mut ws = Self::new(); 66 + 67 + // Add prelude 68 + let prelude_lexicon = crate::parser::parse_lexicon(crate::PRELUDE) 69 + .map_err(|e| { 70 + let mut errors = ValidationErrors::new(); 71 + errors.push(ValidationError::InvalidConstraint { 72 + message: alloc::format!("Failed to parse prelude: {:?}", e), 73 + span: crate::span::Span::new(0, 0), 74 + }); 75 + errors 76 + })?; 77 + ws.add_module("prelude".into(), prelude_lexicon)?; 78 + 79 + // Recursively load all .mlf files from the std directory 80 + fn load_std_modules( 81 + ws: &mut Workspace, 82 + dir: &include_dir::Dir, 83 + ) -> Result<(), ValidationErrors> { 84 + for file in dir.files() { 85 + if let Some(path_str) = file.path().to_str() { 86 + if path_str.ends_with(".mlf") && !path_str.ends_with("prelude.mlf") { 87 + // Convert file path to namespace 88 + // e.g., "com/atproto/repo/defs.mlf" -> "com.atproto.repo" 89 + // e.g., "com/atproto/lexicon/schema.mlf" -> "com.atproto.lexicon" 90 + let mut namespace = path_str 91 + .strip_suffix(".mlf") 92 + .unwrap_or(path_str) 93 + .replace('/', "."); 94 + 95 + // Strip the filename part - files are just containers, not part of the NSID 96 + // Get the directory path as the namespace 97 + if let Some(last_dot) = namespace.rfind('.') { 98 + namespace = namespace[..last_dot].to_string(); 99 + } 100 + 101 + if let Some(contents) = file.contents_utf8() { 102 + let lexicon = crate::parser::parse_lexicon(contents) 103 + .map_err(|e| { 104 + let mut errors = ValidationErrors::new(); 105 + errors.push(ValidationError::InvalidConstraint { 106 + message: alloc::format!("Failed to parse {} (file: {}): {:?}", namespace, path_str, e), 107 + span: crate::span::Span::new(0, 0), 108 + }); 109 + errors 110 + })?; 111 + 112 + // If the module already exists, merge the items 113 + if ws.modules.contains_key(&namespace) { 114 + let module = ws.modules.get_mut(&namespace).unwrap(); 115 + module.lexicon.items.extend(lexicon.items); 116 + // Rebuild symbol table 117 + module.symbols = Workspace::build_symbol_table(&namespace, &module.lexicon)?; 118 + } else { 119 + ws.add_module(namespace, lexicon)?; 120 + } 121 + } 122 + } 123 + } 124 + } 125 + 126 + for subdir in dir.dirs() { 127 + load_std_modules(ws, subdir)?; 128 + } 129 + 130 + Ok(()) 131 + } 132 + 133 + load_std_modules(&mut ws, &crate::STD_DIR)?; 134 + 135 + Ok(ws) 136 + } 137 + 64 138 pub fn add_module(&mut self, namespace: String, lexicon: Lexicon) -> Result<(), ValidationErrors> { 65 139 let symbols = Self::build_symbol_table(&namespace, &lexicon)?; 66 140 ··· 247 321 for constraint in constraints { 248 322 match constraint { 249 323 Constraint::MinLength { span, .. } 250 - | Constraint::MaxLength { span, .. } 251 - | Constraint::MinGraphemes { span, .. } 324 + | Constraint::MaxLength { span, .. } => { 325 + // MinLength/MaxLength can be applied to strings or arrays 326 + let is_string = matches!(base_kind, Some(PrimitiveType::String)); 327 + let is_array = matches!(base, Type::Array { .. }); 328 + if !is_string && !is_array { 329 + errors.push(ValidationError::InvalidConstraint { 330 + message: alloc::format!("Length constraint can only be applied to string or array types"), 331 + span: *span, 332 + }); 333 + } 334 + } 335 + Constraint::MinGraphemes { span, .. } 252 336 | Constraint::MaxGraphemes { span, .. } 253 337 | Constraint::Format { span, .. } 254 338 | Constraint::Enum { span, .. } => { ··· 1494 1578 1495 1579 let result = ws.resolve(); 1496 1580 assert!(result.is_err()); 1581 + } 1582 + 1583 + #[test] 1584 + fn test_with_std() { 1585 + let ws = Workspace::with_std().unwrap(); 1586 + 1587 + // Check that prelude is loaded 1588 + assert!(ws.modules.contains_key("prelude")); 1589 + 1590 + // Print all loaded modules for debugging 1591 + eprintln!("Loaded modules:"); 1592 + for (name, _) in &ws.modules { 1593 + eprintln!(" - {}", name); 1594 + } 1595 + 1596 + // Check that std modules are loaded 1597 + assert!(ws.modules.len() > 1, "Should have more than just prelude"); 1497 1598 } 1498 1599 }
resources/prelude.mlf std/prelude.mlf
+36
std/com/atproto/admin/defs.mlf
··· 1 + // com.atproto.admin.defs 2 + 3 + def type statusAttr = { 4 + applied: boolean, 5 + ref?: string, 6 + }; 7 + 8 + def type accountView = { 9 + did: Did, 10 + handle: Handle, 11 + email?: string, 12 + relatedRecords?: [unknown], 13 + indexedAt: Datetime, 14 + invitedBy?: com.atproto.server.inviteCode, 15 + invites?: [com.atproto.server.inviteCode], 16 + invitesDisabled?: boolean, 17 + emailConfirmedAt?: Datetime, 18 + inviteNote?: string, 19 + deactivatedAt?: Datetime, 20 + threatSignatures?: [threatSignature], 21 + }; 22 + 23 + def type repoRef = { 24 + did: Did, 25 + }; 26 + 27 + def type repoBlobRef = { 28 + did: Did, 29 + cid: Cid, 30 + recordUri?: AtUri, 31 + }; 32 + 33 + def type threatSignature = { 34 + property: string, 35 + value: string, 36 + };
+7
std/com/atproto/identity/defs.mlf
··· 1 + // com.atproto.identity.defs 2 + 3 + def type identityInfo = { 4 + did: Did, 5 + handle: Handle, 6 + didDoc: unknown, 7 + };
+52
std/com/atproto/label/defs.mlf
··· 1 + // com.atproto.label.defs 2 + 3 + def type label = { 4 + ver?: integer, 5 + src: Did, 6 + uri: AtUri, 7 + cid?: Cid, 8 + val: string constrained { maxLength: 128 }, 9 + neg?: boolean, 10 + cts: Datetime, 11 + exp?: Datetime, 12 + sig?: bytes, 13 + }; 14 + 15 + def type selfLabels = { 16 + values: [selfLabel] constrained { maxLength: 10 }, 17 + }; 18 + 19 + def type selfLabel = { 20 + val: string constrained { maxLength: 128 }, 21 + }; 22 + 23 + def type labelValueDefinition = { 24 + identifier: string constrained { maxLength: 100, maxGraphemes: 100 }, 25 + severity: string constrained { knownValues: ["inform", "alert", "none"] }, 26 + blurs: string constrained { knownValues: ["content", "media", "none"] }, 27 + defaultSetting?: string constrained { knownValues: ["ignore", "warn", "hide"], default: "warn" }, 28 + adultOnly?: boolean, 29 + locales: [labelValueDefinitionStrings], 30 + }; 31 + 32 + def type labelValueDefinitionStrings = { 33 + lang: Language, 34 + name: string constrained { maxGraphemes: 64, maxLength: 640 }, 35 + description: string constrained { maxGraphemes: 10000, maxLength: 100000 }, 36 + }; 37 + 38 + def type labelValue = string constrained { 39 + knownValues: [ 40 + "!hide", 41 + "!no-promote", 42 + "!warn", 43 + "!no-unauthenticated", 44 + "dmca-violation", 45 + "doxxing", 46 + "porn", 47 + "sexual", 48 + "nudity", 49 + "nsfl", 50 + "gore", 51 + ], 52 + };
+5
std/com/atproto/lexicon/schema.mlf
··· 1 + // com.atproto.lexicon.schema 2 + 3 + record schema { 4 + lexicon: integer, 5 + }
+68
std/com/atproto/moderation/defs.mlf
··· 1 + // com.atproto.moderation.defs 2 + 3 + def type reasonType = string constrained { 4 + knownValues: [ 5 + "com.atproto.moderation.defs#reasonSpam", 6 + "com.atproto.moderation.defs#reasonViolation", 7 + "com.atproto.moderation.defs#reasonMisleading", 8 + "com.atproto.moderation.defs#reasonSexual", 9 + "com.atproto.moderation.defs#reasonRude", 10 + "com.atproto.moderation.defs#reasonOther", 11 + "com.atproto.moderation.defs#reasonAppeal", 12 + "tools.ozone.report.defs#reasonAppeal", 13 + "tools.ozone.report.defs#reasonViolenceAnimalWelfare", 14 + "tools.ozone.report.defs#reasonViolenceThreats", 15 + "tools.ozone.report.defs#reasonViolenceGraphicContent", 16 + "tools.ozone.report.defs#reasonViolenceSelfHarm", 17 + "tools.ozone.report.defs#reasonViolenceGlorification", 18 + "tools.ozone.report.defs#reasonViolenceExtremistContent", 19 + "tools.ozone.report.defs#reasonViolenceTrafficking", 20 + "tools.ozone.report.defs#reasonViolenceOther", 21 + "tools.ozone.report.defs#reasonSexualAbuseContent", 22 + "tools.ozone.report.defs#reasonSexualNCII", 23 + "tools.ozone.report.defs#reasonSexualSextortion", 24 + "tools.ozone.report.defs#reasonSexualDeepfake", 25 + "tools.ozone.report.defs#reasonSexualAnimal", 26 + "tools.ozone.report.defs#reasonSexualUnlabeled", 27 + "tools.ozone.report.defs#reasonSexualOther", 28 + "tools.ozone.report.defs#reasonChildSafetyCSAM", 29 + "tools.ozone.report.defs#reasonChildSafetyGroom", 30 + "tools.ozone.report.defs#reasonChildSafetyMinorPrivacy", 31 + "tools.ozone.report.defs#reasonChildSafetyEndangerment", 32 + "tools.ozone.report.defs#reasonChildSafetyHarassment", 33 + "tools.ozone.report.defs#reasonChildSafetyPromotion", 34 + "tools.ozone.report.defs#reasonChildSafetyOther", 35 + "tools.ozone.report.defs#reasonHarassmentTroll", 36 + "tools.ozone.report.defs#reasonHarassmentTargeted", 37 + "tools.ozone.report.defs#reasonHarassmentHateSpeech", 38 + "tools.ozone.report.defs#reasonHarassmentDoxxing", 39 + "tools.ozone.report.defs#reasonHarassmentOther", 40 + "tools.ozone.report.defs#reasonMisleadingBot", 41 + "tools.ozone.report.defs#reasonMisleadingImpersonation", 42 + "tools.ozone.report.defs#reasonMisleadingSpam", 43 + "tools.ozone.report.defs#reasonMisleadingScam", 44 + "tools.ozone.report.defs#reasonMisleadingSyntheticContent", 45 + "tools.ozone.report.defs#reasonMisleadingMisinformation", 46 + "tools.ozone.report.defs#reasonMisleadingOther", 47 + "tools.ozone.report.defs#reasonRuleSiteSecurity", 48 + "tools.ozone.report.defs#reasonRuleStolenContent", 49 + "tools.ozone.report.defs#reasonRuleProhibitedSales", 50 + "tools.ozone.report.defs#reasonRuleBanEvasion", 51 + "tools.ozone.report.defs#reasonRuleOther", 52 + "tools.ozone.report.defs#reasonCivicElectoralProcess", 53 + "tools.ozone.report.defs#reasonCivicDisclosure", 54 + "tools.ozone.report.defs#reasonCivicInterference", 55 + "tools.ozone.report.defs#reasonCivicMisinformation", 56 + "tools.ozone.report.defs#reasonCivicImpersonation" 57 + ] 58 + }; 59 + 60 + token reasonSpam; 61 + token reasonViolation; 62 + token reasonMisleading; 63 + token reasonSexual; 64 + token reasonRude; 65 + token reasonOther; 66 + token reasonAppeal; 67 + 68 + def type subjectType = string constrained { knownValues: ["account", "record", "chat"] };
+6
std/com/atproto/repo/defs.mlf
··· 1 + // com.atproto.repo.defs 2 + 3 + def type commitMeta = { 4 + cid: Cid, 5 + rev: Tid, 6 + };
+7
std/com/atproto/repo/strongRef.mlf
··· 1 + // com.atproto.repo.strongRef 2 + // A URI with a content-hash fingerprint. 3 + 4 + inline type strongRef = { 5 + uri: AtUri, 6 + cid: Cid, 7 + };
+16
std/com/atproto/server/defs.mlf
··· 1 + // com.atproto.server.defs 2 + 3 + def type inviteCode = { 4 + code: string, 5 + available: integer, 6 + disabled: boolean, 7 + forAccount: string, 8 + createdBy: string, 9 + createdAt: Datetime, 10 + uses: [inviteCodeUse], 11 + }; 12 + 13 + def type inviteCodeUse = { 14 + usedBy: Did, 15 + usedAt: Datetime, 16 + };
+3
std/com/atproto/sync/defs.mlf
··· 1 + // com.atproto.sync.defs 2 + 3 + def type hostStatus = string constrained { knownValues: ["active", "idle", "offline", "throttled", "banned"] };
+63 -13
website/content/docs/language-guide/09-prelude.md
··· 1 1 +++ 2 - title = "Prelude" 2 + title = "Standard Library" 3 3 weight = 9 4 4 +++ 5 + 6 + MLF ships with a standard library that includes commonly used types for working with the AT Protocol. The standard library consists of: 7 + 8 + 1. **The Prelude** - Format types auto-imported into every file 9 + 2. **ATProto Types** - The full `com.atproto.*` namespace available for import 10 + 11 + ## The Prelude 5 12 6 13 The prelude is a set of definitions automatically available in every MLF file. You don't need to import them - they're always there. 7 14 ··· 61 68 62 69 When you use `Datetime` in your record, it expands to `string` with `format: "datetime"`. 63 70 64 - ## Future: ATProto Types 71 + ## ATProto Standard Types 65 72 66 - In the future, the prelude will also include all `com.atproto.*` definitions: 73 + The standard library includes all `com.atproto.*` types, which you can reference using fully qualified names or imports: 67 74 68 75 ```mlf 69 - // Eventually, these will be in the prelude 76 + // Use fully qualified names 77 + record myPost { 78 + reference: com.atproto.repo.strongRef, 79 + labels: [com.atproto.label.label], 80 + } 81 + 82 + // Or import them 83 + use com.atproto.repo.strongRef; 84 + use com.atproto.label.label; 85 + 70 86 record myPost { 71 - // Reference standard ATProto types without importing 72 - repo: com.atproto.sync.repo, 73 - commit: com.atproto.sync.commit, 87 + reference: strongRef, 88 + labels: [label], 74 89 } 75 90 ``` 76 91 77 - This will make it easier to reference standard ATProto types without manual imports. 92 + ### Available ATProto Namespaces 93 + 94 + The standard library includes: 95 + - `com.atproto.admin.*` - Administration and moderation types 96 + - `com.atproto.identity.*` - Identity resolution types 97 + - `com.atproto.label.*` - Content labeling types 98 + - `com.atproto.lexicon.*` - Lexicon schema types 99 + - `com.atproto.moderation.*` - Moderation action types 100 + - `com.atproto.repo.*` - Repository and record types 101 + - `com.atproto.server.*` - Server configuration types 102 + - `com.atproto.sync.*` - Synchronization types 103 + 104 + ### Common ATProto Types 105 + 106 + Here are some frequently used ATProto types: 107 + 108 + **`com.atproto.repo.strongRef`** - A URI with a content-hash fingerprint: 109 + ```mlf 110 + record post { 111 + replyTo?: com.atproto.repo.strongRef, 112 + } 113 + ``` 114 + 115 + **`com.atproto.label.label`** - Content labels for moderation: 116 + ```mlf 117 + record post { 118 + labels: [com.atproto.label.label], 119 + } 120 + ``` 121 + 122 + **`com.atproto.server.inviteCode`** - Server invite codes: 123 + ```mlf 124 + record account { 125 + invitedBy?: com.atproto.server.inviteCode, 126 + } 127 + ``` 78 128 79 129 ## Using Prelude Types 80 130 ··· 186 236 187 237 ## Summary 188 238 189 - The prelude provides: 190 - - ✅ String format types for common patterns 191 - - ✅ No imports needed - always available 192 - - ✅ Eventually will include all `com.atproto.*` types 239 + The standard library provides: 240 + - ✅ **Prelude types** - String format types auto-imported into every file 241 + - ✅ **ATProto types** - Full `com.atproto.*` namespace available via imports or fully qualified names 242 + - ✅ No manual downloads or setup needed - ships with MLF 193 243 194 - You've now learned all the core features of MLF! You can define records, add constraints, create custom types, use unions and tokens, define XRPC operations, import from other files, and use prelude types. 244 + You've now learned all the core features of MLF! You can define records, add constraints, create custom types, use unions and tokens, define XRPC operations, import from other files, and use standard library types. 195 245 196 246 ## What's Next? 197 247