1use crate::types::{Effect, StackType, Type};
6
7use super::{Program, Statement, WordDef};
8
9impl Program {
10 pub fn new() -> Self {
11 Program {
12 includes: Vec::new(),
13 unions: Vec::new(),
14 words: Vec::new(),
15 }
16 }
17
18 pub fn find_word(&self, name: &str) -> Option<&WordDef> {
19 self.words.iter().find(|w| w.name == name)
20 }
21
22 pub fn validate_word_calls(&self) -> Result<(), String> {
24 self.validate_word_calls_with_externals(&[])
25 }
26
27 pub fn validate_word_calls_with_externals(
32 &self,
33 external_words: &[&str],
34 ) -> Result<(), String> {
35 let builtins = [
38 "io.write",
40 "io.write-line",
41 "io.read-line",
42 "io.read-n",
43 "int->string",
44 "symbol->string",
45 "string->symbol",
46 "args.count",
48 "args.at",
49 "file.slurp",
51 "file.exists?",
52 "file.for-each-line",
53 "file.spit",
54 "file.append",
55 "file.delete",
56 "file.size",
57 "dir.exists?",
59 "dir.make",
60 "dir.delete",
61 "dir.list",
62 "string.concat",
64 "string.length",
65 "string.byte-length",
66 "string.char-at",
67 "string.substring",
68 "char->string",
69 "string.find",
70 "string.split",
71 "string.contains",
72 "string.starts-with",
73 "string.empty?",
74 "string.trim",
75 "string.chomp",
76 "string.to-upper",
77 "string.to-lower",
78 "string.equal?",
79 "string.join",
80 "string.json-escape",
81 "string->int",
82 "symbol.=",
84 "encoding.base64-encode",
86 "encoding.base64-decode",
87 "encoding.base64url-encode",
88 "encoding.base64url-decode",
89 "encoding.hex-encode",
90 "encoding.hex-decode",
91 "crypto.sha256",
93 "crypto.hmac-sha256",
94 "crypto.constant-time-eq",
95 "crypto.random-bytes",
96 "crypto.random-int",
97 "crypto.uuid4",
98 "crypto.aes-gcm-encrypt",
99 "crypto.aes-gcm-decrypt",
100 "crypto.pbkdf2-sha256",
101 "crypto.ed25519-keypair",
102 "crypto.ed25519-sign",
103 "crypto.ed25519-verify",
104 "net.http.get",
106 "net.http.post",
107 "net.http.put",
108 "net.http.delete",
109 "list.make",
111 "list.push",
112 "list.get",
113 "list.set",
114 "list.map",
115 "list.filter",
116 "list.fold",
117 "list.each",
118 "list.length",
119 "list.empty?",
120 "list.reverse",
121 "list.first",
122 "list.last",
123 "map.make",
125 "map.get",
126 "map.set",
127 "map.has?",
128 "map.remove",
129 "map.keys",
130 "map.values",
131 "map.size",
132 "map.empty?",
133 "map.each",
134 "map.fold",
135 "variant.field-count",
137 "variant.tag",
138 "variant.field-at",
139 "variant.append",
140 "variant.first",
141 "variant.last",
142 "variant.init",
143 "variant.make-0",
144 "variant.make-1",
145 "variant.make-2",
146 "variant.make-3",
147 "variant.make-4",
148 "wrap-0",
150 "wrap-1",
151 "wrap-2",
152 "wrap-3",
153 "wrap-4",
154 "i.add",
156 "i.subtract",
157 "i.multiply",
158 "i.divide",
159 "i.modulo",
160 "i.pow",
161 "i.+",
163 "i.-",
164 "i.*",
165 "i./",
166 "i.%",
167 "i.=",
169 "i.<",
170 "i.>",
171 "i.<=",
172 "i.>=",
173 "i.<>",
174 "i.eq",
176 "i.lt",
177 "i.gt",
178 "i.lte",
179 "i.gte",
180 "i.neq",
181 "dup",
183 "drop",
184 "swap",
185 "over",
186 "rot",
187 "nip",
188 "tuck",
189 "2dup",
190 "3drop",
191 "pick",
192 "roll",
193 ">aux",
195 "aux>",
196 "and",
198 "or",
199 "not",
200 "band",
202 "bor",
203 "bxor",
204 "bnot",
205 "i.neg",
206 "negate",
207 "+",
209 "-",
210 "*",
211 "/",
212 "%",
213 "=",
214 "<",
215 ">",
216 "<=",
217 ">=",
218 "<>",
219 "shl",
220 "shr",
221 "popcount",
222 "clz",
223 "ctz",
224 "int-bits",
225 "chan.make",
227 "chan.send",
228 "chan.receive",
229 "chan.close",
230 "chan.yield",
231 "call",
233 "dip",
235 "keep",
236 "bi",
237 "if",
238 "strand.spawn",
239 "strand.weave",
240 "strand.resume",
241 "strand.weave-cancel",
242 "yield",
243 "cond",
244 "net.tcp.listen",
246 "net.tcp.connect",
247 "net.tcp.accept",
248 "net.tcp.local-port",
249 "net.tcp.read",
250 "net.tcp.write",
251 "net.tcp.close",
252 "fd->socket",
254 "socket->fd",
255 "net.udp.bind",
257 "net.udp.send-to",
258 "net.udp.receive-from",
259 "net.udp.close",
260 "net.dns.resolve",
262 "net.tls.client",
264 "os.getenv",
266 "os.home-dir",
267 "os.current-dir",
268 "os.path-exists",
269 "os.path-is-file",
270 "os.path-is-dir",
271 "os.path-join",
272 "os.path-parent",
273 "os.path-filename",
274 "os.exit",
275 "os.name",
276 "os.arch",
277 "signal.trap",
279 "signal.received?",
280 "signal.pending?",
281 "signal.default",
282 "signal.ignore",
283 "signal.clear",
284 "signal.SIGINT",
285 "signal.SIGTERM",
286 "signal.SIGHUP",
287 "signal.SIGPIPE",
288 "signal.SIGUSR1",
289 "signal.SIGUSR2",
290 "signal.SIGCHLD",
291 "signal.SIGALRM",
292 "signal.SIGCONT",
293 "terminal.raw-mode",
295 "terminal.read-char",
296 "terminal.read-char?",
297 "terminal.width",
298 "terminal.height",
299 "terminal.flush",
300 "f.add",
302 "f.subtract",
303 "f.multiply",
304 "f.divide",
305 "f.+",
307 "f.-",
308 "f.*",
309 "f./",
310 "f.=",
312 "f.<",
313 "f.>",
314 "f.<=",
315 "f.>=",
316 "f.<>",
317 "f.eq",
319 "f.lt",
320 "f.gt",
321 "f.lte",
322 "f.gte",
323 "f.neq",
324 "f.sqrt",
326 "f.cbrt",
327 "f.pow",
328 "f.exp",
330 "f.ln",
331 "f.log10",
332 "f.log2",
333 "f.sin",
335 "f.cos",
336 "f.tan",
337 "f.asin",
338 "f.acos",
339 "f.atan",
340 "f.atan2",
341 "f.floor",
343 "f.ceil",
344 "f.round",
345 "f.trunc",
346 "f.pi",
348 "f.e",
349 "f.tau",
350 "int->float",
352 "float->int",
353 "float->string",
354 "string->float",
355 "int.to-bytes-i32-be",
357 "float.to-bytes-f32-be",
358 "test.init",
360 "test.set-name",
361 "test.finish",
362 "test.has-failures",
363 "test.assert",
364 "test.assert-not",
365 "test.assert-eq",
366 "test.assert-eq-str",
367 "test.fail",
368 "test.pass-count",
369 "test.fail-count",
370 "time.now",
372 "time.nanos",
373 "time.sleep-ms",
374 "son.dump",
376 "son.dump-pretty",
377 "stack.dump",
379 "regex.match?",
381 "regex.find",
382 "regex.find-all",
383 "regex.replace",
384 "regex.replace-all",
385 "regex.captures",
386 "regex.split",
387 "regex.valid?",
388 "compress.gzip",
390 "compress.gzip-level",
391 "compress.gunzip",
392 "compress.zstd",
393 "compress.zstd-level",
394 "compress.unzstd",
395 ];
396
397 for word in &self.words {
398 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
399 }
400
401 Ok(())
402 }
403
404 fn validate_statements(
406 &self,
407 statements: &[Statement],
408 word_name: &str,
409 builtins: &[&str],
410 external_words: &[&str],
411 ) -> Result<(), String> {
412 for statement in statements {
413 match statement {
414 Statement::WordCall { name, .. } => {
415 if builtins.contains(&name.as_str()) {
417 continue;
418 }
419 if self.find_word(name).is_some() {
421 continue;
422 }
423 if external_words.contains(&name.as_str()) {
425 continue;
426 }
427 if let Some(replacement) = v7_renamed_to(name) {
431 return Err(format!(
432 "'{}' was renamed to '{}' in v7.0 (called in word '{}'). \
433 See docs/MIGRATION_7_0.md.",
434 name, replacement, word_name
435 ));
436 }
437 return Err(format!(
439 "Undefined word '{}' called in word '{}'. \
440 Did you forget to define it or misspell a built-in?",
441 name, word_name
442 ));
443 }
444 Statement::If {
445 then_branch,
446 else_branch,
447 span: _,
448 } => {
449 self.validate_statements(then_branch, word_name, builtins, external_words)?;
451 if let Some(eb) = else_branch {
452 self.validate_statements(eb, word_name, builtins, external_words)?;
453 }
454 }
455 Statement::Quotation { body, .. } => {
456 self.validate_statements(body, word_name, builtins, external_words)?;
458 }
459 Statement::Match { arms, span: _ } => {
460 for arm in arms {
462 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
463 }
464 }
465 _ => {} }
467 }
468 Ok(())
469 }
470
471 pub const MAX_VARIANT_FIELDS: usize = 12;
475
476 pub fn generate_constructors(&mut self) -> Result<(), String> {
489 let mut new_words = Vec::new();
490
491 for union_def in &self.unions {
492 for variant in &union_def.variants {
493 let field_count = variant.fields.len();
494
495 if field_count > Self::MAX_VARIANT_FIELDS {
497 return Err(format!(
498 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
499 Consider grouping fields into nested union types.",
500 variant.name,
501 union_def.name,
502 field_count,
503 Self::MAX_VARIANT_FIELDS
504 ));
505 }
506
507 let constructor_name = format!("Make-{}", variant.name);
509 let mut input_stack = StackType::RowVar("a".to_string());
510 for field in &variant.fields {
511 let field_type = parse_type_name(&field.type_name);
512 input_stack = input_stack.push(field_type);
513 }
514 let output_stack =
515 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
516 let effect = Effect::new(input_stack, output_stack);
517 let body = vec![
518 Statement::Symbol(variant.name.clone()),
519 Statement::WordCall {
520 name: format!("variant.make-{}", field_count),
521 span: None,
522 },
523 ];
524 new_words.push(WordDef {
525 name: constructor_name,
526 effect: Some(effect),
527 body,
528 source: variant.source.clone(),
529 allowed_lints: vec![],
530 });
531
532 let predicate_name = format!("is-{}?", variant.name);
536 let predicate_input =
537 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
538 let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
539 let predicate_effect = Effect::new(predicate_input, predicate_output);
540 let predicate_body = vec![
541 Statement::WordCall {
542 name: "variant.tag".to_string(),
543 span: None,
544 },
545 Statement::Symbol(variant.name.clone()),
546 Statement::WordCall {
547 name: "symbol.=".to_string(),
548 span: None,
549 },
550 ];
551 new_words.push(WordDef {
552 name: predicate_name,
553 effect: Some(predicate_effect),
554 body: predicate_body,
555 source: variant.source.clone(),
556 allowed_lints: vec![],
557 });
558
559 for (index, field) in variant.fields.iter().enumerate() {
563 let accessor_name = format!("{}-{}", variant.name, field.name);
564 let field_type = parse_type_name(&field.type_name);
565 let accessor_input = StackType::RowVar("a".to_string())
566 .push(Type::Union(union_def.name.clone()));
567 let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
568 let accessor_effect = Effect::new(accessor_input, accessor_output);
569 let accessor_body = vec![
570 Statement::IntLiteral(index as i64),
571 Statement::WordCall {
572 name: "variant.field-at".to_string(),
573 span: None,
574 },
575 ];
576 new_words.push(WordDef {
577 name: accessor_name,
578 effect: Some(accessor_effect),
579 body: accessor_body,
580 source: variant.source.clone(), allowed_lints: vec![],
582 });
583 }
584 }
585 }
586
587 self.words.extend(new_words);
588 Ok(())
589 }
590
591 pub fn fixup_union_types(&mut self) {
600 let union_names: std::collections::HashSet<String> =
602 self.unions.iter().map(|u| u.name.clone()).collect();
603
604 for word in &mut self.words {
606 if let Some(ref mut effect) = word.effect {
607 Self::fixup_stack_type(&mut effect.inputs, &union_names);
608 Self::fixup_stack_type(&mut effect.outputs, &union_names);
609 }
610 }
611 }
612
613 fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
615 match stack {
616 StackType::Empty | StackType::RowVar(_) => {}
617 StackType::Cons { rest, top } => {
618 Self::fixup_type(top, union_names);
619 Self::fixup_stack_type(rest, union_names);
620 }
621 }
622 }
623
624 fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
626 match ty {
627 Type::Var(name) if union_names.contains(name) => {
628 *ty = Type::Union(name.clone());
629 }
630 Type::Quotation(effect) => {
631 Self::fixup_stack_type(&mut effect.inputs, union_names);
632 Self::fixup_stack_type(&mut effect.outputs, union_names);
633 }
634 Type::Closure { effect, captures } => {
635 Self::fixup_stack_type(&mut effect.inputs, union_names);
636 Self::fixup_stack_type(&mut effect.outputs, union_names);
637 for cap in captures {
638 Self::fixup_type(cap, union_names);
639 }
640 }
641 _ => {}
642 }
643 }
644}
645
646fn parse_type_name(name: &str) -> Type {
649 match name {
650 "Int" => Type::Int,
651 "Float" => Type::Float,
652 "Bool" => Type::Bool,
653 "String" => Type::String,
654 "Channel" => Type::Channel,
655 "Socket" => Type::Socket,
656 other => Type::Union(other.to_string()),
657 }
658}
659
660fn v7_renamed_to(name: &str) -> Option<&'static str> {
665 Some(match name {
666 "tcp.listen" => "net.tcp.listen",
667 "tcp.accept" => "net.tcp.accept",
668 "tcp.read" => "net.tcp.read",
669 "tcp.write" => "net.tcp.write",
670 "tcp.close" => "net.tcp.close",
671 "udp.bind" => "net.udp.bind",
672 "udp.send-to" => "net.udp.send-to",
673 "udp.receive-from" => "net.udp.receive-from",
674 "udp.close" => "net.udp.close",
675 "http.get" => "net.http.get",
676 "http.post" => "net.http.post",
677 "http.put" => "net.http.put",
678 "http.delete" => "net.http.delete",
679 "mod" => "i.modulo",
682 _ => return None,
683 })
684}
685
686impl Default for Program {
687 fn default() -> Self {
688 Self::new()
689 }
690}