cas_compiler/lib.rs
1#![doc = include_str!("../README.md")]
2
3pub mod expr;
4pub mod error;
5pub mod instruction;
6pub mod item;
7pub mod sym_table;
8
9use cas_compute::{consts::all as all_consts, funcs::all as all_funcs};
10use cas_error::Error;
11use cas_parser::parser::ast::{FuncHeader, LitSym, Stmt};
12use error::{
13 OverrideBuiltinConstant,
14 OverrideBuiltinFunction,
15 UnknownVariable,
16};
17use std::collections::{HashMap, HashSet};
18use expr::compile_stmts;
19pub use instruction::{Instruction, InstructionKind};
20use item::{FuncDecl, Item, Symbol, SymbolDecl};
21use std::ops::Range;
22use sym_table::{Scope, SymbolTable};
23
24/// A label that can be used to reference a specific instruction in the bytecode.
25///
26/// The internal value is simply a unique ID that is resolved to the actual instruction during
27/// execution.
28#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
29pub struct Label(usize);
30
31/// The state of the compiler.
32#[derive(Clone, Debug, Default)]
33pub struct CompilerState {
34 /// The label pointing to the start of the current loop.
35 pub loop_start: Option<Label>,
36
37 /// The label pointing to the end of the current loop.
38 pub loop_end: Option<Label>,
39
40 /// Whether the current statement is the last statement in a block or the program, indicating
41 /// that its return value is the return value of the block / program.
42 pub last_stmt: bool,
43
44 /// Whether the expression being compiled is a top-level assignment expression, indicating that
45 /// its return value is not used.
46 ///
47 /// This is used to determine whether to use the [`InstructionKind::StoreVar`] or
48 /// [`InstructionKind::AssignVar`] instruction.
49 ///
50 /// For example, in the following code:
51 ///
52 /// ```calcscript
53 /// x = y = 2
54 /// ```
55 ///
56 /// The `x = ...` expression is a top-level assignment expression, so its return value is not
57 /// used within the same statement. However, the `y = 2` expression is not a top-level
58 /// assignment, as its return value is then passed to the `x = ...` expression. In this case,
59 /// the compiler will generate a [`InstructionKind::AssignVar`] instruction for `x` and a
60 /// [`InstructionKind::StoreVar`] instruction for `y`.
61 // TODO: this is not implemented yet
62 pub top_level_assign: bool,
63}
64
65/// A chunk containing a function definition.
66#[derive(Clone, Debug, Default)]
67pub struct Chunk {
68 /// The instructions in this chunk.
69 pub instructions: Vec<Instruction>,
70
71 /// The number of arguments the function takes.
72 pub arity: usize,
73}
74
75impl Chunk {
76 /// Creates a new chunk with the given arity.
77 pub fn new(arity: usize) -> Self {
78 Self {
79 instructions: Vec::new(),
80 arity,
81 }
82 }
83}
84
85/// Value returned by [`Compiler::new_chunk`].
86pub struct NewChunk {
87 /// The unique identifier for the function.
88 pub id: usize,
89
90 /// The index of the new chunk.
91 pub chunk: usize,
92
93 /// The symbols captured by the function from parent scopes.
94 pub captures: HashSet<usize>,
95}
96
97/// Returns the corresponding error if a symbol matches the name of a builtin symbol or function.
98fn check_override_builtin(symbol: &LitSym) -> Result<(), Error> {
99 if all_consts().contains(&*symbol.name) {
100 return Err(Error::new(vec![symbol.span.clone()], OverrideBuiltinConstant {
101 name: symbol.name.to_string(),
102 }));
103 }
104 if all_funcs().contains_key(&*symbol.name) {
105 return Err(Error::new(vec![symbol.span.clone()], OverrideBuiltinFunction {
106 name: symbol.name.to_string(),
107 }));
108 }
109 Ok(())
110}
111
112/// Returns the builtin constant or function with the given name, if it exists.
113fn resolve_builtin(symbol: &LitSym) -> Option<Symbol> {
114 all_consts()
115 .get(&*symbol.name)
116 .map(|name| Symbol::Builtin(name))
117 .or_else(|| {
118 all_funcs()
119 .get(&*symbol.name)
120 .map(|func| Symbol::Builtin(func.name()))
121 })
122}
123
124/// A compiler that provides tools to generate bytecode instructions for `cas-rs`'s virtual machine
125/// (see [`Vm`]).
126///
127/// **Note: If you're looking to run a CalcScript program, you should use the [`Vm`] struct found
128/// in `cas-vm` instead.**
129///
130/// This is the main entry point to the compiler. The compiler translates a CalcScript AST
131/// (produced by [`cas_parser`]) into a series of bytecode [`Chunk`]s, which can then be executed
132/// by `cas-rs`'s [`Vm`]. It also is mostly responsible for managing CalcScript's semantics through
133/// lexical scoping and symbol resolution, value stack layout, and generation of chunks. These
134/// details are described later in this documentation, but are not important if you're just looking
135/// to run a program.
136///
137/// To compile a complete program, it is recommended that you use [`Compiler::compile_program`],
138/// which is the easiest way to ensure the resulting bytecode is valid. However, there are also a
139/// number of other methods that can be used to manually compile CalcScript. There is one important
140/// rule to keep in mind when taking this approach (which would otherwise be handled by the
141/// compiler), which requires a quick explanation of [`Vm`]'s value stack.
142///
143/// During execution, [`Vm`] uses a value stack to keep track of values generated by and used
144/// around the bytecode. The compiler must ensure that the instructions it generates manipulates
145/// the value stack's semantics correctly.
146///
147/// The most important rule is that **the value stack must have exactly one value on it when the
148/// program finishes executing**. This is the value that is returned by the program (printed when
149/// using the `cas-rs` REPL). When manually compiling a program, you must ensure that each
150/// statement's instructions leave no value on the stack when the statement completes, except for
151/// the last statement in a block or chunk.
152///
153/// (Note that failing to uphold this rule will never result in undefined behavior; it will most
154/// likely either panic or result in an error during execution.)
155///
156/// Most CalcScript programs consist of a sequence of statements, for example:
157///
158/// ```calcscript
159/// x = 3
160/// y = 4
161/// z = hypot(x, y)
162/// ```
163///
164/// In this case, the compiler generates these instructions:
165///
166/// ```rust
167/// use cas_compiler::Compiler;
168/// use cas_parser::parser::Parser;
169///
170/// let ast = Parser::new("x = 3
171/// y = 4
172/// z = hypot(x, y)").try_parse_full_many().unwrap();
173///
174/// let compiler = Compiler::compile_program(ast).unwrap();
175///
176/// use cas_compiler::{item::Symbol, Instruction, InstructionKind::*};
177/// assert_eq!(compiler.chunks[0].instructions, vec![
178/// // x = 3
179/// Instruction { kind: LoadConst(3.into()), spans: vec![] },
180/// Instruction { kind: StoreVar(0), spans: vec![] },
181/// Instruction { kind: Drop, spans: vec![] },
182///
183/// // x = 4
184/// Instruction { kind: LoadConst(4.into()), spans: vec![] },
185/// Instruction { kind: StoreVar(1), spans: vec![] },
186/// Instruction { kind: Drop, spans: vec![] },
187///
188/// // z = hypot(x, y)
189/// Instruction { kind: LoadVar(Symbol::User(0)), spans: vec![22..23] },
190/// Instruction { kind: LoadVar(Symbol::User(1)), spans: vec![25..26] },
191/// Instruction { kind: LoadVar(Symbol::Builtin("hypot")), spans: vec![16..21] },
192/// Instruction { kind: Call(2), spans: vec![16..22, 26..27, 22..23, 25..26] },
193/// Instruction { kind: StoreVar(2), spans: vec![] }
194/// ]);
195/// ```
196///
197/// Notice that each statement is terminated by a [`InstructionKind::Drop`] instruction, except for
198/// the last one. For example, the first statement, `x = 3`, loads the constant `3` onto the stack.
199/// The [`InstructionKind::StoreVar`] instruction stores the value into the variable `x` (0), but
200/// does not remove it from the stack. The `Drop` instruction then removes the value from the
201/// stack, leaving the stack empty. (It is more optimal to use [`InstructionKind::AssignVar`] in
202/// this case, but the compiler does not implement this behavior yet.)
203///
204/// The final statement, `z = hypot(x, y)`, stores the computed value into the variable `z` (2),
205/// but does not drop the value from the stack, making it the final value on the stack, and thus
206/// the return value of the program.
207///
208/// You need to be mindful of this behavior when manually compiling programs.
209///
210/// [`Vm`]: https://docs.rs/cas-vm/latest/cas_vm/vm/struct.Vm.html.
211#[derive(Clone, Debug)]
212pub struct Compiler {
213 /// The bytecode chunks generated by the compiler.
214 ///
215 /// The entire program is represented as multiple chunks of bytecode, where each chunk
216 /// represents a function body. The first chunk represents the implicit "main" function.
217 pub chunks: Vec<Chunk>,
218
219 /// Labels generated by the compiler, mapped to the index of the instruction they reference.
220 ///
221 /// When created, labels aren't associated with any instruction. Before the bytecode is
222 /// executed, the compiler will resolve these labels to the actual instruction indices.
223 pub labels: HashMap<Label, Option<(usize, usize)>>,
224
225 /// A symbol table that maps identifiers to information about the values they represent.
226 ///
227 /// This is used to store information about variables and functions that are defined in the
228 /// program.
229 pub sym_table: SymbolTable,
230
231 /// Index of the current chunk.
232 ///
233 /// This value is manually updated by the compiler.
234 chunk: usize,
235
236 /// Next unique identifier for a symbol or function.
237 next_item_id: usize,
238
239 /// Holds state for the current loop.
240 state: CompilerState,
241}
242
243impl Default for Compiler {
244 fn default() -> Self {
245 Self {
246 chunks: vec![Chunk::default()], // add main chunk
247 labels: Default::default(),
248 sym_table: Default::default(),
249 chunk: 0,
250 next_item_id: 0,
251 state: Default::default(),
252 }
253 }
254}
255
256impl Compiler {
257 /// Creates a new compiler.
258 pub fn new() -> Self {
259 Self::default()
260 }
261
262 /// Compiles the given type into a sequence of [`Instruction`]s.
263 pub fn compile<T: Compile>(expr: T) -> Result<Self, Error> {
264 let mut compiler = Self::new();
265 expr.compile(&mut compiler)?;
266 Ok(compiler)
267 }
268
269 /// Compiles multiple statements into a sequence of [`Instruction`]s.
270 pub fn compile_program(stmts: Vec<Stmt>) -> Result<Self, Error> {
271 let mut compiler = Self::new();
272 compile_stmts(&stmts, &mut compiler)?;
273 Ok(compiler)
274 }
275
276 /// Creates a new compilation scope with the given modified state. Compilation that occurs in
277 /// this scope will then use the modified state.
278 pub fn with_state<F, G>(&mut self, modify_state: F, compile: G) -> Result<(), Error>
279 where
280 F: FnOnce(&mut CompilerState),
281 G: FnOnce(&mut Self) -> Result<(), Error>,
282 {
283 let old_state = self.state.clone();
284 modify_state(&mut self.state);
285 compile(&mut *self)?;
286 self.state = old_state;
287 Ok(())
288 }
289
290 /// Returns an immutable reference to the current chunk.
291 pub fn chunk(&self) -> &Chunk {
292 self.chunks.get(self.chunk).unwrap()
293 }
294
295 /// Returns a mutable reference to the current chunk.
296 pub fn chunk_mut(&mut self) -> &mut Chunk {
297 self.chunks.get_mut(self.chunk).unwrap()
298 }
299
300 /// Add an item to the symbol table at the current scope.
301 ///
302 /// If the item to add matches that of a builtin item, one of the following will occur:
303 ///
304 /// - If this function is called from the global scope, an [`OverrideBuiltinConstant`] or
305 /// [`OverrideBuiltinFunction`] error is returned.
306 /// - If this function is called anywhere else, the symbol table will successfully be updated
307 /// with the new item. This item shadows the existing builtin, meaning the builtin will not
308 /// be accessible until the scope in which this item was declared, ends.
309 pub fn add_item(&mut self, symbol: &LitSym, item: Item) -> Result<(), Error> {
310 // if we are in the global scope, ensure we don't accidentally override builtin constants
311 // and functions. this is because there would be no way to access the builtin constants and
312 // functions after they are overridden
313
314 // it's ok to allow overriding in deeper scopes, as the user will still be able to access
315 // the builtin constants and functions afterward
316
317 // TODO: shadowing should probably be explicit (i.e. with `let` keyword)
318 if self.sym_table.is_global_scope() {
319 // existence of higher order functions means that a symbol could potentially override
320 // a builtin symbol or function, even if it is not explicitly declared as a function or
321 // symbol (see no_override_builtin test at bottom of file)
322 check_override_builtin(symbol)?;
323 }
324
325 // add to this final table
326 self.sym_table.insert(symbol.name.to_string(), item);
327 Ok(())
328 }
329
330 /// Creates a new scope in the symbol table. Within the provided function, all `compiler`
331 /// methods that add or mutate symbols will do so in the new scope.
332 ///
333 /// The scope is popped off the symbol table stack when the function returns. If no symbols
334 /// were added to the scope, it will not be added to the symbol table.
335 pub fn new_scope<F>(&mut self, f: F) -> Result<(), Error>
336 where F: FnOnce(&mut Compiler) -> Result<(), Error>
337 {
338 self.sym_table.enter_scope();
339 f(self)?;
340 self.sym_table.exit_scope();
341 Ok(())
342 }
343
344 /// Creates a new scope in the symbol table. Within the provided function, all `compiler`
345 /// methods that add or mutate symbols will do so in the new scope.
346 ///
347 /// The scope is popped when the function returns, and a reference to the scope is returned. It
348 /// will always be added to the symbol table, even if no symbols were added to it.
349 pub(crate) fn new_scope_get<F>(&mut self, f: F) -> Result<&Scope, Error>
350 where F: FnOnce(&mut Compiler) -> Result<(), Error>
351 {
352 self.sym_table.enter_scope();
353 f(self)?;
354 Ok(self.sym_table.exit_scope_get())
355 }
356
357 /// Creates a new chunk and a scope for compilation. Within the provided function, all
358 /// `compiler` methods that add or edit instructions will do so to the new chunk.
359 ///
360 /// Returns the unique identifier for the function and the index of the new chunk, which will
361 /// be used to add corresponding [`InstructionKind::LoadConst`] and [`InstructionKind::StoreVar`]
362 /// instructions to the parent chunk.
363 pub fn new_chunk<F>(&mut self, header: &FuncHeader, f: F) -> Result<NewChunk, Error>
364 where F: FnOnce(&mut Compiler) -> Result<(), Error>
365 {
366 let old_chunk_idx = self.chunk;
367 self.chunks.push(Chunk::new(header.params.len()));
368 let new_chunk_idx = self.chunks.len() - 1;
369
370 let id = self.next_item_id;
371 self.add_item(
372 &header.name,
373 Item::Func(FuncDecl::new(
374 id,
375 self.sym_table.next_id(),
376 new_chunk_idx,
377 header.params.clone(),
378 )),
379 )?;
380 self.next_item_id += 1;
381
382 self.chunk = new_chunk_idx;
383 let scope = self.new_scope_get(f)?;
384 let captures = scope.captures()
385 .iter()
386 .map(|symbol| match symbol {
387 Symbol::User(id) => *id,
388 _ => unreachable!(),
389 })
390 .collect();
391 self.chunk = old_chunk_idx;
392
393 Ok(NewChunk {
394 id,
395 chunk: new_chunk_idx,
396 captures,
397 })
398 }
399
400 /// Creates a new temporary chunk for compilation. Within the provided function, all `compiler`
401 /// methods that add or edit instructions will do so to the new chunk.
402 ///
403 /// The chunk is returned at the end of this function, without being added to the list of
404 /// chunks.
405 pub(crate) fn new_chunk_get<F>(&mut self, f: F) -> Result<Chunk, Error>
406 where F: FnOnce(&mut Compiler) -> Result<(), Error>
407 {
408 let old_chunk_idx = self.chunk;
409 self.chunks.push(Chunk::default());
410 let new_chunk_idx = self.chunks.len() - 1;
411
412 self.chunk = new_chunk_idx;
413 f(self)?;
414 let chunk = self.chunks.pop().unwrap();
415 self.chunk = old_chunk_idx;
416
417 Ok(chunk)
418 }
419
420 /// Adds a symbol to the symbol table at the current scope.
421 ///
422 /// This is a shortcut for [`Compiler::add_item`] that creates a new [`Item::Symbol`] from the
423 /// given symbol and returns the unique identifier for the symbol.
424 ///
425 /// # Manual compilation
426 ///
427 /// [`Compiler::add_symbol`] can be used to declare the existence of uninitialized variables.
428 /// This is useful for creating a symbol and acquiring its unique identifier in order to
429 /// manipulate it in a virtual machine.
430 ///
431 /// If you do this, you must ensure that the symbol is initialized before it is used. This can
432 /// be done in the virtual machine. See the [`cas-vm`] crate for an example.
433 ///
434 /// [`cas-vm`]: https://docs.rs/cas-vm/latest/cas_vm/
435 pub fn add_symbol(&mut self, symbol: &LitSym) -> Result<usize, Error> {
436 let id = self.next_item_id;
437 self.add_item(symbol, Item::Symbol(SymbolDecl { id }))?;
438 self.next_item_id += 1;
439 Ok(id)
440 }
441
442 /// Resolves a path to a user-created symbol, inserting it into the symbol table if it doesn't
443 /// exist.
444 ///
445 /// If the symbol name matches that of a builtin constant, one of the following will occur:
446 ///
447 /// - If this function is called from the global scope, an [`OverrideBuiltinConstant`]
448 /// error is returned.
449 /// - If this function is called anywhere else, the symbol table will successfully be updated
450 /// with the new symbol. This symbol shadows the existing builtin constant, meaning the
451 /// builtin will not be accessible until the scope in which this symbol was declared, ends.
452 ///
453 /// Returns the unique identifier for the symbol, which can be used to reference the symbol in
454 /// the bytecode.
455 pub fn resolve_user_symbol_or_insert(&mut self, symbol: &LitSym) -> Result<usize, Error> {
456 if let Some(item) = self.sym_table.resolve_item(&symbol.name) {
457 // symbol was found in the current or a parent scope
458 Ok(item.id())
459 } else {
460 // if not, insert it
461 self.add_symbol(symbol)
462 }
463 }
464
465 /// Resolves a path to a symbol without inserting it into the symbol table. If the symbol is
466 /// determined to be captured from a parent scope, the enclosing function will be marked as
467 /// capturing the symbol.
468 ///
469 /// Returns the unique identifier for the symbol, or an error if the symbol is not found within
470 /// the current scope.
471 pub fn resolve_symbol(&mut self, symbol: &LitSym) -> Result<Symbol, Error> {
472 if let Some(symbol) = self.sym_table.resolve_item_mark_capture(&symbol.name) {
473 // symbol was found in the current or a parent scope
474 Ok(symbol)
475 } else {
476 // maybe it refers to a builtin constant or function
477 if let Some(symbol) = resolve_builtin(symbol) {
478 Ok(symbol)
479 } else {
480 // no matching symbol found
481 Err(Error::new(vec![symbol.span.clone()], UnknownVariable {
482 name: symbol.name.clone(),
483 }))
484 }
485 }
486 }
487
488 /// Adds an instruction to the current chunk with no associated source code span.
489 pub fn add_instr(&mut self, instruction: impl Into<Instruction>) {
490 let chunk = self.chunk_mut();
491 chunk.instructions.push(instruction.into());
492 }
493
494 /// Adds an instruction to the current chunk with an associated source code span(s).
495 pub fn add_instr_with_spans(
496 &mut self,
497 instruction: impl Into<Instruction>,
498 spans: Vec<Range<usize>>,
499 ) {
500 let mut instruction = instruction.into();
501 instruction.spans = spans;
502 let chunk = self.chunk_mut();
503 chunk.instructions.push(instruction);
504 }
505
506 /// Adds a sequence of instructions from a chunk to the current chunk.
507 pub(crate) fn add_chunk_instrs(&mut self, new_chunk: Chunk) {
508 let chunk = self.chunk_mut();
509 chunk.instructions.extend(new_chunk.instructions);
510 }
511
512 /// Replaces an instruction at the given index in the current chunk with a new instruction.
513 pub fn replace_instr(&mut self, idx: usize, instruction: Instruction) {
514 let chunk = self.chunk_mut();
515 chunk.instructions[idx] = instruction;
516 }
517
518 /// Creates a unique label with no associated instruction. This label can be used to reference
519 /// a specific instruction in the bytecode.
520 pub fn new_unassociated_label(&mut self) -> Label {
521 let label = Label(self.labels.len());
522 self.labels.insert(label, None);
523 label
524 }
525
526 /// Creates a unique label pointing to the end of the currently generated bytecode in the
527 /// current chunk.
528 ///
529 /// When this method is called and [`Compile::compile`] is called immediately after, the label
530 /// will point to the first instruction generated by the compilation.
531 pub fn new_end_label(&mut self) -> Label {
532 let label = Label(self.labels.len());
533 let chunk_instrs = self.chunk().instructions.len();
534 self.labels.insert(label, Some((self.chunk, chunk_instrs)));
535 label
536 }
537
538 /// Associates the given label with the end of the currently generated bytecode.
539 ///
540 /// This is useful for creating labels that point to the end of a loop, for example.
541 pub fn set_end_label(&mut self, label: Label) {
542 let chunk_instrs = self.chunk().instructions.len();
543 self.labels.insert(label, Some((self.chunk, chunk_instrs)));
544 }
545}
546
547/// Trait for types that can be compiled into bytecode [`Instruction`]s.
548///
549/// The compiler is responsible for converting a CalcScript abstract syntax tree into a bytecode
550/// representation that can be executed by the
551/// [`Vm`](https://docs.rs/cas-vm/latest/cas_vm/vm/struct.Vm.html). The available instructions are
552/// defined in the [`InstructionKind`] `enum`.
553pub trait Compile {
554 /// Compiles the type into a sequence of [`Instruction`]s.
555 fn compile(&self, compiler: &mut Compiler) -> Result<(), Error>;
556}
557
558impl<T: Compile> Compile for &T {
559 fn compile(&self, compiler: &mut Compiler) -> Result<(), Error> {
560 (*self).compile(compiler)
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use cas_parser::parser::{ast::stmt::Stmt, Parser};
568
569 /// Compile the given source code.
570 fn compile(source: &str) -> Result<Compiler, Error> {
571 let mut parser = Parser::new(source);
572 let stmts = parser.try_parse_full_many::<Stmt>().unwrap();
573
574 Compiler::compile_program(stmts)
575 }
576
577 #[test]
578 fn function_declaration() {
579 compile("f(x) = {
580 g(x) = {
581 h(x) = x
582 h(x) % 2 == 0
583 }
584 x % 3 == 0 && g(x)
585}
586
587f(18)").unwrap();
588 }
589
590 #[test]
591 fn scoping() {
592 let err = compile("f() = j + 6
593g() = {
594 j = 10
595 f()
596}
597g()").unwrap_err();
598
599 // error is in the definition of `f`
600 // variable `j` is defined in `g`, so `f` can only access it if `x` is passed as an
601 // argument, or `j` is in a higher scope
602 assert_eq!(err.spans[0], 6..7);
603 }
604
605 #[test]
606 fn advanced_scoping() {
607 // `{}` curly braces, `f(x) = ...` function declarations, `loop`, `while`, `for`, `sum`,
608 // and `product` introduce new scopes
609 compile("{ x = 25 }; x").unwrap_err();
610
611 // but if a variable is already defined in a parent scope, it can be accessed in a child
612 // scope
613 compile("x = 25; { x *= 2 }; x").unwrap();
614
615 // this wouldn't make sense if a scope was not created
616 //
617 // functions aren't called at declaration, so `y` wouldn't be initialized when `y` is
618 // accessed
619 compile("f(x) = y = 25; y").unwrap_err();
620
621 compile("f(x) = { y = 25 }; y").unwrap_err();
622 compile("loop { t = rand(); if t < 0.2 break t }; t").unwrap_err();
623 compile("a = for i in 1..5 { t = i }; t").unwrap_err();
624
625 // `loop` and `while` _do_ introduce new scopes, specifically to avoid the first test
626 // below: if scopes were not introduced, `t` would be uninitialized after the loop
627 // immediately breaks, causing an error when `t` is accessed outside the loop
628 //
629 // despite that real issue, in general, scopes aren't all that useful in loops, since the
630 // loop body will usually need to have multiple statements. the user would have to use
631 // curly braces `{}` to write multiple statements, and thus, introduce a new scope
632 //
633 // otherwise, the loop body would just be a single statement, and the scope would be
634 compile("loop t = break 2; t").unwrap_err();
635 compile("while true break t = 2; t").unwrap_err();
636
637 compile("(sum n in 1..5 of n) > n").unwrap_err();
638 }
639
640 #[test]
641 fn scoping_with_compiler_declared_variables() {
642 compile("for n in 1..n then print(n)").unwrap_err();
643 compile("n = 50; for n in 0..n then print(n)").unwrap();
644 compile("for n in n..50 then print(n)").unwrap_err();
645 }
646
647 #[test]
648 fn shadowing() {
649 compile("pi = 5").unwrap_err();
650 compile("f() = pi = 5").unwrap(); // implicit shadowing occurs in non-global scopes
651 }
652
653 #[test]
654 fn no_override_builtin() {
655 compile("i = 5").unwrap_err(); // i is a builtin constant
656 compile("pi(x) = x").unwrap_err(); // pi is a builtin constant
657 compile("sqrt = 3").unwrap_err(); // sqrt is a builtin function
658 compile("ncr(a) = a").unwrap_err(); // ncr is a builtin function
659 }
660
661 #[test]
662 fn define_and_call() {
663 compile("f(x) = return x + 1/sqrt(x)
664g(x, y) = f(x) + f(y)
665g(2, 3)").unwrap();
666 }
667
668 #[test]
669 fn refer_to_parent() {
670 compile("f(x) = g(x) = h(x) = f(x)").unwrap();
671 compile("f(x) = g(x) = h(x) = g(x)").unwrap();
672 compile("f(x) = g(x) = h(x) = h(x)").unwrap();
673 }
674
675 #[test]
676 fn derivative() {
677 compile("f(x) = x^2; f'(2)").unwrap();
678 // TODO: it is not possible to derivate `ncr` (due to 2 parameters), so this should be an
679 // error
680 // however, ever since higher order functions were supported, functions couldn't be checked
681 // if they were valid for derivation until runtime
682 // however again, it is possible to determine if a function reference is referring to a
683 // builtin function, so technically we have enough information to determine if this is
684 // derivable at compile time. maybe we should do that
685 compile("ncr''(5, 3)").unwrap();
686 }
687
688 #[test]
689 fn list_index() {
690 compile("arr = [1, 2, 3]
691arr[0] = 5
692arr[0] + arr[1] + arr[2] == 10").unwrap();
693 }
694}