1use crate::ast::{
2 ComponentProtocol, ComponentReference, CompositeIndex, Constraint, ConstraintParam, Field,
3 FieldType, Model, RelationInclusion, RelationType, Schema, Struct,
4};
5use crate::lexer::{Lexer, Token, TokenWithPos};
6use forgedb_validation::{validate_field_name, validate_model_name, Position};
7
8pub struct Parser {
9 tokens: Vec<Token>,
10 tokens_with_pos: Vec<TokenWithPos>,
11 position: usize,
12 use_validation: bool,
13}
14
15impl Parser {
16 pub fn new(input: &str) -> Result<Self, String> {
17 Self::new_with_validation(input, true)
18 }
19
20 pub fn new_with_validation(input: &str, use_validation: bool) -> Result<Self, String> {
21 let mut lexer = Lexer::new(input);
22 let tokens = lexer.tokenize()?;
23
24 let mut lexer = Lexer::new(input);
25 let tokens_with_pos = lexer.tokenize_with_pos()?;
26
27 Ok(Parser {
28 tokens,
29 tokens_with_pos,
30 position: 0,
31 use_validation,
32 })
33 }
34
35 fn get_current_position(&self) -> Option<Position> {
36 if self.position < self.tokens_with_pos.len() {
37 Some(self.tokens_with_pos[self.position].position)
38 } else {
39 None
40 }
41 }
42
43 fn current_token(&self) -> &Token {
44 if self.position < self.tokens.len() {
45 &self.tokens[self.position]
46 } else {
47 &Token::Eof
48 }
49 }
50
51 fn advance(&mut self) {
52 if self.position < self.tokens.len() {
53 self.position += 1;
54 }
55 }
56
57 fn skip_newlines(&mut self) {
58 while matches!(self.current_token(), Token::Newline) {
59 self.advance();
60 }
61 }
62
63 fn read_component_path(&mut self) -> Result<String, String> {
64 let mut path = String::new();
67
68 loop {
69 match self.current_token() {
70 Token::Ident(name) => {
71 path.push_str(name);
72 self.advance();
73
74 if matches!(self.current_token(), Token::Slash) {
76 path.push('/');
77 self.advance();
78 } else {
80 break;
82 }
83 }
84 _ => {
85 return Err(format!(
86 "Expected path component (identifier), found {:?}",
87 self.current_token()
88 ))
89 }
90 }
91 }
92
93 if path.is_empty() {
94 return Err("Component path cannot be empty".to_string());
95 }
96
97 Ok(path)
98 }
99
100 fn expect(&mut self, expected: Token) -> Result<(), String> {
101 if self.current_token() == &expected {
102 self.advance();
103 Ok(())
104 } else {
105 Err(format!(
106 "Expected {:?}, found {:?}",
107 expected,
108 self.current_token()
109 ))
110 }
111 }
112
113 fn parse_constraint(&mut self) -> Result<Constraint, String> {
114 self.expect(Token::At)?;
116
117 let name = match self.current_token() {
119 Token::Ident(s) => s.clone(),
120 _ => {
121 return Err(format!(
122 "Expected constraint name after '@', found {:?}",
123 self.current_token()
124 ))
125 }
126 };
127 self.advance();
128
129 let mut constraint = Constraint::new(name);
130
131 if matches!(self.current_token(), Token::LParen) {
133 self.advance();
134
135 loop {
137 match self.current_token() {
138 Token::Number(n) => {
139 constraint = constraint.with_param(ConstraintParam::Number(*n));
140 self.advance();
141 }
142 Token::Ident(s) => {
143 constraint = constraint.with_param(ConstraintParam::String(s.clone()));
144 self.advance();
145 }
146 Token::Str(s) => {
147 constraint = constraint.with_param(ConstraintParam::String(s.clone()));
148 self.advance();
149 }
150 Token::Asterisk => {
151 constraint =
153 constraint.with_param(ConstraintParam::String("*".to_string()));
154 self.advance();
155 }
156 _ => {
157 return Err(format!(
158 "Expected constraint parameter, found {:?}",
159 self.current_token()
160 ))
161 }
162 }
163
164 match self.current_token() {
166 Token::Comma => {
167 self.advance();
168 continue;
169 }
170 Token::RParen => {
171 self.advance();
172 break;
173 }
174 _ => {
175 return Err(format!(
176 "Expected ',' or ')' in constraint parameters, found {:?}",
177 self.current_token()
178 ))
179 }
180 }
181 }
182 }
183
184 Ok(constraint)
185 }
186
187 fn parse_type(&mut self) -> Result<FieldType, String> {
188 match self.current_token() {
190 Token::LBracket => {
192 self.advance();
193
194 let first_token = self.current_token().clone();
196
197 match first_token {
198 Token::Ident(name) => {
199 self.advance();
200
201 match self.current_token() {
203 Token::Semicolon => {
204 self.advance();
206 let count = match self.current_token() {
207 Token::Number(n) => *n as usize,
208 _ => {
209 return Err(format!(
210 "Expected array count after ';', found {:?}",
211 self.current_token()
212 ))
213 }
214 };
215 self.advance();
216 self.expect(Token::RBracket)?;
217 return Ok(FieldType::FixedArray(
219 Box::new(FieldType::StructType(name)),
220 count,
221 ));
222 }
223 Token::RBracket => {
224 self.advance();
226 return Ok(FieldType::Relation(RelationType::OneToMany(name)));
227 }
228 _ => {
229 return Err(format!(
230 "Expected ';' or ']' after type name, found {:?}",
231 self.current_token()
232 ))
233 }
234 }
235 }
236 _ => {
237 let inner_type = self.parse_primitive_type()?;
239 self.expect(Token::Semicolon)?;
240 let count = match self.current_token() {
241 Token::Number(n) => *n as usize,
242 _ => {
243 return Err(format!(
244 "Expected array count, found {:?}",
245 self.current_token()
246 ))
247 }
248 };
249 self.advance();
250 self.expect(Token::RBracket)?;
251 return Ok(FieldType::FixedArray(Box::new(inner_type), count));
252 }
253 }
254 }
255 Token::Asterisk => {
257 self.advance();
258 let model_name = match self.current_token() {
259 Token::Ident(name) => name.clone(),
260 _ => {
261 return Err(format!(
262 "Expected model name after '*', found {:?}",
263 self.current_token()
264 ))
265 }
266 };
267 self.advance();
268 return Ok(FieldType::Relation(RelationType::RequiredReference(
269 model_name,
270 )));
271 }
272 Token::Question => {
274 self.advance();
275 match self.current_token().clone() {
276 Token::Ident(name) => {
277 self.advance();
278 return Ok(FieldType::Relation(RelationType::OptionalReference(name)));
279 }
280 Token::TypeU32
282 | Token::TypeU64
283 | Token::TypeI32
284 | Token::TypeI64
285 | Token::TypeF64
286 | Token::TypeBool
287 | Token::TypeString
288 | Token::TypeUuid
289 | Token::TypeTimestamp
290 | Token::TypeChar => {
291 let inner = self.parse_primitive_type()?;
292 return Ok(FieldType::Nullable(Box::new(inner)));
293 }
294 _ => {
295 return Err(format!(
296 "Expected model name or primitive type after '?', found {:?}",
297 self.current_token()
298 ))
299 }
300 }
301 }
302 Token::Ident(name) => {
304 let type_name = name.clone();
305 self.advance();
306
307 if matches!(type_name.as_str(), "tsx" | "jsx" | "api")
309 && matches!(self.current_token(), Token::Colon)
310 {
311 self.advance(); self.skip_newlines(); self.expect(Token::Slash)?;
317 self.expect(Token::Slash)?;
318
319 let path = self.read_component_path()?;
321
322 let protocol = match type_name.as_str() {
324 "tsx" => ComponentProtocol::Tsx,
325 "jsx" => ComponentProtocol::Jsx,
326 "api" => ComponentProtocol::Api,
327 _ => unreachable!(),
328 };
329
330 return Ok(FieldType::Component(ComponentReference {
331 protocol,
332 path,
333 relations: RelationInclusion::None,
334 }));
335 }
336
337 return Ok(FieldType::StructType(type_name));
339 }
340 _ => {}
341 }
342
343 self.parse_primitive_type()
345 }
346
347 fn parse_primitive_type(&mut self) -> Result<FieldType, String> {
348 let field_type = match self.current_token() {
349 Token::TypeU32 => FieldType::U32,
350 Token::TypeU64 => FieldType::U64,
351 Token::TypeI32 => FieldType::I32,
352 Token::TypeI64 => FieldType::I64,
353 Token::TypeF64 => FieldType::F64,
354 Token::TypeBool => FieldType::Bool,
355 Token::TypeString => FieldType::String,
356 Token::TypeUuid => FieldType::Uuid,
357 Token::TypeTimestamp => FieldType::Timestamp,
358 Token::TypeChar => {
359 self.advance();
361 self.expect(Token::LParen)?;
362 let size = match self.current_token() {
363 Token::Number(n) => *n as usize,
364 _ => {
365 return Err(format!(
366 "Expected size after 'char(', found {:?}",
367 self.current_token()
368 ))
369 }
370 };
371 self.advance();
372 self.expect(Token::RParen)?;
373 return Ok(FieldType::Char(size));
374 }
375 _ => return Err(format!("Expected type, found {:?}", self.current_token())),
376 };
377 self.advance();
378 Ok(field_type)
379 }
380
381 fn parse_directive(&mut self) -> Result<CompositeIndex, String> {
382 self.expect(Token::At)?;
384
385 let directive_name = match self.current_token() {
387 Token::Ident(s) => s.clone(),
388 _ => {
389 return Err(format!(
390 "Expected directive name after '@', found {:?}",
391 self.current_token()
392 ))
393 }
394 };
395 self.advance();
396
397 match directive_name.as_str() {
398 "index" => self.parse_index_directive(),
399 _ => Err(format!("Unknown directive: @{}", directive_name)),
400 }
401 }
402
403 fn parse_index_directive(&mut self) -> Result<CompositeIndex, String> {
404 self.expect(Token::LParen)?;
406
407 let mut fields = Vec::new();
409 loop {
410 let field_name = match self.current_token() {
412 Token::Ident(s) => s.clone(),
413 _ => {
414 return Err(format!(
415 "Expected field name in @index directive, found {:?}",
416 self.current_token()
417 ))
418 }
419 };
420 self.advance();
421 fields.push(field_name);
422
423 match self.current_token() {
425 Token::Comma => {
426 self.advance();
427 }
429 Token::RParen => {
430 self.advance();
431 break;
432 }
433 _ => {
434 return Err(format!(
435 "Expected ',' or ')' in @index directive, found {:?}",
436 self.current_token()
437 ))
438 }
439 }
440 }
441
442 if fields.len() < 2 {
443 return Err("Composite index must include at least 2 fields".to_string());
444 }
445
446 Ok(CompositeIndex { fields })
447 }
448
449 fn parse_field(&mut self) -> Result<Field, String> {
450 self.skip_newlines();
451
452 let field_pos = self.get_current_position();
454 let name = match self.current_token() {
455 Token::Ident(s) => s.clone(),
456 _ => {
457 return Err(format!(
458 "Expected field name, found {:?}",
459 self.current_token()
460 ))
461 }
462 };
463 self.advance();
464
465 if self.use_validation {
467 if let Err(e) = validate_field_name(&name, field_pos) {
468 return Err(e.to_string());
469 }
470 }
471
472 self.expect(Token::Colon)?;
474
475 let mut auto_generate = false;
477 let mut unique = false;
478 let mut indexed = false;
479
480 loop {
481 match self.current_token() {
482 Token::Plus => {
483 auto_generate = true;
484 self.advance();
485 }
486 Token::Ampersand => {
487 unique = true;
488 self.advance();
489 }
490 Token::Caret => {
491 indexed = true;
492 self.advance();
493 }
494 _ => break,
495 }
496 }
497
498 let mut field_type = self.parse_type()?;
500
501 if matches!(self.current_token(), Token::Question) {
503 match field_type {
504 FieldType::StructType(ref name) => {
505 let name = name.clone();
506 self.advance();
507 field_type = FieldType::OptionalStructType(name);
508 }
509 FieldType::U32
511 | FieldType::U64
512 | FieldType::I32
513 | FieldType::I64
514 | FieldType::F64
515 | FieldType::Bool
516 | FieldType::String
517 | FieldType::Uuid
518 | FieldType::Timestamp
519 | FieldType::Char(_)
520 | FieldType::FixedArray(_, _) => {
521 let inner = field_type.clone();
522 self.advance();
523 field_type = FieldType::Nullable(Box::new(inner));
524 }
525 _ => {} }
527 }
528
529 if auto_generate && !field_type.is_auto_generatable() {
531 return Err(format!(
532 "Auto-generate symbol '+' cannot be used with type {:?}. Only u32, u64, uuid, and timestamp support auto-generation",
533 field_type
534 ));
535 }
536
537 let mut constraints = Vec::new();
539 let mut is_computed = false;
540 let mut fulltext_indexed = false;
541 let mut is_materialized = false;
542 while matches!(self.current_token(), Token::At) {
543 let constraint = self.parse_constraint()?;
544 if constraint.name == "relations" {
546 if let FieldType::Component(ref mut comp_ref) = field_type {
547 if constraint.params.is_empty() {
549 return Err("@relations directive requires parameters".to_string());
550 }
551
552 if constraint.params.len() == 1 {
554 if let ConstraintParam::String(s) = &constraint.params[0] {
555 if s == "*" {
556 comp_ref.relations = RelationInclusion::All;
557 } else {
558 comp_ref.relations = RelationInclusion::Specific(vec![s.clone()]);
560 }
561 } else {
562 return Err(
563 "@relations parameter must be a field name or *".to_string()
564 );
565 }
566 } else {
567 let mut fields = Vec::new();
569 for param in &constraint.params {
570 if let ConstraintParam::String(field_name) = param {
571 fields.push(field_name.clone());
572 } else {
573 return Err("@relations parameters must be field names".to_string());
574 }
575 }
576 comp_ref.relations = RelationInclusion::Specific(fields);
577 }
578 continue;
580 } else {
581 return Err(
582 "@relations directive can only be used with component fields".to_string(),
583 );
584 }
585 }
586 if constraint.name == "computed" {
588 is_computed = true;
589 }
590 if constraint.name == "fulltext" {
592 fulltext_indexed = true;
593 }
594 if constraint.name == "materialized" {
596 is_materialized = true;
597 }
598 constraints.push(constraint);
599 }
600
601 let index_type = field_type.default_index_type();
603
604 Ok(Field {
605 name,
606 field_type,
607 auto_generate,
608 unique,
609 indexed,
610 constraints,
611 index_type,
612 is_computed,
613 fulltext_indexed,
614 is_materialized,
615 })
616 }
617
618 fn parse_struct(&mut self) -> Result<Struct, String> {
619 self.skip_newlines();
620
621 self.expect(Token::KwStruct)?;
623
624 let struct_pos = self.get_current_position();
626 let name = match self.current_token() {
627 Token::Ident(s) => s.clone(),
628 _ => {
629 return Err(format!(
630 "Expected struct name, found {:?}",
631 self.current_token()
632 ))
633 }
634 };
635 self.advance();
636
637 if self.use_validation {
639 if let Err(e) = validate_model_name(&name, struct_pos) {
640 return Err(e.to_string());
641 }
642 }
643
644 self.skip_newlines();
646 self.expect(Token::LBrace)?;
647
648 let mut fields = Vec::new();
650 let mut field_names = std::collections::HashSet::new();
651 self.skip_newlines();
652
653 while !matches!(self.current_token(), Token::RBrace | Token::Eof) {
654 let field = self.parse_field()?;
655
656 if field_names.contains(&field.name) {
658 return Err(format!(
659 "Duplicate field name '{}' in struct '{}'",
660 field.name, name
661 ));
662 }
663 field_names.insert(field.name.clone());
664
665 fields.push(field);
666 self.skip_newlines();
667 }
668
669 self.expect(Token::RBrace)?;
671
672 if fields.is_empty() {
673 return Err(format!("Struct '{}' has no fields", name));
674 }
675
676 Ok(Struct { name, fields })
677 }
678
679 fn parse_model(&mut self) -> Result<Model, String> {
680 self.skip_newlines();
681
682 let model_pos = self.get_current_position();
684 let name = match self.current_token() {
685 Token::Ident(s) => s.clone(),
686 _ => {
687 return Err(format!(
688 "Expected model name, found {:?}",
689 self.current_token()
690 ))
691 }
692 };
693 self.advance();
694
695 if self.use_validation {
697 if let Err(e) = validate_model_name(&name, model_pos) {
698 return Err(e.to_string());
699 }
700 }
701
702 self.skip_newlines();
704 self.expect(Token::LBrace)?;
705
706 let mut fields = Vec::new();
708 let mut field_names = std::collections::HashSet::new();
709 let mut composite_indexes = Vec::new();
710 let mut soft_delete = false;
711 self.skip_newlines();
712
713 while !matches!(self.current_token(), Token::RBrace | Token::Eof) {
714 if matches!(self.current_token(), Token::At) {
716 let start_pos = self.position;
718 match self.parse_directive() {
719 Ok(composite_index) => {
720 composite_indexes.push(composite_index);
721 self.skip_newlines();
722 }
723 Err(_) => {
724 self.position = start_pos;
726 self.advance(); let directive_name = match self.current_token() {
728 Token::Ident(s) => s.clone(),
729 _ => {
730 return Err(format!(
731 "Expected directive name after '@', found {:?}",
732 self.current_token()
733 ))
734 }
735 };
736 self.advance();
737
738 match directive_name.as_str() {
739 "soft_delete" => {
740 soft_delete = true;
741 self.skip_newlines();
742 }
743 _ => {
744 return Err(format!(
745 "Unknown model directive: @{}",
746 directive_name
747 ));
748 }
749 }
750 }
751 }
752 } else {
753 let field = self.parse_field()?;
754
755 if field_names.contains(&field.name) {
757 return Err(format!(
758 "Duplicate field name '{}' in model '{}'",
759 field.name, name
760 ));
761 }
762 field_names.insert(field.name.clone());
763
764 fields.push(field);
765 self.skip_newlines();
766 }
767 }
768
769 self.expect(Token::RBrace)?;
771
772 if fields.is_empty() {
773 return Err(format!("Model '{}' has no fields", name));
774 }
775
776 for comp_idx in &composite_indexes {
778 for field_name in &comp_idx.fields {
779 if !field_names.contains(field_name) {
780 return Err(format!(
781 "Composite index in model '{}' references undefined field '{}'",
782 name, field_name
783 ));
784 }
785 }
786 }
787
788 Ok(Model {
789 name,
790 fields,
791 composite_indexes,
792 soft_delete,
793 })
794 }
795
796 pub fn parse(&mut self) -> Result<Schema, String> {
797 let mut structs = Vec::new();
798 let mut models = Vec::new();
799 let mut struct_names = std::collections::HashSet::new();
800 let mut model_names = std::collections::HashSet::new();
801 self.skip_newlines();
802
803 while !matches!(self.current_token(), Token::Eof) {
804 if matches!(self.current_token(), Token::KwStruct) {
806 let struct_def = self.parse_struct()?;
807
808 if struct_names.contains(&struct_def.name) {
810 return Err(format!("Duplicate struct name '{}'", struct_def.name));
811 }
812 struct_names.insert(struct_def.name.clone());
813
814 structs.push(struct_def);
815 } else {
816 let model = self.parse_model()?;
817
818 if model_names.contains(&model.name) {
820 return Err(format!("Duplicate model name '{}'", model.name));
821 }
822 model_names.insert(model.name.clone());
823
824 models.push(model);
825 }
826
827 self.skip_newlines();
828 }
829
830 if models.is_empty() && structs.is_empty() {
831 return Err("Schema is empty".to_string());
832 }
833
834 let schema = Schema { structs, models };
835
836 schema.validate_relations()?;
838 schema.validate_struct_references()?;
839
840 Ok(schema)
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847
848 fn field_constraints<'a>(schema: &'a Schema, model: &str, field: &str) -> &'a [Constraint] {
849 let m = schema
850 .models
851 .iter()
852 .find(|m| m.name == model)
853 .expect("model not found");
854 let f = m
855 .fields
856 .iter()
857 .find(|f| f.name == field)
858 .expect("field not found");
859 &f.constraints
860 }
861
862 #[test]
863 fn parses_string_literal_directive_arg() {
864 let src = r#"
865 User {
866 id: +uuid
867 phone: string @pattern("^[0-9]+$")
868 }
869 "#;
870 let schema = Parser::new(src).unwrap().parse().unwrap();
871 let cons = field_constraints(&schema, "User", "phone");
872 assert_eq!(cons.len(), 1);
873 assert_eq!(cons[0].name, "pattern");
874 assert_eq!(
875 cons[0].params,
876 vec![ConstraintParam::String("^[0-9]+$".to_string())]
877 );
878 }
879
880 #[test]
881 fn parses_string_default_on_nullable_string() {
882 let src = r#"
883 Ticket {
884 id: +uuid
885 status: string? @default("pending")
886 }
887 "#;
888 let schema = Parser::new(src).unwrap().parse().unwrap();
889 let cons = field_constraints(&schema, "Ticket", "status");
890 assert_eq!(cons[0].name, "default");
891 assert_eq!(
892 cons[0].params,
893 vec![ConstraintParam::String("pending".to_string())]
894 );
895 }
896
897 #[test]
898 fn bare_identifier_default_still_parses() {
899 let src = r#"
901 Ticket {
902 id: +uuid
903 status: string @default(pending)
904 }
905 "#;
906 let schema = Parser::new(src).unwrap().parse().unwrap();
907 let cons = field_constraints(&schema, "Ticket", "status");
908 assert_eq!(
909 cons[0].params,
910 vec![ConstraintParam::String("pending".to_string())]
911 );
912 }
913
914 #[test]
915 fn string_and_number_args_mix() {
916 let src = r#"
917 Thing {
918 id: +uuid
919 code: string @pattern("[A-Z]{3}") @length(3, 3)
920 }
921 "#;
922 let schema = Parser::new(src).unwrap().parse().unwrap();
923 let cons = field_constraints(&schema, "Thing", "code");
924 assert_eq!(cons.len(), 2);
925 assert_eq!(cons[0].name, "pattern");
926 assert_eq!(
927 cons[0].params,
928 vec![ConstraintParam::String("[A-Z]{3}".to_string())]
929 );
930 assert_eq!(cons[1].name, "length");
931 assert_eq!(
932 cons[1].params,
933 vec![ConstraintParam::Number(3), ConstraintParam::Number(3)]
934 );
935 }
936}