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