Skip to main content

endbasic_core/
callable.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Symbol definitions and symbols table representation.
18
19use crate::ast::ArgSep;
20use crate::ast::ExprType;
21use crate::bytecode::TaggedRegisterRef;
22use crate::bytecode::VarArgTag;
23use crate::mem::HeapOverflowError;
24use crate::mem::{ArrayData, ConstantDatum, DatumPtr, Heap, HeapDatum};
25use crate::reader::LineCol;
26use async_trait::async_trait;
27use std::borrow::Cow;
28use std::fmt;
29use std::io;
30use std::ops::RangeInclusive;
31use std::rc::Rc;
32use std::str::Lines;
33
34/// Error types for callable execution.
35#[derive(Debug, thiserror::Error)]
36pub enum CallError {
37    /// Invalid callable argument.
38    #[error("{0}")]
39    Argument(String),
40
41    /// Runtime evaluation error.
42    #[error("{0}")]
43    Eval(String),
44
45    /// I/O error.
46    #[error("{0}")]
47    IoError(io::Error),
48
49    /// Callable precondition failure.
50    #[error("{0}")]
51    Precondition(String),
52
53    /// Indicates a syntax error only detectable at runtime.
54    #[error("{0}: {1}")]
55    Syntax(LineCol, String),
56}
57
58impl From<io::Error> for CallError {
59    fn from(value: io::Error) -> Self {
60        Self::IoError(value)
61    }
62}
63
64impl From<HeapOverflowError> for CallError {
65    fn from(value: HeapOverflowError) -> Self {
66        Self::Eval(value.to_string())
67    }
68}
69
70impl CallError {
71    /// Converts this call error to an upcall error with a mandatory position.
72    ///
73    /// If the error does not carry an origin position on its own, uses
74    /// `default_pos`.
75    pub(crate) fn to_upcall_error(&self, default_pos: LineCol) -> UpcallError {
76        match self {
77            CallError::Argument(message) => UpcallError::Argument(default_pos, message.clone()),
78
79            CallError::Eval(message) => UpcallError::Eval(default_pos, message.clone()),
80
81            CallError::IoError(e) => UpcallError::IoError(default_pos, e.to_string()),
82
83            CallError::Precondition(message) => {
84                UpcallError::Precondition(default_pos, message.clone())
85            }
86
87            CallError::Syntax(pos, message) => UpcallError::Syntax(*pos, message.clone()),
88        }
89    }
90}
91
92/// Result type for callable execution.
93pub type CallResult<T> = Result<T, CallError>;
94
95/// Error type for uncaught upcall failures.
96///
97/// This should be the same as `CallError` but with all error variants annotated with a position.
98#[derive(Debug, thiserror::Error)]
99pub enum UpcallError {
100    /// Invalid callable argument at a given source location.
101    #[error("{0}: {1}")]
102    Argument(LineCol, String),
103
104    /// Runtime evaluation error at a given source location.
105    #[error("{0}: {1}")]
106    Eval(LineCol, String),
107
108    /// I/O error at a given source location.
109    #[error("{0}: {1}")]
110    IoError(LineCol, String),
111
112    /// Callable precondition failure at a given source location.
113    #[error("{0}: {1}")]
114    Precondition(LineCol, String),
115
116    /// Runtime syntax error at a specific source location.
117    #[error("{0}: {1}")]
118    Syntax(LineCol, String),
119}
120
121impl UpcallError {
122    /// Returns the source position and message of this error.
123    pub fn parts(&self) -> (LineCol, String) {
124        match self {
125            UpcallError::Argument(pos, message) => (*pos, message.clone()),
126            UpcallError::Eval(pos, message) => (*pos, message.clone()),
127            UpcallError::IoError(pos, message) => (*pos, message.clone()),
128            UpcallError::Precondition(pos, message) => (*pos, message.clone()),
129            UpcallError::Syntax(pos, message) => (*pos, message.clone()),
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_io_error_has_no_source() {
140        let error = CallError::from(io::Error::other("Some I/O error"));
141
142        assert!(std::error::Error::source(&error).is_none());
143    }
144}
145
146/// Syntax specification for a required scalar parameter.
147#[derive(Clone, Debug, PartialEq)]
148pub struct RequiredValueSyntax {
149    /// The name of the parameter for help purposes.
150    pub name: Cow<'static, str>,
151
152    /// The type of the expected parameter.
153    pub vtype: ExprType,
154}
155
156/// Syntax specification for a required reference parameter.
157#[derive(Clone, Debug, PartialEq)]
158pub struct RequiredRefSyntax {
159    /// The name of the parameter for help purposes.
160    pub name: Cow<'static, str>,
161
162    /// If true, require an array reference; if false, a variable reference.
163    pub require_array: bool,
164
165    /// If true, allow references to undefined variables because the command will define them when
166    /// missing.  Can only be set to true for commands, not functions, and `require_array` must be
167    /// false.
168    pub define_undefined: bool,
169}
170
171/// Syntax specification for an optional scalar parameter.
172///
173/// Optional parameters are only supported in commands.
174#[derive(Clone, Debug, PartialEq)]
175pub struct OptionalValueSyntax {
176    /// The name of the parameter for help purposes.
177    pub name: Cow<'static, str>,
178
179    /// The type of the expected parameter.
180    pub vtype: ExprType,
181}
182
183/// Specifies the type constraints for a repeated parameter.
184#[derive(Clone, Debug, PartialEq)]
185pub enum RepeatedTypeSyntax {
186    /// Allows any value type, including empty arguments.  The values pushed onto the stack have
187    /// the same semantics as those pushed by `AnyValueSyntax`.
188    AnyValue,
189
190    /// Expects a value of the given type.
191    TypedValue(ExprType),
192
193    /// Expects a reference to a variable (not an array) and allows the variables to not be defined.
194    VariableRef,
195}
196
197/// Syntax specification for a repeated parameter.
198///
199/// The repeated parameter must appear after all singular positional parameters.
200#[derive(Clone, Debug, PartialEq)]
201pub struct RepeatedSyntax {
202    /// The name of the parameter for help purposes.
203    pub name: Cow<'static, str>,
204
205    /// The type of the expected parameters.
206    pub type_syn: RepeatedTypeSyntax,
207
208    /// The separator to expect between the repeated parameters.  For functions, this must be the
209    /// long separator (the comma).
210    pub sep: ArgSepSyntax,
211
212    /// Whether the repeated parameter must at least have one element or not.
213    pub require_one: bool,
214
215    /// Whether to allow any parameter to not be present or not.  Can only be true for commands.
216    pub allow_missing: bool,
217}
218
219impl RepeatedSyntax {
220    /// Formats the repeated argument syntax for help purposes into `output`.
221    ///
222    /// `last_singular_sep` contains the separator of the last singular argument syntax, if any,
223    /// which we need to place inside of the optional group.
224    fn describe(&self, output: &mut String, last_singular_sep: Option<&ArgSepSyntax>) {
225        if !self.require_one {
226            output.push('[');
227        }
228
229        if let Some(sep) = last_singular_sep {
230            sep.describe(output);
231        }
232
233        output.push_str(&self.name);
234        output.push('1');
235        if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
236            output.push(vtype.annotation());
237        }
238
239        if self.require_one {
240            output.push('[');
241        }
242
243        self.sep.describe(output);
244        output.push_str("..");
245        self.sep.describe(output);
246
247        output.push_str(&self.name);
248        output.push('N');
249        if let RepeatedTypeSyntax::TypedValue(vtype) = self.type_syn {
250            output.push(vtype.annotation());
251        }
252
253        output.push(']');
254    }
255}
256
257/// Syntax specification for a parameter that accepts any scalar type.
258#[derive(Clone, Debug, PartialEq)]
259pub struct AnyValueSyntax {
260    /// The name of the parameter for help purposes.
261    pub name: Cow<'static, str>,
262
263    /// Whether to allow the parameter to not be present or not.  Can only be true for commands.
264    pub allow_missing: bool,
265}
266
267/// Specifies the expected argument separator in a callable's syntax.
268#[derive(Copy, Clone, Debug, PartialEq)]
269pub enum ArgSepSyntax {
270    /// The argument separator must exactly be the one given.
271    Exactly(ArgSep),
272
273    /// The argument separator may be any of the ones given.
274    OneOf(&'static [ArgSep]),
275
276    /// The argument separator is the end of the call.
277    End,
278}
279
280impl ArgSepSyntax {
281    /// Formats the argument separator for help purposes into `output`.
282    fn describe(&self, output: &mut String) {
283        match self {
284            ArgSepSyntax::Exactly(sep) => {
285                let (text, needs_space) = sep.describe();
286
287                if !text.is_empty() && needs_space {
288                    output.push(' ');
289                }
290                output.push_str(text);
291                if !text.is_empty() {
292                    output.push(' ');
293                }
294            }
295
296            ArgSepSyntax::OneOf(seps) => {
297                output.push_str(" <");
298                for (i, sep) in seps.iter().enumerate() {
299                    let (text, _needs_space) = sep.describe();
300                    output.push_str(text);
301                    if i < seps.len() - 1 {
302                        output.push('|');
303                    }
304                }
305                output.push_str("> ");
306            }
307
308            ArgSepSyntax::End => (),
309        };
310    }
311}
312
313/// Syntax specification for a non-repeated argument.
314///
315/// Every item in this enum is composed of a struct that provides the details on the parameter and
316/// a struct that provides the details on how this parameter is separated from the next.
317#[derive(Clone, Debug, PartialEq)]
318pub enum SingularArgSyntax {
319    /// A required scalar value with the syntax details and the separator that follows.
320    RequiredValue(RequiredValueSyntax, ArgSepSyntax),
321
322    /// A required reference with the syntax details and the separator that follows.
323    RequiredRef(RequiredRefSyntax, ArgSepSyntax),
324
325    /// An optional scalar value with the syntax details and the separator that follows.
326    OptionalValue(OptionalValueSyntax, ArgSepSyntax),
327
328    /// A required scalar value of any type with the syntax details and the separator that follows.
329    AnyValue(AnyValueSyntax, ArgSepSyntax),
330}
331
332/// Complete syntax specification for a callable's arguments.
333///
334/// Note that the description of function arguments is more restricted than that of commands.
335/// The arguments compiler panics when these preconditions aren't met with the rationale that
336/// builtin functions must never be ill-defined.
337// TODO(jmmv): It might be nice to try to express these restrictions in the type system, but
338// things are already too verbose as they are...
339#[derive(Clone, Debug, PartialEq)]
340pub(crate) struct CallableSyntax {
341    /// Ordered list of singular arguments that appear before repeated arguments.
342    pub(crate) singular: Cow<'static, [SingularArgSyntax]>,
343
344    /// Details on the repeated argument allowed after singular arguments, if any.
345    pub(crate) repeated: Option<Cow<'static, RepeatedSyntax>>,
346}
347
348impl CallableSyntax {
349    /// Creates a new callable arguments definition from its parts defined statically in the
350    /// code.
351    pub(crate) fn new_static(
352        singular: &'static [SingularArgSyntax],
353        repeated: Option<&'static RepeatedSyntax>,
354    ) -> Self {
355        Self { singular: Cow::Borrowed(singular), repeated: repeated.map(Cow::Borrowed) }
356    }
357
358    /// Creates a new callable arguments definition from its parts defined dynamically at
359    /// runtime.
360    pub(crate) fn new_dynamic(
361        singular: Vec<SingularArgSyntax>,
362        repeated: Option<RepeatedSyntax>,
363    ) -> Self {
364        Self { singular: Cow::Owned(singular), repeated: repeated.map(Cow::Owned) }
365    }
366
367    /// Computes the range of the expected number of parameters for this syntax.
368    pub(crate) fn expected_nargs(&self) -> RangeInclusive<usize> {
369        let mut min = self.singular.len();
370        let mut max = self.singular.len();
371
372        if let Some(syn) = self.repeated.as_ref() {
373            if syn.require_one {
374                min += 1;
375            }
376            max = usize::MAX;
377        }
378
379        min..=max
380    }
381
382    /// Returns true if this syntax represents "no arguments".
383    pub(crate) fn is_empty(&self) -> bool {
384        self.singular.is_empty() && self.repeated.is_none()
385    }
386
387    /// Produces a user-friendly description of this callable syntax.
388    pub(crate) fn describe(&self) -> String {
389        let mut description = String::new();
390        let mut last_singular_sep = None;
391        for (i, s) in self.singular.iter().enumerate() {
392            let sep = match s {
393                SingularArgSyntax::RequiredValue(details, sep) => {
394                    description.push_str(&details.name);
395                    description.push(details.vtype.annotation());
396                    sep
397                }
398
399                SingularArgSyntax::RequiredRef(details, sep) => {
400                    description.push_str(&details.name);
401                    sep
402                }
403
404                SingularArgSyntax::OptionalValue(details, sep) => {
405                    description.push('[');
406                    description.push_str(&details.name);
407                    description.push(details.vtype.annotation());
408                    description.push(']');
409                    sep
410                }
411
412                SingularArgSyntax::AnyValue(details, sep) => {
413                    if details.allow_missing {
414                        description.push('[');
415                    }
416                    description.push_str(&details.name);
417                    if details.allow_missing {
418                        description.push(']');
419                    }
420                    sep
421                }
422            };
423
424            if self.repeated.is_none() || i < self.singular.len() - 1 {
425                sep.describe(&mut description);
426            }
427            if i == self.singular.len() - 1 {
428                last_singular_sep = Some(sep);
429            }
430        }
431
432        if let Some(syn) = &self.repeated {
433            syn.describe(&mut description, last_singular_sep);
434        }
435
436        description
437    }
438}
439
440/// Builder pattern for constructing a callable's metadata.
441pub struct CallableMetadataBuilder {
442    /// Name of the callable, stored in uppercase.
443    name: Cow<'static, str>,
444
445    /// Return type of the callable, or `None` for commands/subroutines.
446    return_type: Option<ExprType>,
447
448    /// Whether this callable requires asynchronous dispatch.
449    is_async: bool,
450
451    /// Category for grouping related callables in help messages.
452    category: Option<&'static str>,
453
454    /// Syntax specifications for the callable's arguments.
455    syntaxes: Vec<CallableSyntax>,
456
457    /// Description of the callable for documentation purposes.
458    description: Option<&'static str>,
459}
460
461impl CallableMetadataBuilder {
462    /// Constructs a new metadata builder with the minimum information necessary.
463    ///
464    /// All code except tests must populate the whole builder with details.  This is enforced at
465    /// construction time, where we only allow some fields to be missing under the test
466    /// configuration.
467    pub fn new(name: &'static str) -> Self {
468        assert!(name == name.to_ascii_uppercase(), "Callable name must be in uppercase");
469
470        Self {
471            name: Cow::Borrowed(name),
472            return_type: None,
473            is_async: false,
474            syntaxes: vec![],
475            category: None,
476            description: None,
477        }
478    }
479
480    /// Constructs a new metadata builder with the minimum information necessary.
481    ///
482    /// This is the same as `new` but using a dynamically-allocated name, which is necessary for
483    /// user-defined symbols.
484    pub fn new_dynamic<S: Into<String>>(name: S) -> Self {
485        Self {
486            name: Cow::Owned(name.into().to_ascii_uppercase()),
487            return_type: None,
488            is_async: false,
489            syntaxes: vec![],
490            category: Some("User defined"),
491            description: Some("User defined symbol."),
492        }
493    }
494
495    /// Sets the return type of the callable.
496    pub fn with_return_type(mut self, return_type: ExprType) -> Self {
497        self.return_type = Some(return_type);
498        self
499    }
500
501    /// Sets whether this callable requires asynchronous dispatch.
502    pub fn with_async(mut self, is_async: bool) -> Self {
503        self.is_async = is_async;
504        self
505    }
506
507    /// Sets the syntax specifications for this callable.
508    pub fn with_syntax(
509        mut self,
510        syntaxes: &'static [(&'static [SingularArgSyntax], Option<&'static RepeatedSyntax>)],
511    ) -> Self {
512        self.syntaxes = syntaxes
513            .iter()
514            .map(|s| CallableSyntax::new_static(s.0, s.1))
515            .collect::<Vec<CallableSyntax>>();
516        self
517    }
518
519    /// Sets the syntax specifications for this callable.
520    pub(crate) fn with_syntaxes<S: Into<Vec<CallableSyntax>>>(mut self, syntaxes: S) -> Self {
521        self.syntaxes = syntaxes.into();
522        self
523    }
524
525    /// Sets the syntax specifications for this callable.
526    pub(crate) fn with_dynamic_syntax(
527        self,
528        syntaxes: Vec<(Vec<SingularArgSyntax>, Option<RepeatedSyntax>)>,
529    ) -> Self {
530        let syntaxes = syntaxes
531            .into_iter()
532            .map(|s| CallableSyntax::new_dynamic(s.0, s.1))
533            .collect::<Vec<CallableSyntax>>();
534        self.with_syntaxes(syntaxes)
535    }
536
537    /// Sets the category for this callable.  All callables with the same category name will be
538    /// grouped together in help messages.
539    pub fn with_category(mut self, category: &'static str) -> Self {
540        self.category = Some(category);
541        self
542    }
543
544    /// Sets the description for this callable.  The `description` is a collection of paragraphs
545    /// separated by a single newline character, where the first paragraph is taken as the summary
546    /// of the description.  The summary must be a short sentence that is descriptive enough to be
547    /// understood without further details.  Empty lines (paragraphs) are not allowed.
548    pub fn with_description(mut self, description: &'static str) -> Self {
549        for l in description.lines() {
550            assert!(!l.is_empty(), "Description cannot contain empty lines");
551        }
552        self.description = Some(description);
553        self
554    }
555
556    /// Generates the final `CallableMetadata` object, ensuring all values are present.
557    pub fn build(self) -> Rc<CallableMetadata> {
558        assert!(!self.syntaxes.is_empty(), "All callables must specify a syntax");
559        Rc::from(CallableMetadata {
560            name: self.name,
561            return_type: self.return_type,
562            is_async: self.is_async,
563            syntaxes: self.syntaxes,
564            category: self.category.expect("All callables must specify a category"),
565            description: self.description.expect("All callables must specify a description"),
566        })
567    }
568
569    /// Generates the final `CallableMetadata` object, ensuring the minimal set of values are
570    /// present.  Only useful for testing.
571    pub fn test_build(mut self) -> Rc<CallableMetadata> {
572        if self.syntaxes.is_empty() {
573            self.syntaxes.push(CallableSyntax::new_static(&[], None));
574        }
575        Rc::from(CallableMetadata {
576            name: self.name,
577            return_type: self.return_type,
578            is_async: self.is_async,
579            syntaxes: self.syntaxes,
580            category: self.category.unwrap_or(""),
581            description: self.description.unwrap_or(""),
582        })
583    }
584}
585
586/// Representation of a callable's metadata.
587///
588/// The callable is expected to hold onto an instance of this object within its struct to make
589/// queries fast.
590#[derive(Clone, Debug, PartialEq)]
591pub struct CallableMetadata {
592    /// Name of the callable, stored in uppercase.
593    name: Cow<'static, str>,
594
595    /// Return type of the callable, or `None` for commands/subroutines.
596    return_type: Option<ExprType>,
597
598    /// Whether this callable requires asynchronous dispatch.
599    is_async: bool,
600
601    /// Syntax specifications for the callable's arguments.
602    syntaxes: Vec<CallableSyntax>,
603
604    /// Category for grouping related callables in help messages.
605    category: &'static str,
606
607    /// Description of the callable for documentation purposes.
608    description: &'static str,
609}
610
611impl CallableMetadata {
612    /// Gets the callable's name, all in uppercase.
613    pub fn name(&self) -> &str {
614        &self.name
615    }
616
617    /// Gets the callable's return type.
618    pub fn return_type(&self) -> Option<ExprType> {
619        self.return_type
620    }
621
622    /// Gets whether this callable requires asynchronous dispatch.
623    pub fn is_async(&self) -> bool {
624        self.is_async
625    }
626
627    /// Gets the callable's syntax specification.
628    pub fn syntax(&self) -> String {
629        fn format_one(cs: &CallableSyntax) -> String {
630            let mut syntax = cs.describe();
631            if syntax.is_empty() {
632                syntax.push_str("no arguments");
633            }
634            syntax
635        }
636
637        match self.syntaxes.as_slice() {
638            [] => panic!("Callables without syntaxes are not allowed at construction time"),
639            [one] => format_one(one),
640            many => many
641                .iter()
642                .map(|syn| format!("<{}>", syn.describe()))
643                .collect::<Vec<String>>()
644                .join(" | "),
645        }
646    }
647
648    /// Returns true if `sep` is valid for a function call (only `Long` and `End` are allowed because
649    /// the parser only produces comma separators for function arguments).
650    fn is_function_sep(sep: &ArgSepSyntax) -> bool {
651        match sep {
652            ArgSepSyntax::Exactly(ArgSep::Long) | ArgSepSyntax::End => true,
653            ArgSepSyntax::OneOf(seps) => seps.iter().all(|s| *s == ArgSep::Long),
654            _ => false,
655        }
656    }
657
658    /// Checks that the syntax of a callable that returns a value only uses separators that can appear
659    /// in a function call (i.e. the comma separator).  The parser only produces `ArgSep::Long` for
660    /// function arguments, so any other separator in the metadata would be dead/untestable.
661    fn debug_assert_function_seps(&self, syntax: &CallableSyntax) {
662        if self.return_type().is_none() {
663            return;
664        }
665        for syn in syntax.singular.iter() {
666            let sep = match syn {
667                SingularArgSyntax::RequiredValue(_, sep) => sep,
668                SingularArgSyntax::RequiredRef(_, sep) => sep,
669                SingularArgSyntax::OptionalValue(_, sep) => sep,
670                SingularArgSyntax::AnyValue(_, sep) => sep,
671            };
672            debug_assert!(
673                Self::is_function_sep(sep),
674                "Function {} has a non-comma separator in its singular args syntax",
675                self.name()
676            );
677        }
678        if let Some(repeated) = syntax.repeated.as_ref() {
679            debug_assert!(
680                Self::is_function_sep(&repeated.sep),
681                "Function {} has a non-comma separator in its repeated args syntax",
682                self.name()
683            );
684        }
685    }
686
687    /// Finds the syntax definition that matches the given argument count.
688    ///
689    /// Returns an error if no syntax matches, and panics if multiple syntaxes match (which would
690    /// indicate an ambiguous callable definition).
691    pub(crate) fn find_syntax(&self, nargs: usize) -> Option<&CallableSyntax> {
692        let mut matches = self.syntaxes.iter().filter(|s| s.expected_nargs().contains(&nargs));
693        let syntax = matches.next();
694        match syntax {
695            Some(syntax) => {
696                debug_assert!(matches.next().is_none(), "Ambiguous syntax definitions");
697                if cfg!(debug_assertions) {
698                    self.debug_assert_function_seps(syntax);
699                }
700                Some(syntax)
701            }
702            None => None,
703        }
704    }
705
706    /// Gets the callable's category as a collection of lines.  The first line is the title of the
707    /// category, and any extra lines are additional information for it.
708    #[allow(unused)]
709    pub fn category(&self) -> &'static str {
710        self.category
711    }
712
713    /// Gets the callable's textual description as a collection of lines.  The first line is the
714    /// summary of the callable's purpose.
715    #[allow(unused)]
716    pub fn description(&self) -> Lines<'static> {
717        self.description.lines()
718    }
719
720    /// Returns true if this is a callable that takes no arguments.
721    #[allow(unused)]
722    pub fn is_argless(&self) -> bool {
723        self.syntaxes.is_empty() || (self.syntaxes.len() == 1 && self.syntaxes[0].is_empty())
724    }
725
726    /// Returns true if this callable is a function (not a command).
727    #[allow(unused)]
728    pub(crate) fn is_function(&self) -> bool {
729        self.return_type.is_some()
730    }
731
732    /// Returns true if this callable is user-defined.
733    pub(crate) fn is_user_defined(&self) -> bool {
734        self.category == "User defined"
735    }
736}
737
738/// Reads a boolean from the register at `index`, asserting that `vtype` is `Boolean`.
739fn deref_boolean(regs: &[u64], index: usize, vtype: ExprType) -> bool {
740    assert_eq!(ExprType::Boolean, vtype);
741    regs[index] != 0
742}
743
744/// Reads a double from the register at `index`, asserting that `vtype` is `Double`.
745fn deref_double(regs: &[u64], index: usize, vtype: ExprType) -> f64 {
746    assert_eq!(ExprType::Double, vtype);
747    f64::from_bits(regs[index])
748}
749
750/// Reads an integer from the register at `index`, asserting that `vtype` is `Integer`.
751fn deref_integer(regs: &[u64], index: usize, vtype: ExprType) -> i32 {
752    assert_eq!(ExprType::Integer, vtype);
753    regs[index] as i32
754}
755
756/// Reads a string from the register at `index`, asserting that `vtype` is `Text`.
757fn deref_string<'a>(
758    regs: &[u64],
759    index: usize,
760    vtype: ExprType,
761    constants: &'a [ConstantDatum],
762    heap: &'a Heap,
763) -> &'a str {
764    assert_eq!(ExprType::Text, vtype);
765    let ptr = DatumPtr::from(regs[index]);
766    ptr.resolve_string(constants, heap)
767}
768
769/// Dereferences this register reference as an array and returns its contents.
770fn array_data<'a>(regs: &'a [u64], index: usize, heap: &'a Heap) -> &'a ArrayData {
771    let ptr = DatumPtr::from(regs[index]);
772    let heap_idx = ptr.heap_index();
773    let HeapDatum::Array(a) = heap.get(heap_idx) else {
774        panic!("Scalar variable does not point to an array on the heap");
775    };
776    a
777}
778
779/// Dereferences this register reference as an array and returns its dimensions.
780fn array_dimensions<'a>(regs: &'a [u64], index: usize, heap: &'a Heap) -> &'a [usize] {
781    let a = array_data(regs, index, heap);
782    &a.dimensions
783}
784
785/// Dereferences an integer from an array element in this register reference.
786fn deref_array_integer(
787    regs: &[u64],
788    index: usize,
789    vtype: ExprType,
790    heap: &Heap,
791    subscripts: &[i32],
792) -> Result<i32, String> {
793    assert_eq!(ExprType::Integer, vtype);
794    let a = array_data(regs, index, heap);
795    let flat_idx = a.flat_index(subscripts)?;
796    Ok(a.values[flat_idx] as i32)
797}
798
799/// An immutable reference to a variable (register) in the register file, carrying
800/// its type for runtime validation of dereference operations.
801pub struct RegisterRef<'a, 'vm> {
802    /// The scope through which to access the register.
803    scope: &'a Scope<'vm>,
804
805    /// The absolute index of the register.
806    index: usize,
807
808    /// The type of the value pointed to.
809    pub vtype: ExprType,
810}
811
812impl<'a, 'vm> RegisterRef<'a, 'vm> {
813    /// Dereferences this register reference as a boolean.
814    pub fn deref_boolean(&self) -> bool {
815        deref_boolean(self.scope.regs, self.index, self.vtype)
816    }
817
818    /// Dereferences this register reference as a double.
819    pub fn deref_double(&self) -> f64 {
820        deref_double(self.scope.regs, self.index, self.vtype)
821    }
822
823    /// Dereferences this register reference as an integer.
824    pub fn deref_integer(&self) -> i32 {
825        deref_integer(self.scope.regs, self.index, self.vtype)
826    }
827
828    /// Dereferences this register reference as a string.
829    pub fn deref_string(&self) -> &str {
830        deref_string(self.scope.regs, self.index, self.vtype, self.scope.constants, self.scope.heap)
831    }
832
833    /// Dereferences this register reference as an array and returns its dimensions.
834    pub fn array_dimensions(&self) -> &[usize] {
835        array_dimensions(self.scope.regs, self.index, self.scope.heap)
836    }
837
838    /// Dereferences this register reference as an integer array and returns an element.
839    pub fn deref_array_integer(&self, subscripts: &[i32]) -> Result<i32, String> {
840        deref_array_integer(self.scope.regs, self.index, self.vtype, self.scope.heap, subscripts)
841    }
842}
843
844impl<'a, 'vm> fmt::Display for RegisterRef<'a, 'vm> {
845    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
846        write!(f, "&[R{}]{}", self.index, self.vtype)
847    }
848}
849
850/// A mutable reference to a variable (register) in the register file, carrying
851/// its type for runtime validation of dereference and set operations.
852pub struct RegisterRefMut<'a, 'vm> {
853    /// The scope through which to access the register.
854    scope: &'a mut Scope<'vm>,
855
856    /// The absolute index of the register.
857    index: usize,
858
859    /// The type of the value pointed to.
860    pub vtype: ExprType,
861}
862
863impl<'a, 'vm> RegisterRefMut<'a, 'vm> {
864    /// Dereferences this register reference as a boolean.
865    pub fn deref_boolean(&self) -> bool {
866        deref_boolean(self.scope.regs, self.index, self.vtype)
867    }
868
869    /// Dereferences this register reference as a double.
870    pub fn deref_double(&self) -> f64 {
871        deref_double(self.scope.regs, self.index, self.vtype)
872    }
873
874    /// Dereferences this register reference as an integer.
875    pub fn deref_integer(&self) -> i32 {
876        deref_integer(self.scope.regs, self.index, self.vtype)
877    }
878
879    /// Dereferences this register reference as a string.
880    pub fn deref_string(&self) -> &str {
881        deref_string(self.scope.regs, self.index, self.vtype, self.scope.constants, self.scope.heap)
882    }
883
884    /// Dereferences this register reference as an array and returns its dimensions.
885    pub fn array_dimensions(&self) -> &[usize] {
886        array_dimensions(self.scope.regs, self.index, self.scope.heap)
887    }
888
889    /// Dereferences this register reference as an integer array and returns an element.
890    pub fn deref_array_integer(&self, subscripts: &[i32]) -> Result<i32, String> {
891        deref_array_integer(self.scope.regs, self.index, self.vtype, self.scope.heap, subscripts)
892    }
893
894    /// Sets a boolean via this register reference.
895    pub fn set_boolean(&mut self, b: bool) {
896        assert_eq!(ExprType::Boolean, self.vtype);
897        self.scope.regs[self.index] = if b { 1 } else { 0 };
898    }
899
900    /// Sets a double via this register reference.
901    pub fn set_double(&mut self, d: f64) {
902        assert_eq!(ExprType::Double, self.vtype);
903        self.scope.regs[self.index] = d.to_bits();
904    }
905
906    /// Sets an integer via this register reference.
907    pub fn set_integer(&mut self, i: i32) {
908        assert_eq!(ExprType::Integer, self.vtype);
909        self.scope.regs[self.index] = i as u64;
910    }
911
912    /// Sets a string via this register reference.
913    pub fn set_string<S: Into<String>>(&mut self, s: S) -> CallResult<()> {
914        assert_eq!(ExprType::Text, self.vtype);
915        self.scope.regs[self.index] = self.scope.heap.push(HeapDatum::Text(s.into()))?;
916        Ok(())
917    }
918}
919
920impl<'a, 'vm> fmt::Display for RegisterRefMut<'a, 'vm> {
921    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
922        write!(f, "&[R{}]{}", self.index, self.vtype)
923    }
924}
925
926/// Arguments provided to a callable during its execution.
927pub struct Scope<'a> {
928    /// Slice of register values containing the callable's arguments.
929    pub(crate) regs: &'a mut [u64],
930
931    /// Reference to the constants pool for resolving constant pointers.
932    pub(crate) constants: &'a [ConstantDatum],
933
934    /// Reference to the heap for resolving heap pointers.
935    pub(crate) heap: &'a mut Heap,
936
937    /// Start of the current frame (where the arguments to the upcall start).
938    pub(crate) fp: usize,
939
940    /// Number of register slots to skip before the first argument.
941    ///
942    /// For commands, this is 0 because the first register slot holds the first argument.  For
943    /// functions, this is 1 because the first register slot holds the return value and arguments
944    /// start at slot 1.  The `get_*` methods add this offset to their register accesses so that
945    /// both commands and functions can use index 0 to refer to the first argument.
946    pub(crate) arg_offset: usize,
947
948    /// Source locations of the call arguments, one per argument in encounter order.
949    ///
950    /// Indexed by logical argument number: `arg_linecols[0]` is the source position of the first
951    /// argument.  Does not include an entry for the function return value slot.  May be shorter
952    /// than the actual argument count if debug information is unavailable.
953    pub(crate) arg_linecols: &'a [LineCol],
954
955    /// Last error raised in the VM, if any.
956    pub(crate) last_error: &'a Option<(LineCol, String)>,
957
958    /// `DATA` values captured from the compiled source.
959    pub(crate) data: &'a [Option<ConstantDatum>],
960}
961
962impl<'a> Scope<'a> {
963    /// Returns `DATA` values captured from the compiled source in encounter order.
964    pub fn data(&self) -> &[Option<ConstantDatum>] {
965        self.data
966    }
967
968    /// Returns the total number of argument register slots.
969    pub fn nargs(&self) -> usize {
970        self.arg_linecols.len()
971    }
972
973    /// Returns the source position of the argument at `arg`.
974    ///
975    /// `arg` is the logical argument index, matching the `N` in `scope.get_*(N)`.
976    pub fn get_pos(&self, arg: u8) -> LineCol {
977        self.arg_linecols[usize::from(arg)]
978    }
979
980    /// Gets the type tag of the argument at `arg`.
981    pub fn get_type(&self, arg: u8) -> VarArgTag {
982        VarArgTag::parse_u64(self.regs[self.fp + self.arg_offset + (arg as usize)]).unwrap()
983    }
984
985    /// Gets the boolean value of the argument at `arg`.
986    pub fn get_boolean(&self, arg: u8) -> bool {
987        self.regs[self.fp + self.arg_offset + (arg as usize)] != 0
988    }
989
990    /// Gets the double value of the argument at `arg`.
991    pub fn get_double(&self, arg: u8) -> f64 {
992        f64::from_bits(self.regs[self.fp + self.arg_offset + (arg as usize)])
993    }
994
995    /// Gets the integer value of the argument at `arg`.
996    pub fn get_integer(&self, arg: u8) -> i32 {
997        self.regs[self.fp + self.arg_offset + (arg as usize)] as i32
998    }
999
1000    /// Gets an immutable register reference from the argument at `arg`.
1001    pub fn get_ref(&self, arg: u8) -> RegisterRef<'_, 'a> {
1002        let tagged_ptr = self.regs[self.fp + self.arg_offset + (arg as usize)];
1003        let (index, vtype) = TaggedRegisterRef::from_u64(tagged_ptr).parse();
1004        RegisterRef { scope: self, index, vtype }
1005    }
1006
1007    /// Gets a mutable register reference from the argument at `arg`.
1008    pub fn get_mut_ref(&mut self, arg: u8) -> RegisterRefMut<'_, 'a> {
1009        let tagged_ptr = self.regs[self.fp + self.arg_offset + (arg as usize)];
1010        let (index, vtype) = TaggedRegisterRef::from_u64(tagged_ptr).parse();
1011        RegisterRefMut { scope: self, index, vtype }
1012    }
1013
1014    /// Gets the string value of the argument at `arg`.
1015    pub fn get_string(&self, arg: u8) -> &str {
1016        let index = self.regs[self.fp + self.arg_offset + (arg as usize)];
1017        let ptr = DatumPtr::from(index);
1018        ptr.resolve_string(self.constants, self.heap)
1019    }
1020
1021    /// Returns the last error stored in the VM, if any.
1022    pub fn last_error(&self) -> Option<(LineCol, &str)> {
1023        self.last_error.as_ref().map(|(pos, message)| (*pos, message.as_str()))
1024    }
1025
1026    /// Sets the return value of the function to `b`.
1027    ///
1028    /// Always returns success.  The returned value is only to support the idiomatic invocation
1029    /// `return scope.return_boolean(...)`.
1030    pub fn return_boolean(self, b: bool) -> CallResult<()> {
1031        self.regs[self.fp] = if b { 1 } else { 0 };
1032        Ok(())
1033    }
1034
1035    /// Sets the return value of the function to `d`.
1036    ///
1037    /// Always returns success.  The returned value is only to support the idiomatic invocation
1038    /// `return scope.return_double(...)`.
1039    pub fn return_double(self, d: f64) -> CallResult<()> {
1040        self.regs[self.fp] = d.to_bits();
1041        Ok(())
1042    }
1043
1044    /// Sets the return value of the function to `i`.
1045    ///
1046    /// Always returns success.  The returned value is only to support the idiomatic invocation
1047    /// `return scope.return_integer(...)`.
1048    pub fn return_integer(self, i: i32) -> CallResult<()> {
1049        self.regs[self.fp] = i as u64;
1050        Ok(())
1051    }
1052
1053    /// Sets the return value of the function to `s`.
1054    ///
1055    /// Always returns success.  The returned value is only to support the idiomatic invocation
1056    /// `return scope.return_string(...)`.
1057    pub fn return_string<S: Into<String>>(self, s: S) -> CallResult<()> {
1058        self.regs[self.fp] = self.heap.push(HeapDatum::Text(s.into()))?;
1059        Ok(())
1060    }
1061}
1062
1063/// A trait to define a callable that is executed by a `Machine`.
1064///
1065/// The callable themselves are immutable but they can reference mutable state.  Given that
1066/// EndBASIC is not threaded, it is sufficient for those references to be behind a `RefCell`
1067/// and/or an `Rc`.
1068///
1069/// Idiomatically, these objects need to provide a `new()` method that returns an `Rc<Callable>`, as
1070/// that's the type used throughout the execution engine.
1071#[async_trait(?Send)]
1072pub trait Callable {
1073    /// Returns the metadata for this function.
1074    ///
1075    /// The return value takes the form of a reference to force the callable to store the metadata
1076    /// as a struct field so that calls to this function are guaranteed to be cheap.
1077    fn metadata(&self) -> Rc<CallableMetadata>;
1078
1079    /// Executes the callable if it is synchronous.
1080    fn exec(&self, _scope: Scope<'_>) -> CallResult<()> {
1081        unimplemented!("Must be implemented for !is_async callables")
1082    }
1083
1084    /// Executes the callable if it is asynchronous.
1085    async fn async_exec(&self, _scope: Scope<'_>) -> CallResult<()> {
1086        unimplemented!("Must be implemented for is_async callables")
1087    }
1088}