1use crate::types::{Effect, StackType, Type};
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct SourceLocation {
12 pub file: PathBuf,
13 pub start_line: usize,
15 pub end_line: usize,
17}
18
19impl SourceLocation {
20 pub fn new(file: PathBuf, line: usize) -> Self {
22 SourceLocation {
23 file,
24 start_line: line,
25 end_line: line,
26 }
27 }
28
29 pub fn span(file: PathBuf, start_line: usize, end_line: usize) -> Self {
31 debug_assert!(
32 start_line <= end_line,
33 "SourceLocation: start_line ({}) must be <= end_line ({})",
34 start_line,
35 end_line
36 );
37 SourceLocation {
38 file,
39 start_line,
40 end_line,
41 }
42 }
43
44 pub fn line(&self) -> usize {
46 self.start_line
47 }
48}
49
50impl std::fmt::Display for SourceLocation {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 if self.start_line == self.end_line {
53 write!(f, "{}:{}", self.file.display(), self.start_line + 1)
54 } else {
55 write!(
56 f,
57 "{}:{}-{}",
58 self.file.display(),
59 self.start_line + 1,
60 self.end_line + 1
61 )
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub enum Include {
69 Std(String),
71 Relative(String),
73 Ffi(String),
75}
76
77#[derive(Debug, Clone, PartialEq)]
84pub struct UnionField {
85 pub name: String,
86 pub type_name: String, }
88
89#[derive(Debug, Clone, PartialEq)]
92pub struct UnionVariant {
93 pub name: String,
94 pub fields: Vec<UnionField>,
95 pub source: Option<SourceLocation>,
96}
97
98#[derive(Debug, Clone, PartialEq)]
108pub struct UnionDef {
109 pub name: String,
110 pub variants: Vec<UnionVariant>,
111 pub source: Option<SourceLocation>,
112}
113
114#[derive(Debug, Clone, PartialEq)]
118pub enum Pattern {
119 Variant(String),
122
123 VariantWithBindings { name: String, bindings: Vec<String> },
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub struct MatchArm {
131 pub pattern: Pattern,
132 pub body: Vec<Statement>,
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub struct Program {
137 pub includes: Vec<Include>,
138 pub unions: Vec<UnionDef>,
139 pub words: Vec<WordDef>,
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct WordDef {
144 pub name: String,
145 pub effect: Option<Effect>,
148 pub body: Vec<Statement>,
149 pub source: Option<SourceLocation>,
151}
152
153#[derive(Debug, Clone, PartialEq, Default)]
155pub struct Span {
156 pub line: usize,
158 pub column: usize,
160 pub length: usize,
162}
163
164impl Span {
165 pub fn new(line: usize, column: usize, length: usize) -> Self {
166 Span {
167 line,
168 column,
169 length,
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Default)]
176pub struct QuotationSpan {
177 pub start_line: usize,
179 pub start_column: usize,
181 pub end_line: usize,
183 pub end_column: usize,
185}
186
187impl QuotationSpan {
188 pub fn new(start_line: usize, start_column: usize, end_line: usize, end_column: usize) -> Self {
189 QuotationSpan {
190 start_line,
191 start_column,
192 end_line,
193 end_column,
194 }
195 }
196
197 pub fn contains(&self, line: usize, column: usize) -> bool {
199 if line < self.start_line || line > self.end_line {
200 return false;
201 }
202 if line == self.start_line && column < self.start_column {
203 return false;
204 }
205 if line == self.end_line && column >= self.end_column {
206 return false;
207 }
208 true
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
213pub enum Statement {
214 IntLiteral(i64),
216
217 FloatLiteral(f64),
219
220 BoolLiteral(bool),
222
223 StringLiteral(String),
225
226 Symbol(String),
231
232 WordCall { name: String, span: Option<Span> },
235
236 If {
241 then_branch: Vec<Statement>,
243 else_branch: Option<Vec<Statement>>,
245 },
246
247 Quotation {
257 id: usize,
258 body: Vec<Statement>,
259 span: Option<QuotationSpan>,
260 },
261
262 Match {
276 arms: Vec<MatchArm>,
278 },
279}
280
281impl Program {
282 pub fn new() -> Self {
283 Program {
284 includes: Vec::new(),
285 unions: Vec::new(),
286 words: Vec::new(),
287 }
288 }
289
290 pub fn find_union(&self, name: &str) -> Option<&UnionDef> {
292 self.unions.iter().find(|u| u.name == name)
293 }
294
295 pub fn find_word(&self, name: &str) -> Option<&WordDef> {
296 self.words.iter().find(|w| w.name == name)
297 }
298
299 pub fn validate_word_calls(&self) -> Result<(), String> {
301 self.validate_word_calls_with_externals(&[])
302 }
303
304 pub fn validate_word_calls_with_externals(
309 &self,
310 external_words: &[&str],
311 ) -> Result<(), String> {
312 let builtins = [
315 "io.write",
317 "io.write-line",
318 "io.read-line",
319 "io.read-line+",
320 "io.read-n",
321 "int->string",
322 "symbol->string",
323 "string->symbol",
324 "args.count",
326 "args.at",
327 "file.slurp",
329 "file.exists?",
330 "file.for-each-line+",
331 "string.concat",
333 "string.length",
334 "string.byte-length",
335 "string.char-at",
336 "string.substring",
337 "char->string",
338 "string.find",
339 "string.split",
340 "string.contains",
341 "string.starts-with",
342 "string.empty?",
343 "string.trim",
344 "string.chomp",
345 "string.to-upper",
346 "string.to-lower",
347 "string.equal?",
348 "string.json-escape",
349 "string->int",
350 "symbol.=",
352 "encoding.base64-encode",
354 "encoding.base64-decode",
355 "encoding.base64url-encode",
356 "encoding.base64url-decode",
357 "encoding.hex-encode",
358 "encoding.hex-decode",
359 "crypto.sha256",
361 "crypto.hmac-sha256",
362 "crypto.constant-time-eq",
363 "crypto.random-bytes",
364 "crypto.uuid4",
365 "http.get",
367 "http.post",
368 "http.put",
369 "http.delete",
370 "list.make",
372 "list.push",
373 "list.get",
374 "list.set",
375 "list.map",
376 "list.filter",
377 "list.fold",
378 "list.each",
379 "list.length",
380 "list.empty?",
381 "map.make",
383 "map.get",
384 "map.set",
385 "map.has?",
386 "map.remove",
387 "map.keys",
388 "map.values",
389 "map.size",
390 "map.empty?",
391 "variant.field-count",
393 "variant.tag",
394 "variant.field-at",
395 "variant.append",
396 "variant.last",
397 "variant.init",
398 "variant.make-0",
399 "variant.make-1",
400 "variant.make-2",
401 "variant.make-3",
402 "variant.make-4",
403 "wrap-0",
405 "wrap-1",
406 "wrap-2",
407 "wrap-3",
408 "wrap-4",
409 "i.add",
411 "i.subtract",
412 "i.multiply",
413 "i.divide",
414 "i.modulo",
415 "i.+",
417 "i.-",
418 "i.*",
419 "i./",
420 "i.%",
421 "i.=",
423 "i.<",
424 "i.>",
425 "i.<=",
426 "i.>=",
427 "i.<>",
428 "i.eq",
430 "i.lt",
431 "i.gt",
432 "i.lte",
433 "i.gte",
434 "i.neq",
435 "dup",
437 "drop",
438 "swap",
439 "over",
440 "rot",
441 "nip",
442 "tuck",
443 "2dup",
444 "3drop",
445 "pick",
446 "roll",
447 "and",
449 "or",
450 "not",
451 "band",
453 "bor",
454 "bxor",
455 "bnot",
456 "shl",
457 "shr",
458 "popcount",
459 "clz",
460 "ctz",
461 "int-bits",
462 "chan.make",
464 "chan.send",
465 "chan.receive",
466 "chan.close",
467 "chan.yield",
468 "call",
470 "times",
471 "while",
472 "until",
473 "strand.spawn",
474 "strand.weave",
475 "strand.resume",
476 "strand.weave-cancel",
477 "yield",
478 "cond",
479 "tcp.listen",
481 "tcp.accept",
482 "tcp.read",
483 "tcp.write",
484 "tcp.close",
485 "os.getenv",
487 "os.home-dir",
488 "os.current-dir",
489 "os.path-exists",
490 "os.path-is-file",
491 "os.path-is-dir",
492 "os.path-join",
493 "os.path-parent",
494 "os.path-filename",
495 "os.exit",
496 "os.name",
497 "os.arch",
498 "f.add",
500 "f.subtract",
501 "f.multiply",
502 "f.divide",
503 "f.+",
505 "f.-",
506 "f.*",
507 "f./",
508 "f.=",
510 "f.<",
511 "f.>",
512 "f.<=",
513 "f.>=",
514 "f.<>",
515 "f.eq",
517 "f.lt",
518 "f.gt",
519 "f.lte",
520 "f.gte",
521 "f.neq",
522 "int->float",
524 "float->int",
525 "float->string",
526 "string->float",
527 "test.init",
529 "test.finish",
530 "test.has-failures",
531 "test.assert",
532 "test.assert-not",
533 "test.assert-eq",
534 "test.assert-eq-str",
535 "test.fail",
536 "test.pass-count",
537 "test.fail-count",
538 "time.now",
540 "time.nanos",
541 "time.sleep-ms",
542 "son.dump",
544 "son.dump-pretty",
545 "stack.dump",
547 ];
548
549 for word in &self.words {
550 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
551 }
552
553 Ok(())
554 }
555
556 fn validate_statements(
558 &self,
559 statements: &[Statement],
560 word_name: &str,
561 builtins: &[&str],
562 external_words: &[&str],
563 ) -> Result<(), String> {
564 for statement in statements {
565 match statement {
566 Statement::WordCall { name, .. } => {
567 if builtins.contains(&name.as_str()) {
569 continue;
570 }
571 if self.find_word(name).is_some() {
573 continue;
574 }
575 if external_words.contains(&name.as_str()) {
577 continue;
578 }
579 return Err(format!(
581 "Undefined word '{}' called in word '{}'. \
582 Did you forget to define it or misspell a built-in?",
583 name, word_name
584 ));
585 }
586 Statement::If {
587 then_branch,
588 else_branch,
589 } => {
590 self.validate_statements(then_branch, word_name, builtins, external_words)?;
592 if let Some(eb) = else_branch {
593 self.validate_statements(eb, word_name, builtins, external_words)?;
594 }
595 }
596 Statement::Quotation { body, .. } => {
597 self.validate_statements(body, word_name, builtins, external_words)?;
599 }
600 Statement::Match { arms } => {
601 for arm in arms {
603 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
604 }
605 }
606 _ => {} }
608 }
609 Ok(())
610 }
611
612 pub const MAX_VARIANT_FIELDS: usize = 4;
616
617 pub fn generate_constructors(&mut self) -> Result<(), String> {
627 let mut new_words = Vec::new();
628
629 for union_def in &self.unions {
630 for variant in &union_def.variants {
631 let constructor_name = format!("Make-{}", variant.name);
632 let field_count = variant.fields.len();
633
634 if field_count > Self::MAX_VARIANT_FIELDS {
636 return Err(format!(
637 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
638 Consider grouping fields into nested union types.",
639 variant.name,
640 union_def.name,
641 field_count,
642 Self::MAX_VARIANT_FIELDS
643 ));
644 }
645
646 let mut input_stack = StackType::RowVar("a".to_string());
649 for field in &variant.fields {
650 let field_type = parse_type_name(&field.type_name);
651 input_stack = input_stack.push(field_type);
652 }
653
654 let output_stack =
656 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
657
658 let effect = Effect::new(input_stack, output_stack);
659
660 let body = vec![
664 Statement::Symbol(variant.name.clone()),
665 Statement::WordCall {
666 name: format!("variant.make-{}", field_count),
667 span: None, },
669 ];
670
671 new_words.push(WordDef {
672 name: constructor_name,
673 effect: Some(effect),
674 body,
675 source: variant.source.clone(),
676 });
677 }
678 }
679
680 self.words.extend(new_words);
681 Ok(())
682 }
683}
684
685fn parse_type_name(name: &str) -> Type {
688 match name {
689 "Int" => Type::Int,
690 "Float" => Type::Float,
691 "Bool" => Type::Bool,
692 "String" => Type::String,
693 "Channel" => Type::Channel,
694 other => Type::Union(other.to_string()),
695 }
696}
697
698impl Default for Program {
699 fn default() -> Self {
700 Self::new()
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_validate_builtin_words() {
710 let program = Program {
711 includes: vec![],
712 unions: vec![],
713 words: vec![WordDef {
714 name: "main".to_string(),
715 effect: None,
716 body: vec![
717 Statement::IntLiteral(2),
718 Statement::IntLiteral(3),
719 Statement::WordCall {
720 name: "i.add".to_string(),
721 span: None,
722 },
723 Statement::WordCall {
724 name: "io.write-line".to_string(),
725 span: None,
726 },
727 ],
728 source: None,
729 }],
730 };
731
732 assert!(program.validate_word_calls().is_ok());
734 }
735
736 #[test]
737 fn test_validate_user_defined_words() {
738 let program = Program {
739 includes: vec![],
740 unions: vec![],
741 words: vec![
742 WordDef {
743 name: "helper".to_string(),
744 effect: None,
745 body: vec![Statement::IntLiteral(42)],
746 source: None,
747 },
748 WordDef {
749 name: "main".to_string(),
750 effect: None,
751 body: vec![Statement::WordCall {
752 name: "helper".to_string(),
753 span: None,
754 }],
755 source: None,
756 },
757 ],
758 };
759
760 assert!(program.validate_word_calls().is_ok());
762 }
763
764 #[test]
765 fn test_validate_undefined_word() {
766 let program = Program {
767 includes: vec![],
768 unions: vec![],
769 words: vec![WordDef {
770 name: "main".to_string(),
771 effect: None,
772 body: vec![Statement::WordCall {
773 name: "undefined_word".to_string(),
774 span: None,
775 }],
776 source: None,
777 }],
778 };
779
780 let result = program.validate_word_calls();
782 assert!(result.is_err());
783 let error = result.unwrap_err();
784 assert!(error.contains("undefined_word"));
785 assert!(error.contains("main"));
786 }
787
788 #[test]
789 fn test_validate_misspelled_builtin() {
790 let program = Program {
791 includes: vec![],
792 unions: vec![],
793 words: vec![WordDef {
794 name: "main".to_string(),
795 effect: None,
796 body: vec![Statement::WordCall {
797 name: "wrte_line".to_string(),
798 span: None,
799 }], source: None,
801 }],
802 };
803
804 let result = program.validate_word_calls();
806 assert!(result.is_err());
807 let error = result.unwrap_err();
808 assert!(error.contains("wrte_line"));
809 assert!(error.contains("misspell"));
810 }
811
812 #[test]
813 fn test_generate_constructors() {
814 let mut program = Program {
815 includes: vec![],
816 unions: vec![UnionDef {
817 name: "Message".to_string(),
818 variants: vec![
819 UnionVariant {
820 name: "Get".to_string(),
821 fields: vec![UnionField {
822 name: "response-chan".to_string(),
823 type_name: "Int".to_string(),
824 }],
825 source: None,
826 },
827 UnionVariant {
828 name: "Put".to_string(),
829 fields: vec![
830 UnionField {
831 name: "value".to_string(),
832 type_name: "String".to_string(),
833 },
834 UnionField {
835 name: "response-chan".to_string(),
836 type_name: "Int".to_string(),
837 },
838 ],
839 source: None,
840 },
841 ],
842 source: None,
843 }],
844 words: vec![],
845 };
846
847 program.generate_constructors().unwrap();
849
850 assert_eq!(program.words.len(), 2);
852
853 let make_get = program
855 .find_word("Make-Get")
856 .expect("Make-Get should exist");
857 assert_eq!(make_get.name, "Make-Get");
858 assert!(make_get.effect.is_some());
859 let effect = make_get.effect.as_ref().unwrap();
860 assert_eq!(
863 format!("{:?}", effect.outputs),
864 "Cons { rest: RowVar(\"a\"), top: Union(\"Message\") }"
865 );
866
867 let make_put = program
869 .find_word("Make-Put")
870 .expect("Make-Put should exist");
871 assert_eq!(make_put.name, "Make-Put");
872 assert!(make_put.effect.is_some());
873
874 assert_eq!(make_get.body.len(), 2);
877 match &make_get.body[0] {
878 Statement::Symbol(s) if s == "Get" => {}
879 other => panic!("Expected Symbol(\"Get\") for variant tag, got {:?}", other),
880 }
881 match &make_get.body[1] {
882 Statement::WordCall { name, span: None } if name == "variant.make-1" => {}
883 _ => panic!("Expected WordCall(variant.make-1)"),
884 }
885
886 assert_eq!(make_put.body.len(), 2);
888 match &make_put.body[0] {
889 Statement::Symbol(s) if s == "Put" => {}
890 other => panic!("Expected Symbol(\"Put\") for variant tag, got {:?}", other),
891 }
892 match &make_put.body[1] {
893 Statement::WordCall { name, span: None } if name == "variant.make-2" => {}
894 _ => panic!("Expected WordCall(variant.make-2)"),
895 }
896 }
897}