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.read",
249 "net.tcp.write",
250 "net.tcp.close",
251 "fd->socket",
253 "socket->fd",
254 "net.udp.bind",
256 "net.udp.send-to",
257 "net.udp.receive-from",
258 "net.udp.close",
259 "net.dns.resolve",
261 "net.tls.client",
263 "os.getenv",
265 "os.home-dir",
266 "os.current-dir",
267 "os.path-exists",
268 "os.path-is-file",
269 "os.path-is-dir",
270 "os.path-join",
271 "os.path-parent",
272 "os.path-filename",
273 "os.exit",
274 "os.name",
275 "os.arch",
276 "signal.trap",
278 "signal.received?",
279 "signal.pending?",
280 "signal.default",
281 "signal.ignore",
282 "signal.clear",
283 "signal.SIGINT",
284 "signal.SIGTERM",
285 "signal.SIGHUP",
286 "signal.SIGPIPE",
287 "signal.SIGUSR1",
288 "signal.SIGUSR2",
289 "signal.SIGCHLD",
290 "signal.SIGALRM",
291 "signal.SIGCONT",
292 "terminal.raw-mode",
294 "terminal.read-char",
295 "terminal.read-char?",
296 "terminal.width",
297 "terminal.height",
298 "terminal.flush",
299 "f.add",
301 "f.subtract",
302 "f.multiply",
303 "f.divide",
304 "f.+",
306 "f.-",
307 "f.*",
308 "f./",
309 "f.=",
311 "f.<",
312 "f.>",
313 "f.<=",
314 "f.>=",
315 "f.<>",
316 "f.eq",
318 "f.lt",
319 "f.gt",
320 "f.lte",
321 "f.gte",
322 "f.neq",
323 "f.sqrt",
325 "f.cbrt",
326 "f.pow",
327 "f.exp",
329 "f.ln",
330 "f.log10",
331 "f.log2",
332 "f.sin",
334 "f.cos",
335 "f.tan",
336 "f.asin",
337 "f.acos",
338 "f.atan",
339 "f.atan2",
340 "f.floor",
342 "f.ceil",
343 "f.round",
344 "f.trunc",
345 "f.pi",
347 "f.e",
348 "f.tau",
349 "int->float",
351 "float->int",
352 "float->string",
353 "string->float",
354 "int.to-bytes-i32-be",
356 "float.to-bytes-f32-be",
357 "test.init",
359 "test.set-name",
360 "test.finish",
361 "test.has-failures",
362 "test.assert",
363 "test.assert-not",
364 "test.assert-eq",
365 "test.assert-eq-str",
366 "test.fail",
367 "test.pass-count",
368 "test.fail-count",
369 "time.now",
371 "time.nanos",
372 "time.sleep-ms",
373 "son.dump",
375 "son.dump-pretty",
376 "stack.dump",
378 "regex.match?",
380 "regex.find",
381 "regex.find-all",
382 "regex.replace",
383 "regex.replace-all",
384 "regex.captures",
385 "regex.split",
386 "regex.valid?",
387 "compress.gzip",
389 "compress.gzip-level",
390 "compress.gunzip",
391 "compress.zstd",
392 "compress.zstd-level",
393 "compress.unzstd",
394 ];
395
396 for word in &self.words {
397 self.validate_statements(&word.body, &word.name, &builtins, external_words)?;
398 }
399
400 Ok(())
401 }
402
403 fn validate_statements(
405 &self,
406 statements: &[Statement],
407 word_name: &str,
408 builtins: &[&str],
409 external_words: &[&str],
410 ) -> Result<(), String> {
411 for statement in statements {
412 match statement {
413 Statement::WordCall { name, .. } => {
414 if builtins.contains(&name.as_str()) {
416 continue;
417 }
418 if self.find_word(name).is_some() {
420 continue;
421 }
422 if external_words.contains(&name.as_str()) {
424 continue;
425 }
426 if let Some(replacement) = v7_renamed_to(name) {
430 return Err(format!(
431 "'{}' was renamed to '{}' in v7.0 (called in word '{}'). \
432 See docs/MIGRATION_7_0.md.",
433 name, replacement, word_name
434 ));
435 }
436 return Err(format!(
438 "Undefined word '{}' called in word '{}'. \
439 Did you forget to define it or misspell a built-in?",
440 name, word_name
441 ));
442 }
443 Statement::If {
444 then_branch,
445 else_branch,
446 span: _,
447 } => {
448 self.validate_statements(then_branch, word_name, builtins, external_words)?;
450 if let Some(eb) = else_branch {
451 self.validate_statements(eb, word_name, builtins, external_words)?;
452 }
453 }
454 Statement::Quotation { body, .. } => {
455 self.validate_statements(body, word_name, builtins, external_words)?;
457 }
458 Statement::Match { arms, span: _ } => {
459 for arm in arms {
461 self.validate_statements(&arm.body, word_name, builtins, external_words)?;
462 }
463 }
464 _ => {} }
466 }
467 Ok(())
468 }
469
470 pub const MAX_VARIANT_FIELDS: usize = 12;
474
475 pub fn generate_constructors(&mut self) -> Result<(), String> {
488 let mut new_words = Vec::new();
489
490 for union_def in &self.unions {
491 for variant in &union_def.variants {
492 let field_count = variant.fields.len();
493
494 if field_count > Self::MAX_VARIANT_FIELDS {
496 return Err(format!(
497 "Variant '{}' in union '{}' has {} fields, but the maximum is {}. \
498 Consider grouping fields into nested union types.",
499 variant.name,
500 union_def.name,
501 field_count,
502 Self::MAX_VARIANT_FIELDS
503 ));
504 }
505
506 let constructor_name = format!("Make-{}", variant.name);
508 let mut input_stack = StackType::RowVar("a".to_string());
509 for field in &variant.fields {
510 let field_type = parse_type_name(&field.type_name);
511 input_stack = input_stack.push(field_type);
512 }
513 let output_stack =
514 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
515 let effect = Effect::new(input_stack, output_stack);
516 let body = vec![
517 Statement::Symbol(variant.name.clone()),
518 Statement::WordCall {
519 name: format!("variant.make-{}", field_count),
520 span: None,
521 },
522 ];
523 new_words.push(WordDef {
524 name: constructor_name,
525 effect: Some(effect),
526 body,
527 source: variant.source.clone(),
528 allowed_lints: vec![],
529 });
530
531 let predicate_name = format!("is-{}?", variant.name);
535 let predicate_input =
536 StackType::RowVar("a".to_string()).push(Type::Union(union_def.name.clone()));
537 let predicate_output = StackType::RowVar("a".to_string()).push(Type::Bool);
538 let predicate_effect = Effect::new(predicate_input, predicate_output);
539 let predicate_body = vec![
540 Statement::WordCall {
541 name: "variant.tag".to_string(),
542 span: None,
543 },
544 Statement::Symbol(variant.name.clone()),
545 Statement::WordCall {
546 name: "symbol.=".to_string(),
547 span: None,
548 },
549 ];
550 new_words.push(WordDef {
551 name: predicate_name,
552 effect: Some(predicate_effect),
553 body: predicate_body,
554 source: variant.source.clone(),
555 allowed_lints: vec![],
556 });
557
558 for (index, field) in variant.fields.iter().enumerate() {
562 let accessor_name = format!("{}-{}", variant.name, field.name);
563 let field_type = parse_type_name(&field.type_name);
564 let accessor_input = StackType::RowVar("a".to_string())
565 .push(Type::Union(union_def.name.clone()));
566 let accessor_output = StackType::RowVar("a".to_string()).push(field_type);
567 let accessor_effect = Effect::new(accessor_input, accessor_output);
568 let accessor_body = vec![
569 Statement::IntLiteral(index as i64),
570 Statement::WordCall {
571 name: "variant.field-at".to_string(),
572 span: None,
573 },
574 ];
575 new_words.push(WordDef {
576 name: accessor_name,
577 effect: Some(accessor_effect),
578 body: accessor_body,
579 source: variant.source.clone(), allowed_lints: vec![],
581 });
582 }
583 }
584 }
585
586 self.words.extend(new_words);
587 Ok(())
588 }
589
590 pub fn fixup_union_types(&mut self) {
599 let union_names: std::collections::HashSet<String> =
601 self.unions.iter().map(|u| u.name.clone()).collect();
602
603 for word in &mut self.words {
605 if let Some(ref mut effect) = word.effect {
606 Self::fixup_stack_type(&mut effect.inputs, &union_names);
607 Self::fixup_stack_type(&mut effect.outputs, &union_names);
608 }
609 }
610 }
611
612 fn fixup_stack_type(stack: &mut StackType, union_names: &std::collections::HashSet<String>) {
614 match stack {
615 StackType::Empty | StackType::RowVar(_) => {}
616 StackType::Cons { rest, top } => {
617 Self::fixup_type(top, union_names);
618 Self::fixup_stack_type(rest, union_names);
619 }
620 }
621 }
622
623 fn fixup_type(ty: &mut Type, union_names: &std::collections::HashSet<String>) {
625 match ty {
626 Type::Var(name) if union_names.contains(name) => {
627 *ty = Type::Union(name.clone());
628 }
629 Type::Quotation(effect) => {
630 Self::fixup_stack_type(&mut effect.inputs, union_names);
631 Self::fixup_stack_type(&mut effect.outputs, union_names);
632 }
633 Type::Closure { effect, captures } => {
634 Self::fixup_stack_type(&mut effect.inputs, union_names);
635 Self::fixup_stack_type(&mut effect.outputs, union_names);
636 for cap in captures {
637 Self::fixup_type(cap, union_names);
638 }
639 }
640 _ => {}
641 }
642 }
643}
644
645fn parse_type_name(name: &str) -> Type {
648 match name {
649 "Int" => Type::Int,
650 "Float" => Type::Float,
651 "Bool" => Type::Bool,
652 "String" => Type::String,
653 "Channel" => Type::Channel,
654 "Socket" => Type::Socket,
655 other => Type::Union(other.to_string()),
656 }
657}
658
659fn v7_renamed_to(name: &str) -> Option<&'static str> {
664 Some(match name {
665 "tcp.listen" => "net.tcp.listen",
666 "tcp.accept" => "net.tcp.accept",
667 "tcp.read" => "net.tcp.read",
668 "tcp.write" => "net.tcp.write",
669 "tcp.close" => "net.tcp.close",
670 "udp.bind" => "net.udp.bind",
671 "udp.send-to" => "net.udp.send-to",
672 "udp.receive-from" => "net.udp.receive-from",
673 "udp.close" => "net.udp.close",
674 "http.get" => "net.http.get",
675 "http.post" => "net.http.post",
676 "http.put" => "net.http.put",
677 "http.delete" => "net.http.delete",
678 "mod" => "i.modulo",
681 _ => return None,
682 })
683}
684
685impl Default for Program {
686 fn default() -> Self {
687 Self::new()
688 }
689}