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
45impl std::fmt::Display for SourceLocation {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 if self.start_line == self.end_line {
48 write!(f, "{}:{}", self.file.display(), self.start_line + 1)
49 } else {
50 write!(
51 f,
52 "{}:{}-{}",
53 self.file.display(),
54 self.start_line + 1,
55 self.end_line + 1
56 )
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub enum Include {
64 Std(String),
66 Relative(String),
68 Ffi(String),
70}
71
72#[derive(Debug, Clone, PartialEq)]
79pub struct UnionField {
80 pub name: String,
81 pub type_name: String, }
83
84#[derive(Debug, Clone, PartialEq)]
87pub struct UnionVariant {
88 pub name: String,
89 pub fields: Vec<UnionField>,
90 pub source: Option<SourceLocation>,
91}
92
93#[derive(Debug, Clone, PartialEq)]
103pub struct UnionDef {
104 pub name: String,
105 pub variants: Vec<UnionVariant>,
106 pub source: Option<SourceLocation>,
107}
108
109#[derive(Debug, Clone, PartialEq)]
113pub enum Pattern {
114 Variant(String),
117
118 VariantWithBindings { name: String, bindings: Vec<String> },
121}
122
123#[derive(Debug, Clone, PartialEq)]
125pub struct MatchArm {
126 pub pattern: Pattern,
127 pub body: Vec<Statement>,
128 pub span: Option<Span>,
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub struct Program {
134 pub includes: Vec<Include>,
135 pub unions: Vec<UnionDef>,
136 pub words: Vec<WordDef>,
137}
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct WordDef {
141 pub name: String,
142 pub effect: Option<Effect>,
145 pub body: Vec<Statement>,
146 pub source: Option<SourceLocation>,
148 pub allowed_lints: Vec<String>,
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 span: Option<Span>,
247 },
248
249 Quotation {
259 id: usize,
260 body: Vec<Statement>,
261 span: Option<QuotationSpan>,
262 },
263
264 Match {
278 arms: Vec<MatchArm>,
280 span: Option<Span>,
282 },
283}
284
285impl Program {
286 pub fn new() -> Self {
287 Program {
288 includes: Vec::new(),
289 unions: Vec::new(),
290 words: Vec::new(),
291 }
292 }
293
294 pub fn find_word(&self, name: &str) -> Option<&WordDef> {
295 self.words.iter().find(|w| w.name == name)
296 }
297
298 pub fn validate_word_calls(&self) -> Result<(), String> {
300 self.validate_word_calls_with_externals(&[])
301 }
302
303 pub fn validate_word_calls_with_externals(
308 &self,
309 external_words: &[&str],
310 ) -> Result<(), String> {
311 let builtins = [
314 "io.write",
316 "io.write-line",
317 "io.read-line",
318 "io.read-line+",
319 "io.read-n",
320 "int->string",
321 "symbol->string",
322 "string->symbol",
323 "args.count",
325 "args.at",
326 "file.slurp",
328 "file.exists?",
329 "file.for-each-line+",
330 "file.spit",
331 "file.append",
332 "file.delete",
333 "file.size",
334 "dir.exists?",
336 "dir.make",
337 "dir.delete",
338 "dir.list",
339 "string.concat",
341 "string.length",
342 "string.byte-length",
343 "string.char-at",
344 "string.substring",
345 "char->string",
346 "string.find",
347 "string.split",
348 "string.contains",
349 "string.starts-with",
350 "string.empty?",
351 "string.trim",
352 "string.chomp",
353 "string.to-upper",
354 "string.to-lower",
355 "string.equal?",
356 "string.json-escape",
357 "string->int",
358 "symbol.=",
360 "encoding.base64-encode",
362 "encoding.base64-decode",
363 "encoding.base64url-encode",
364 "encoding.base64url-decode",
365 "encoding.hex-encode",
366 "encoding.hex-decode",
367 "crypto.sha256",
369 "crypto.hmac-sha256",
370 "crypto.constant-time-eq",
371 "crypto.random-bytes",
372 "crypto.random-int",
373 "crypto.uuid4",
374 "crypto.aes-gcm-encrypt",
375 "crypto.aes-gcm-decrypt",
376 "crypto.pbkdf2-sha256",
377 "crypto.ed25519-keypair",
378 "crypto.ed25519-sign",
379 "crypto.ed25519-verify",
380 "http.get",
382 "http.post",
383 "http.put",
384 "http.delete",
385 "list.make",
387 "list.push",
388 "list.push!",
389 "list.get",
390 "list.set",
391 "list.map",
392 "list.filter",
393 "list.fold",
394 "list.each",
395 "list.length",
396 "list.empty?",
397 "map.make",
399 "map.get",
400 "map.set",
401 "map.has?",
402 "map.remove",
403 "map.keys",
404 "map.values",
405 "map.size",
406 "map.empty?",
407 "variant.field-count",
409 "variant.tag",
410 "variant.field-at",
411 "variant.append",
412 "variant.last",
413 "variant.init",
414 "variant.make-0",
415 "variant.make-1",
416 "variant.make-2",
417 "variant.make-3",
418 "variant.make-4",
419 "wrap-0",
421 "wrap-1",
422 "wrap-2",
423 "wrap-3",
424 "wrap-4",
425 "i.add",
427 "i.subtract",
428 "i.multiply",
429 "i.divide",
430 "i.modulo",
431 "i.+",
433 "i.-",
434 "i.*",
435 "i./",
436 "i.%",
437 "i.=",
439 "i.<",
440 "i.>",
441 "i.<=",
442 "i.>=",
443 "i.<>",
444 "i.eq",
446 "i.lt",
447 "i.gt",
448 "i.lte",
449 "i.gte",
450 "i.neq",
451 "dup",
453 "drop",
454 "swap",
455 "over",
456 "rot",
457 "nip",
458 "tuck",
459 "2dup",
460 "3drop",
461 "pick",
462 "roll",
463 ">aux",
465 "aux>",
466 "and",
468 "or",
469 "not",
470 "band",
472 "bor",
473 "bxor",
474 "bnot",
475 "shl",
476 "shr",
477 "popcount",
478 "clz",
479 "ctz",
480 "int-bits",
481 "chan.make",
483 "chan.send",
484 "chan.receive",
485 "chan.close",
486 "chan.yield",
487 "call",
489 "strand.spawn",
490 "strand.weave",
491 "strand.resume",
492 "strand.weave-cancel",
493 "yield",
494 "cond",
495 "tcp.listen",
497 "tcp.accept",
498 "tcp.read",
499 "tcp.write",
500 "tcp.close",
501 "os.getenv",
503 "os.home-dir",
504 "os.current-dir",
505 "os.path-exists",
506 "os.path-is-file",
507 "os.path-is-dir",
508 "os.path-join",
509 "os.path-parent",
510 "os.path-filename",
511 "os.exit",
512 "os.name",
513 "os.arch",
514 "signal.trap",
516 "signal.received?",
517 "signal.pending?",
518 "signal.default",
519 "signal.ignore",
520 "signal.clear",
521 "signal.SIGINT",
522 "signal.SIGTERM",
523 "signal.SIGHUP",
524 "signal.SIGPIPE",
525 "signal.SIGUSR1",
526 "signal.SIGUSR2",
527 "signal.SIGCHLD",
528 "signal.SIGALRM",
529 "signal.SIGCONT",
530 "terminal.raw-mode",
532 "terminal.read-char",
533 "terminal.read-char?",
534 "terminal.width",
535 "terminal.height",
536 "terminal.flush",
537 "f.add",
539 "f.subtract",
540 "f.multiply",
541 "f.divide",
542 "f.+",
544 "f.-",
545 "f.*",
546 "f./",
547 "f.=",
549 "f.<",
550 "f.>",
551 "f.<=",
552 "f.>=",
553 "f.<>",
554 "f.eq",
556 "f.lt",
557 "f.gt",
558 "f.lte",
559 "f.gte",
560 "f.neq",
561 "int->float",
563 "float->int",
564 "float->string",
565 "string->float",
566 "test.init",
568 "test.finish",
569 "test.has-failures",
570 "test.assert",
571 "test.assert-not",
572 "test.assert-eq",
573 "test.assert-eq-str",
574 "test.fail",
575 "test.pass-count",
576 "test.fail-count",
577 "time.now",
579 "time.nanos",
580 "time.sleep-ms",
581 "son.dump",
583 "son.dump-pretty",
584 "stack.dump",
586 "regex.match?",
588 "regex.find",
589 "regex.find-all",
590 "regex.replace",
591 "regex.replace-all",
592 "regex.captures",
593 "regex.split",
594 "regex.valid?",
595 "compress.gzip",
597 "compress.gzip-level",
598 "compress.gunzip",
599 "compress.zstd",
600 "compress.zstd-level",
601 "compress.unzstd",
602 ];
603
604 for word in &self.words {
605 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
606 }
607
608 Ok(())
609 }
610
611 fn validate_statements(
613 &self,
614 statements: &[Statement],
615 word_name: &str,
616 builtins: &[&str],
617 external_words: &[&str],
618 ) -> Result<(), String> {
619 for statement in statements {
620 match statement {
621 Statement::WordCall { name, .. } => {
622 if builtins.contains(&name.as_str()) {
624 continue;
625 }
626 if self.find_word(name).is_some() {
628 continue;
629 }
630 if external_words.contains(&name.as_str()) {
632 continue;
633 }
634 return Err(format!(
636 "Undefined word '{}' called in word '{}'. \
637 Did you forget to define it or misspell a built-in?",
638 name, word_name
639 ));
640 }
641 Statement::If {
642 then_branch,
643 else_branch,
644 span: _,
645 } => {
646 self.validate_statements(then_branch, word_name, builtins, external_words)?;
648 if let Some(eb) = else_branch {
649 self.validate_statements(eb, word_name, builtins, external_words)?;
650 }
651 }
652 Statement::Quotation { body, .. } => {
653 self.validate_statements(body, word_name, builtins, external_words)?;
655 }
656 Statement::Match { arms, span: _ } => {
657 for arm in arms {
659 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
660 }
661 }
662 _ => {} }
664 }
665 Ok(())
666 }
667
668 pub const MAX_VARIANT_FIELDS: usize = 12;
672
673 pub fn generate_constructors(&mut self) -> Result<(), String> {
686 let mut new_words = Vec::new();
687
688 for union_def in &self.unions {
689 for variant in &union_def.variants {
690 let field_count = variant.fields.len();
691
692 if field_count > Self::MAX_VARIANT_FIELDS {
694 return Err(format!(
695 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
696 Consider grouping fields into nested union types.",
697 variant.name,
698 union_def.name,
699 field_count,
700 Self::MAX_VARIANT_FIELDS
701 ));
702 }
703
704 let constructor_name = format!("Make-{}", variant.name);
706 let mut input_stack = StackType::RowVar("a".to_string());
707 for field in &variant.fields {
708 let field_type = parse_type_name(&field.type_name);
709 input_stack = input_stack.push(field_type);
710 }
711 let output_stack =
712 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
713 let effect = Effect::new(input_stack, output_stack);
714 let body = vec![
715 Statement::Symbol(variant.name.clone()),
716 Statement::WordCall {
717 name: format!("variant.make-{}", field_count),
718 span: None,
719 },
720 ];
721 new_words.push(WordDef {
722 name: constructor_name,
723 effect: Some(effect),
724 body,
725 source: variant.source.clone(),
726 allowed_lints: vec![],
727 });
728
729 let predicate_name = format!("is-{}?", variant.name);
733 let predicate_input =
734 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
735 let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
736 let predicate_effect = Effect::new(predicate_input, predicate_output);
737 let predicate_body = vec![
738 Statement::WordCall {
739 name: "variant.tag".to_string(),
740 span: None,
741 },
742 Statement::Symbol(variant.name.clone()),
743 Statement::WordCall {
744 name: "symbol.=".to_string(),
745 span: None,
746 },
747 ];
748 new_words.push(WordDef {
749 name: predicate_name,
750 effect: Some(predicate_effect),
751 body: predicate_body,
752 source: variant.source.clone(),
753 allowed_lints: vec![],
754 });
755
756 for (index, field) in variant.fields.iter().enumerate() {
760 let accessor_name = format!("{}-{}", variant.name, field.name);
761 let field_type = parse_type_name(&field.type_name);
762 let accessor_input = StackType::RowVar("a".to_string())
763 .push(Type::Union(union_def.name.clone()));
764 let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
765 let accessor_effect = Effect::new(accessor_input, accessor_output);
766 let accessor_body = vec![
767 Statement::IntLiteral(index as i64),
768 Statement::WordCall {
769 name: "variant.field-at".to_string(),
770 span: None,
771 },
772 ];
773 new_words.push(WordDef {
774 name: accessor_name,
775 effect: Some(accessor_effect),
776 body: accessor_body,
777 source: variant.source.clone(), allowed_lints: vec![],
779 });
780 }
781 }
782 }
783
784 self.words.extend(new_words);
785 Ok(())
786 }
787
788 pub fn fixup_union_types(&mut self) {
797 let union_names: std::collections::HashSet<String> =
799 self.unions.iter().map(|u| u.name.clone()).collect();
800
801 for word in &mut self.words {
803 if let Some(ref mut effect) = word.effect {
804 Self::fixup_stack_type(&mut effect.inputs, &union_names);
805 Self::fixup_stack_type(&mut effect.outputs, &union_names);
806 }
807 }
808 }
809
810 fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
812 match stack {
813 StackType::Empty | StackType::RowVar(_) => {}
814 StackType::Cons { rest, top } => {
815 Self::fixup_type(top, union_names);
816 Self::fixup_stack_type(rest, union_names);
817 }
818 }
819 }
820
821 fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
823 match ty {
824 Type::Var(name) if union_names.contains(name) => {
825 *ty = Type::Union(name.clone());
826 }
827 Type::Quotation(effect) => {
828 Self::fixup_stack_type(&mut effect.inputs, union_names);
829 Self::fixup_stack_type(&mut effect.outputs, union_names);
830 }
831 Type::Closure { effect, captures } => {
832 Self::fixup_stack_type(&mut effect.inputs, union_names);
833 Self::fixup_stack_type(&mut effect.outputs, union_names);
834 for cap in captures {
835 Self::fixup_type(cap, union_names);
836 }
837 }
838 _ => {}
839 }
840 }
841}
842
843fn parse_type_name(name: &str) -> Type {
846 match name {
847 "Int" => Type::Int,
848 "Float" => Type::Float,
849 "Bool" => Type::Bool,
850 "String" => Type::String,
851 "Channel" => Type::Channel,
852 other => Type::Union(other.to_string()),
853 }
854}
855
856impl Default for Program {
857 fn default() -> Self {
858 Self::new()
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn test_validate_builtin_words() {
868 let program = Program {
869 includes: vec![],
870 unions: vec![],
871 words: vec![WordDef {
872 name: "main".to_string(),
873 effect: None,
874 body: vec![
875 Statement::IntLiteral(2),
876 Statement::IntLiteral(3),
877 Statement::WordCall {
878 name: "i.add".to_string(),
879 span: None,
880 },
881 Statement::WordCall {
882 name: "io.write-line".to_string(),
883 span: None,
884 },
885 ],
886 source: None,
887 allowed_lints: vec![],
888 }],
889 };
890
891 assert!(program.validate_word_calls().is_ok());
893 }
894
895 #[test]
896 fn test_validate_user_defined_words() {
897 let program = Program {
898 includes: vec![],
899 unions: vec![],
900 words: vec![
901 WordDef {
902 name: "helper".to_string(),
903 effect: None,
904 body: vec![Statement::IntLiteral(42)],
905 source: None,
906 allowed_lints: vec![],
907 },
908 WordDef {
909 name: "main".to_string(),
910 effect: None,
911 body: vec![Statement::WordCall {
912 name: "helper".to_string(),
913 span: None,
914 }],
915 source: None,
916 allowed_lints: vec![],
917 },
918 ],
919 };
920
921 assert!(program.validate_word_calls().is_ok());
923 }
924
925 #[test]
926 fn test_validate_undefined_word() {
927 let program = Program {
928 includes: vec![],
929 unions: vec![],
930 words: vec![WordDef {
931 name: "main".to_string(),
932 effect: None,
933 body: vec![Statement::WordCall {
934 name: "undefined_word".to_string(),
935 span: None,
936 }],
937 source: None,
938 allowed_lints: vec![],
939 }],
940 };
941
942 let result = program.validate_word_calls();
944 assert!(result.is_err());
945 let error = result.unwrap_err();
946 assert!(error.contains("undefined_word"));
947 assert!(error.contains("main"));
948 }
949
950 #[test]
951 fn test_validate_misspelled_builtin() {
952 let program = Program {
953 includes: vec![],
954 unions: vec![],
955 words: vec![WordDef {
956 name: "main".to_string(),
957 effect: None,
958 body: vec![Statement::WordCall {
959 name: "wrte_line".to_string(),
960 span: None,
961 }], source: None,
963 allowed_lints: vec![],
964 }],
965 };
966
967 let result = program.validate_word_calls();
969 assert!(result.is_err());
970 let error = result.unwrap_err();
971 assert!(error.contains("wrte_line"));
972 assert!(error.contains("misspell"));
973 }
974
975 #[test]
976 fn test_generate_constructors() {
977 let mut program = Program {
978 includes: vec![],
979 unions: vec![UnionDef {
980 name: "Message".to_string(),
981 variants: vec![
982 UnionVariant {
983 name: "Get".to_string(),
984 fields: vec![UnionField {
985 name: "response-chan".to_string(),
986 type_name: "Int".to_string(),
987 }],
988 source: None,
989 },
990 UnionVariant {
991 name: "Put".to_string(),
992 fields: vec![
993 UnionField {
994 name: "value".to_string(),
995 type_name: "String".to_string(),
996 },
997 UnionField {
998 name: "response-chan".to_string(),
999 type_name: "Int".to_string(),
1000 },
1001 ],
1002 source: None,
1003 },
1004 ],
1005 source: None,
1006 }],
1007 words: vec![],
1008 };
1009
1010 program.generate_constructors().unwrap();
1012
1013 assert_eq!(program.words.len(), 7);
1017
1018 let make_get = program
1020 .find_word("Make-Get")
1021 .expect("Make-Get should exist");
1022 assert_eq!(make_get.name, "Make-Get");
1023 assert!(make_get.effect.is_some());
1024 let effect = make_get.effect.as_ref().unwrap();
1025 assert_eq!(
1028 format!("{:?}", effect.outputs),
1029 "Cons { rest: RowVar(\"a\"), top: Union(\"Message\") }"
1030 );
1031
1032 let make_put = program
1034 .find_word("Make-Put")
1035 .expect("Make-Put should exist");
1036 assert_eq!(make_put.name, "Make-Put");
1037 assert!(make_put.effect.is_some());
1038
1039 assert_eq!(make_get.body.len(), 2);
1042 match &make_get.body[0] {
1043 Statement::Symbol(s) if s == "Get" => {}
1044 other => panic!("Expected Symbol(\"Get\") for variant tag, got {:?}", other),
1045 }
1046 match &make_get.body[1] {
1047 Statement::WordCall { name, span: None } if name == "variant.make-1" => {}
1048 _ => panic!("Expected WordCall(variant.make-1)"),
1049 }
1050
1051 assert_eq!(make_put.body.len(), 2);
1053 match &make_put.body[0] {
1054 Statement::Symbol(s) if s == "Put" => {}
1055 other => panic!("Expected Symbol(\"Put\") for variant tag, got {:?}", other),
1056 }
1057 match &make_put.body[1] {
1058 Statement::WordCall { name, span: None } if name == "variant.make-2" => {}
1059 _ => panic!("Expected WordCall(variant.make-2)"),
1060 }
1061
1062 let is_get = program.find_word("is-Get?").expect("is-Get? should exist");
1064 assert_eq!(is_get.name, "is-Get?");
1065 assert!(is_get.effect.is_some());
1066 let effect = is_get.effect.as_ref().unwrap();
1067 assert_eq!(
1070 format!("{:?}", effect.outputs),
1071 "Cons { rest: RowVar(\"a\"), top: Bool }"
1072 );
1073
1074 let get_chan = program
1076 .find_word("Get-response-chan")
1077 .expect("Get-response-chan should exist");
1078 assert_eq!(get_chan.name, "Get-response-chan");
1079 assert!(get_chan.effect.is_some());
1080 let effect = get_chan.effect.as_ref().unwrap();
1081 assert_eq!(
1084 format!("{:?}", effect.outputs),
1085 "Cons { rest: RowVar(\"a\"), top: Int }"
1086 );
1087 }
1088}