pub(crate) mod algebra;
mod binding;
mod data;
mod import;
pub(crate) mod invert;
mod modifier;
pub(crate) mod optimize;
mod pre_eval;
use std::{
cell::RefCell,
cmp::Ordering,
collections::{BTreeSet, HashMap, HashSet, VecDeque},
env::current_dir,
fmt, fs,
hash::{DefaultHasher, Hash, Hasher},
iter::{once, repeat_n},
mem::{replace, swap, take},
panic::{AssertUnwindSafe, catch_unwind},
path::{Path, PathBuf},
slice,
sync::Arc,
};
use ecow::{EcoString, EcoVec, eco_vec};
use indexmap::{IndexMap, IndexSet};
use serde::{Deserialize, Serialize};
use crate::{
Array, Assembly, BindingKind, BindingMeta, Boxed, CONSTANTS, CodeMacro, CodeSpan,
CustomInverse, Diagnostic, DiagnosticKind, DocComment, DocCommentSig, EXAMPLE_UA,
ExactDoubleIterator, Function, FunctionId, FunctionOrigin, GitTarget, Ident, ImplPrimitive,
IndexMacro, InputSrc, IntoInputSrc, IntoSysBackend, Node, NumericSubscript, PrimClass,
Primitive, Purity, RunMode, SUBSCRIPT_DIGITS, SemanticComment, SigNode, Signature, Sp, Span,
SubSide, Subscript, SysBackend, Uiua, UiuaError, UiuaErrorKind, UiuaResult, VERSION, Value,
algorithm::ga::{self, Spec},
ast::*,
check::nodes_sig,
format::{format_word, format_words},
function::DynamicFunction,
lsp::{CodeMeta, Completion, ImportSrc, SetInverses, SigDecl},
parse::{
flip_unsplit_items, flip_unsplit_lines, ident_modifier_args, max_placeholder, parse,
split_items, split_words,
},
};
pub(crate) use modifier::*;
pub use pre_eval::PreEvalMode;
#[derive(Clone)]
pub struct Compiler {
pub(crate) asm: Assembly,
pub(crate) code_meta: CodeMeta,
current_bindings: Vec<CurrentBinding>,
next_global: usize,
pub(crate) scope: Scope,
higher_scopes: Vec<Scope>,
mode: RunMode,
current_imports: EcoVec<PathBuf>,
externals: HashMap<Ident, usize>,
comptime_depth: usize,
in_try: bool,
in_fill: bool,
errors: Vec<UiuaError>,
deprecated_prim_errors: HashSet<Primitive>,
deprecated_const_errors: HashSet<&'static str>,
diagnostics: BTreeSet<Diagnostic>,
pub(crate) print_diagnostics: bool,
comptime: bool,
pre_eval_mode: PreEvalMode,
macro_env: Uiua,
start_addrs: Vec<usize>,
prim_arg_bindings: HashMap<(Primitive, Ident), usize>,
}
impl Default for Compiler {
fn default() -> Self {
Compiler {
asm: Assembly::default(),
code_meta: CodeMeta::default(),
current_bindings: Vec::new(),
next_global: 0,
scope: Scope::default(),
higher_scopes: Vec::new(),
mode: RunMode::All,
current_imports: EcoVec::new(),
externals: HashMap::new(),
comptime_depth: 0,
in_try: false,
in_fill: false,
errors: Vec::new(),
deprecated_prim_errors: HashSet::new(),
deprecated_const_errors: HashSet::new(),
diagnostics: BTreeSet::new(),
print_diagnostics: false,
comptime: true,
pre_eval_mode: PreEvalMode::default(),
macro_env: Uiua::default(),
start_addrs: Vec::new(),
prim_arg_bindings: HashMap::new(),
}
}
}
#[derive(Debug, Default)]
struct BindingPrelude {
comment: Option<EcoString>,
track_caller: bool,
no_inline: bool,
external: bool,
deprecation: Option<EcoString>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LocalNames(IndexMap<Ident, Vec<LocalIndex>>);
#[allow(missing_docs)]
impl LocalNames {
pub fn insert(&mut self, name: impl Into<Ident>, local: LocalIndex) {
self.0.entry(name.into()).or_default().push(local);
}
pub fn remove(&mut self, name: &str) {
self.0.swap_remove(name);
}
pub fn visible_iter(&self) -> impl Iterator<Item = (&Ident, LocalIndex)> {
(self.0.iter()).map(|(name, locals)| (name, *locals.last().unwrap()))
}
pub fn into_visible_iter(self) -> impl Iterator<Item = (Ident, LocalIndex)> {
(self.0.into_iter()).map(|(name, locals)| (name, locals.into_iter().next_back().unwrap()))
}
pub fn all_iter(&self) -> impl Iterator<Item = (&Ident, LocalIndex)> {
(self.0.iter()).flat_map(|(name, locals)| locals.iter().map(move |local| (name, *local)))
}
pub fn get_only_module(&self, name: &str, asm: &Assembly) -> Option<LocalIndex> {
let locals = self.0.get(name)?;
(locals.iter().rev())
.find(|local| asm.bindings[local.index].kind.is_module())
.copied()
}
pub fn get_only_function(&self, name: &str, asm: &Assembly) -> Option<LocalIndex> {
let locals = self.0.get(name)?;
(locals.iter().rev())
.find(|local| asm.bindings[local.index].kind.has_sig())
.copied()
}
pub fn get_prefer_module(&self, name: &str, asm: &Assembly) -> Option<LocalIndex> {
self.get_only_module(name, asm)
.or_else(|| self.get_last(name))
}
pub fn get_prefer_function(&self, name: &str, asm: &Assembly) -> Option<LocalIndex> {
self.get_only_function(name, asm)
.or_else(|| self.get_last(name))
}
pub fn get_last(&self, name: &str) -> Option<LocalIndex> {
self.0.get(name)?.last().copied()
}
pub fn extend_from_other(&mut self, other: Self) {
for (name, locals) in other.0 {
self.0.entry(name).or_default().extend(locals);
}
}
pub fn contains_key(&self, name: &str) -> bool {
self.0.contains_key(name)
}
}
impl Extend<(Ident, LocalIndex)> for LocalNames {
fn extend<T: IntoIterator<Item = (Ident, LocalIndex)>>(&mut self, iter: T) {
for (name, local) in iter {
self.0.entry(name).or_default().push(local);
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Module {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comment: Option<EcoString>,
pub names: LocalNames,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
data_func: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
experimental: bool,
}
impl AsRef<Assembly> for Compiler {
fn as_ref(&self) -> &Assembly {
&self.asm
}
}
impl AsMut<Assembly> for Compiler {
fn as_mut(&mut self) -> &mut Assembly {
&mut self.asm
}
}
#[derive(Debug, Clone)]
struct CurrentBinding {
name: Ident,
signature: Option<Signature>,
recurses: usize,
global_index: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct Scope {
kind: ScopeKind,
file_path: Option<PathBuf>,
comment: Option<EcoString>,
names: LocalNames,
has_data_def: bool,
is_data_func: bool,
data_variants: IndexSet<EcoString>,
pub experimental: bool,
experimental_error: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ScopeKind {
File(FileScopeKind),
Module(Ident),
AllInModule,
Macro(Option<MacroLocal>),
Binding,
Function,
Test,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FileScopeKind {
Source,
Git,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MacroLocal {
macro_index: usize,
expansion_index: Option<usize>,
}
impl Default for Scope {
fn default() -> Self {
Self {
kind: ScopeKind::File(FileScopeKind::Source),
file_path: None,
comment: None,
names: LocalNames::default(),
has_data_def: false,
is_data_func: false,
data_variants: IndexSet::new(),
experimental: false,
experimental_error: false,
}
}
}
impl Scope {
pub(crate) fn add_module_name(&mut self, name: EcoString, local: LocalIndex) {
self.names.remove(format!("{name}!").as_str());
self.names.insert(name, local);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct LocalIndex {
pub index: usize,
#[serde(default = "tru", skip_serializing_if = "std::ops::Not::not")]
pub public: bool,
}
fn tru() -> bool {
true
}
impl Compiler {
pub fn new() -> Self {
Self::default()
}
pub fn with_backend(backend: impl IntoSysBackend) -> Self {
Self {
macro_env: Uiua::with_backend(backend.into_sys_backend()),
..Self::default()
}
}
pub fn with_assembly(self, asm: Assembly) -> Self {
Self { asm, ..self }
}
pub fn assembly(&self) -> &Assembly {
&self.asm
}
pub fn assembly_mut(&mut self) -> &mut Assembly {
&mut self.asm
}
pub fn code_meta(&self) -> &CodeMeta {
&self.code_meta
}
pub fn code_meta_mut(&mut self) -> &mut CodeMeta {
&mut self.code_meta
}
pub fn finish(&mut self) -> Assembly {
take(&mut self.asm)
}
pub fn comptime(&mut self, comptime: bool) -> &mut Self {
self.comptime = comptime;
self
}
pub fn pre_eval_mode(&mut self, mode: PreEvalMode) -> &mut Self {
self.pre_eval_mode = mode;
self
}
pub fn print_diagnostics(&mut self, print_diagnostics: bool) -> &mut Self {
self.print_diagnostics = print_diagnostics;
self
}
pub fn mode(&mut self, mode: RunMode) -> &mut Self {
self.mode = mode;
self
}
pub fn experimental(&mut self, experimental: bool) -> &mut Self {
self.scope.experimental = experimental;
self
}
pub fn backend(&self) -> Arc<dyn SysBackend> {
self.macro_env.rt.backend.clone()
}
pub fn downcast_backend<T: SysBackend>(&self) -> Option<&T> {
self.macro_env.downcast_backend()
}
pub fn downcast_backend_mut<T: SysBackend>(&mut self) -> Option<&mut T> {
self.macro_env.downcast_backend_mut()
}
pub fn take_backend<T: SysBackend + Default>(&mut self) -> Option<T> {
self.macro_env.take_backend()
}
pub fn set_backend<T: SysBackend>(&mut self, backend: T) {
self.macro_env.rt.backend = Arc::new(backend);
}
pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> UiuaResult<&mut Self> {
let path = path.as_ref();
let input: EcoString = fs::read_to_string(path)
.map_err(|e| UiuaErrorKind::Load(path.into(), e.into()))?
.into();
self.asm.inputs.files.insert(path.into(), input.clone());
self.load_impl(&input, InputSrc::File(path.into()))
}
pub fn load_str(&mut self, input: &str) -> UiuaResult<&mut Self> {
let src = self.asm.inputs.add_src((), input);
self.load_impl(input, src)
}
pub fn load_str_src(&mut self, input: &str, src: impl IntoInputSrc) -> UiuaResult<&mut Self> {
let src = self.asm.inputs.add_src(src, input);
self.load_impl(input, src)
}
fn scopes(&self) -> impl ExactDoubleIterator<Item = &Scope> {
once(&self.scope)
.chain(self.higher_scopes.iter().rev())
.collect::<Vec<_>>()
.into_iter()
}
#[allow(dead_code)]
fn scopes_mut(&mut self) -> impl Iterator<Item = &mut Scope> {
once(&mut self.scope).chain(self.higher_scopes.iter_mut().rev())
}
fn scopes_to_file(&self) -> impl Iterator<Item = &Scope> {
let already_file = matches!(self.scope.kind, ScopeKind::File(_));
once(&self.scope).chain(
(!already_file)
.then(|| {
let file_index = self
.higher_scopes
.iter()
.rposition(|s| matches!(s.kind, ScopeKind::File(_)))?;
Some(self.higher_scopes[file_index..].iter().rev())
})
.flatten()
.into_iter()
.flatten(),
)
}
fn in_scope<T>(
&mut self,
kind: ScopeKind,
f: impl FnOnce(&mut Self) -> UiuaResult<T>,
) -> UiuaResult<(Module, T)> {
self.higher_scopes.push(take(&mut self.scope));
self.scope.kind = kind;
let res = f(self);
let scope = replace(&mut self.scope, self.higher_scopes.pop().unwrap());
let res = res?;
let module = Module {
path: None,
comment: scope.comment,
names: scope.names,
data_func: scope.is_data_func,
experimental: scope.experimental,
};
Ok((module, res))
}
fn load_impl(&mut self, input: &str, src: InputSrc) -> UiuaResult<&mut Self> {
let node_start = self.asm.root.len();
let (items, errors, diagnostics) = parse(input, src.clone(), &mut self.asm.inputs);
for diagnostic in diagnostics {
self.emit_diagnostic_impl(diagnostic);
}
if !errors.is_empty() {
return Err(UiuaErrorKind::Parse(errors, self.asm.inputs.clone().into()).into());
}
if let InputSrc::File(path) = &src {
self.current_imports.push(path.to_path_buf());
self.scope.file_path = Some(if path.is_absolute() {
current_dir()
.ok()
.and_then(|dir| pathdiff::diff_paths(path, dir))
.unwrap_or_else(|| path.to_path_buf())
} else {
path.to_path_buf()
});
}
let base = 0u8;
self.start_addrs.push(&base as *const u8 as usize);
let res = self.catching_crash(input, |env| env.items(items, ItemCompMode::TopLevel));
self.start_addrs.pop();
let do_optimize = match src {
InputSrc::File(_) => self.current_imports.len() <= 1,
_ => self.current_imports.is_empty(),
};
if do_optimize {
self.asm.root.optimize_full();
for i in 0..self.asm.functions.len() {
self.asm.functions.make_mut()[i].optimize_full();
if let Some((root, errs)) = self.pre_eval(&self.asm.functions[i]) {
self.asm.functions.make_mut()[i] = root;
self.errors.extend(errs);
self.asm.functions.make_mut()[i].optimize_full();
}
}
}
if self.print_diagnostics {
for diagnostic in self.take_diagnostics() {
eprintln!("{}", diagnostic.report());
}
}
let exports = Arc::make_mut(&mut self.asm.exports);
for (name, locals) in &self.scope.names.0 {
exports.insert(name.clone(), locals.last().unwrap().index);
}
self.code_meta.top_level_names = (self.scope.names.all_iter())
.map(|(name, local)| (name.clone(), local))
.collect();
if let InputSrc::File(_) = &src {
self.current_imports.pop();
}
match res {
Err(e) | Ok(Err(e)) => {
self.asm.root.truncate(node_start);
self.errors.push(e);
}
_ => {}
}
match self.errors.len() {
0 => Ok(self),
1 => Err(self.errors.pop().unwrap()),
_ => Err(UiuaError::from_multi(take(&mut self.errors))),
}
}
fn catching_crash<T>(
&mut self,
input: impl fmt::Display,
f: impl FnOnce(&mut Self) -> T,
) -> UiuaResult<T> {
match catch_unwind(AssertUnwindSafe(|| f(self))) {
Ok(res) => Ok(res),
Err(_) => Err(UiuaErrorKind::CompilerPanic(format!(
"\
The compiler has crashed!
Hooray! You found a bug!
Please report this at http://github.com/uiua-lang/uiua/issues/new \
or on Discord at https://discord.gg/9CU2ME4kmn.
Uiua version {VERSION}
code:
{input}"
))
.into()),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ItemCompMode {
TopLevel,
Function,
CodeMacro,
}
impl Compiler {
fn items(&mut self, items: Vec<Item>, mode: ItemCompMode) -> UiuaResult {
let mut started = false;
let mut comment = String::new();
let mut items_iter = items.iter();
while let Some(Item::Words(line)) = items_iter.next() {
for word in line {
match &word.value {
Word::Comment(c) => {
let mut c = c.as_str();
if c.starts_with(' ') {
c = &c[1..];
}
comment.push_str(c);
started = true;
}
Word::Spaces => {}
_ => {
comment.clear();
break;
}
}
}
if line.is_empty() && started {
break;
}
}
comment.push('\n');
if !comment.trim().is_empty() {
self.scope.comment = Some(comment.trim().into());
}
let mut prelude = BindingPrelude::default();
let mut item_errored = false;
let mut item_queue = VecDeque::from(flip_unsplit_items(split_items(items)));
while let Some(item) = item_queue.pop_front() {
let must_run = mode == ItemCompMode::CodeMacro
|| 'blk: {
let Item::Words(words) = &item else {
break 'blk false;
};
if (words.iter().find(|w| w.value.is_code()))
.is_some_and(|w| matches!(w.value, Word::Primitive(Primitive::Assert)))
{
break 'blk true;
}
item_queue.iter().any(|item| match item {
Item::Binding(binding)
if (binding.words.iter())
.filter(|word| word.value.is_code())
.count()
== 0 =>
{
true
}
Item::Words(line) => (line.iter().find(|w| w.value.is_code()))
.is_some_and(|w| matches!(w.value, Word::Primitive(Primitive::Assert))),
_ => false,
})
};
if let Err(e) = self.item(item, must_run, mode, &mut prelude) {
if !item_errored || self.errors.is_empty() {
self.errors.push(e);
}
item_errored = true;
}
}
Ok(())
}
fn item(
&mut self,
item: Item,
must_run: bool,
mode: ItemCompMode,
prelude: &mut BindingPrelude,
) -> UiuaResult {
match item {
Item::Module(m) => self.module(m, take(prelude)),
Item::Words(line) => self.top_level_words(line, must_run, mode, prelude),
Item::Binding(binding) => self.binding(binding, take(prelude)),
Item::Import(import) => self.import(import, take(prelude).comment),
Item::Data(defs) => {
for data in defs {
self.data_def(data, true, take(prelude))?
}
Ok(())
}
}
}
fn prelude_word(&self, word: &Word, prelude: &mut BindingPrelude) -> bool {
match word {
Word::Comment(c) => {
if let Some(curr_com) = &mut prelude.comment {
curr_com.push('\n');
curr_com.push_str(c);
} else {
prelude.comment = Some(c.as_str().into());
}
}
Word::SemanticComment(SemanticComment::NoInline) => prelude.no_inline = true,
Word::SemanticComment(SemanticComment::TrackCaller) => prelude.track_caller = true,
Word::SemanticComment(SemanticComment::External) => prelude.external = true,
Word::SemanticComment(SemanticComment::Deprecated(s)) => {
prelude.deprecation = Some(s.clone())
}
_ => return false,
}
true
}
fn top_level_words(
&mut self,
mut line: Vec<Sp<Word>>,
must_run: bool,
mode: ItemCompMode,
prelude: &mut BindingPrelude,
) -> UiuaResult {
let mut words = line.iter().filter(|w| !matches!(w.value, Word::Spaces));
if words.clone().count() == 1 && self.prelude_word(&words.next().unwrap().value, prelude) {
line.clear();
} else {
*prelude = BindingPrelude::default();
}
fn words_should_run_anyway(words: &[Sp<Word>]) -> bool {
let mut anyway = false;
recurse_words(words, &mut |w| {
anyway = anyway
|| matches!(&w.value, Word::SemanticComment(_))
|| matches!(&w.value, Word::Modified(m)
if matches!(m.modifier.value, Modifier::Ref(_)))
});
anyway
}
let in_test = self.scopes().any(|sc| sc.kind == ScopeKind::Test);
let can_run = mode != ItemCompMode::TopLevel
|| match self.mode {
RunMode::Normal => !in_test,
RunMode::Test => in_test,
RunMode::All => true,
};
if line.is_empty() || !(can_run || must_run || words_should_run_anyway(&line)) {
return Ok(());
}
let span = (line.first().unwrap().span.clone()).merge(line.last().unwrap().span.clone());
if max_placeholder(&line).is_some() {
self.add_error(
span.clone(),
"Cannot use placeholder outside of an index macro",
);
}
let binding_count_before = self.asm.bindings.len();
let root_len_before = self.asm.root.len();
let error_count_before = self.errors.len();
let mut line_node = self.line(line)?;
if matches!(self.scope.kind, ScopeKind::File(_)) {
if let Err((e, f, mut spans)) = line_node.check_callability(&self.asm) {
let e = e.clone();
if let Some(f_id) = f.map(|f| f.id.clone()) {
let src_span = self.get_span(spans.remove(0));
let call_span = self.get_span(spans.pop().unwrap());
let error = self.error_with_info(
call_span,
format!("Cannot call {f_id} because of an inversion error"),
[(src_span, e)],
);
self.errors.push(error);
} else {
let span = self.get_span(spans.pop().unwrap());
self.add_error(span, e);
}
}
if let Ok(sig) = line_node.sig() {
self.asm.line_sigs.insert(span.end.line, sig);
}
}
let binding_count_after = self.asm.bindings.len();
let error_count_after = self.errors.len();
if mode != ItemCompMode::Function {
line_node.optimize_full();
}
match line_node.sig() {
Ok(sig) => {
if self.mode != RunMode::Normal
&& mode != ItemCompMode::Function
&& !self
.scopes()
.any(|sc| sc.kind == ScopeKind::File(FileScopeKind::Git))
{
let test_assert = line_node
.last_mut_recursive(&mut self.asm, |node| {
if let &mut Node::Prim(Primitive::Assert, span) = node {
*node = Node::ImplPrim(ImplPrimitive::TestAssert, span);
true
} else {
false
}
})
.unwrap_or(false);
if test_assert {
self.asm.test_assert_count += 1;
}
}
if mode != ItemCompMode::Function
&& self.pre_eval_mode > PreEvalMode::Line
&& error_count_after == error_count_before
&& binding_count_before == binding_count_after
&& root_len_before == self.asm.root.len()
&& !line_node.is_empty()
&& self.asm.root.len() >= sig.args()
&& (self.asm.root.iter().rev().take(sig.args()))
.all(|node| matches!(node, Node::Push(_)))
{
let mut node = Node::from(&self.asm.root[self.asm.root.len() - sig.args()..]);
node.extend(line_node.iter().cloned());
if let Some((node, errs)) = self.pre_eval(&node) {
self.errors.extend(errs);
if !matches!(line_node.last().unwrap(), Node::SetOutputComment { .. })
&& node.iter().all(|node| matches!(node, Node::Push(_)))
{
let vals: Vec<_> = node
.iter()
.map(|node| match node {
Node::Push(val) => val.clone(),
_ => unreachable!(),
})
.collect();
self.code_meta.top_level_values.insert(span, vals);
}
self.asm.root.truncate(self.asm.root.len() - sig.args());
line_node = node;
}
}
}
Err(e) => self.add_error(span, e),
}
self.asm.root.push(line_node);
Ok(())
}
fn compile_bind_function(
&mut self,
name: Ident,
local: LocalIndex,
function: Function,
span: usize,
meta: BindingMeta,
) -> UiuaResult {
self.scope.names.insert(name.clone(), local);
let span = if span == 0 {
Some(CodeSpan::literal(name))
} else {
self.get_span(span).clone().code()
};
self.asm
.add_binding_at(local, BindingKind::Func(function), span, meta);
Ok(())
}
fn compile_bind_const(
&mut self,
name: Ident,
local: LocalIndex,
value: Option<Value>,
span: usize,
meta: BindingMeta,
) {
let span = self.get_span(span).clone().code();
self.asm
.add_binding_at(local, BindingKind::Const(value), span, meta);
self.scope.names.insert(name, local);
}
fn line(&mut self, mut line: Vec<Sp<Word>>) -> UiuaResult<Node> {
let comment_sig = line_sig(&line);
let output_comment = if line
.last()
.is_some_and(|w| matches!(w.value, Word::OutputComment { .. }))
{
let word = line.pop().unwrap();
Some(self.word(word)?)
} else {
None
};
let mut node = self.words(line)?;
node.extend(output_comment);
if let Some(comment_sig) = comment_sig {
self.apply_node_comment(&mut node, &comment_sig.value, "Line", &comment_sig.span);
}
Ok(node)
}
fn apply_node_comment(
&mut self,
node: &mut Node,
comment_sig: &DocCommentSig,
name: &str,
span: &CodeSpan,
) {
let mut spandex: Option<usize> = None;
if let Ok(sig) = node.sig()
&& !comment_sig.matches_sig(sig)
{
let span = *spandex.get_or_insert_with(|| self.add_span(span.clone()));
self.emit_diagnostic(
format!(
"{name} comment describes {}, \
but its code has signature {sig}",
comment_sig.sig_string()
),
DiagnosticKind::Warning,
self.get_span(span).clone().code().unwrap(),
);
}
if comment_sig.label {
if let Some(args) = &comment_sig.args {
let span = *spandex.get_or_insert_with(|| self.add_span(span.clone()));
let labels = Node::bracket(
args.iter()
.map(|a| Node::Label(a.name.clone(), span).sig_node().unwrap()),
span,
);
node.prepend(labels);
}
if let Some(outputs) = &comment_sig.outputs {
let span = *spandex.get_or_insert_with(|| self.add_span(span.clone()));
let labels = Node::bracket(
outputs
.iter()
.map(|o| Node::Label(o.name.clone(), span).sig_node().unwrap()),
span,
);
node.push(labels);
}
}
}
fn args(&mut self, words: Vec<Sp<Word>>) -> UiuaResult<EcoVec<SigNode>> {
(words.into_iter())
.filter(|w| w.value.is_code())
.map(|w| self.word_sig(w))
.collect()
}
fn words_sig(&mut self, words: Vec<Sp<Word>>) -> UiuaResult<SigNode> {
let span = words
.first()
.zip(words.last())
.map(|(f, l)| f.span.clone().merge(l.span.clone()));
let node = self.words(words)?;
let sig = if let Some(span) = span {
self.sig_of(&node, &span)?
} else {
Signature::new(0, 0)
};
Ok(SigNode::new(sig, node))
}
fn words(&mut self, mut words: Vec<Sp<Word>>) -> UiuaResult<Node> {
words.retain(|word| word.value.is_code());
let mut sem = None;
if let Some(word) = words.last()
&& let Word::SemanticComment(com) = &word.value
{
let com = com.clone();
sem = Some(words.pop().unwrap().span.sp(com));
}
words.reverse();
#[derive(Debug, Clone)]
struct PrevWord(
Option<Primitive>,
Option<Primitive>,
Option<Signature>,
CodeSpan,
);
let mut a: Option<PrevWord> = None;
let mut b: Option<PrevWord> = None;
let mut nodes = Node::empty();
for word in flip_unsplit_lines(split_words(words)).into_iter().flatten() {
let span = word.span.clone();
let (mut modif, mut prim) = (None, None);
match &word.value {
Word::Primitive(p) => prim = Some(*p),
Word::Modified(m) => {
if let Modifier::Primitive(mprim) = &m.modifier.value
&& let Some(first) = m.operands.first()
&& let Word::Primitive(p) = first.value
{
modif = Some(*mprim);
prim = Some(p);
}
}
_ => {}
}
match (a, &b, prim.filter(|_| modif.is_none())) {
(
Some(PrevWord(None, Some(Primitive::Dup), _, a_span)),
Some(PrevWord(_, _, Some(sig), _)),
Some(Primitive::Flip),
) if *sig == (1, 1) => {
self.emit_diagnostic(
format!(
"Prefer {} over {} {} here",
Primitive::On,
Primitive::Flip,
Primitive::Dup
),
DiagnosticKind::Style,
a_span.merge(span.clone()),
);
}
(
Some(PrevWord(None, Some(Primitive::Dup), _, a_span)),
Some(PrevWord(None, Some(Primitive::Unique), _, _)),
Some(Primitive::Keep),
) => {
self.emit_diagnostic(
format!(
"Prefer {} over {}{}{}",
Primitive::Deduplicate.format(),
Primitive::Keep,
Primitive::Unique,
Primitive::Dup
),
DiagnosticKind::Advice,
a_span.merge(span.clone()),
);
}
(
_,
Some(PrevWord(None, Some(Primitive::Reverse), _, b_span)),
Some(Primitive::First),
) => {
self.emit_diagnostic(
format!(
"Prefer {} over {}{}",
Primitive::Last.format(),
Primitive::First,
Primitive::Reverse
),
DiagnosticKind::Advice,
b_span.clone().merge(span.clone()),
);
}
(
Some(PrevWord(None, Some(Primitive::Dup), _, a_span)),
Some(PrevWord(None, Some(Primitive::Rise), _, _)),
Some(Primitive::Select),
) => {
self.emit_diagnostic(
format!(
"Prefer {} over {}{}{}",
Primitive::Sort.format(),
Primitive::Select,
Primitive::Rise,
Primitive::Dup
),
DiagnosticKind::Advice,
a_span.merge(span.clone()),
);
}
(
_,
Some(PrevWord(Some(Primitive::By), Some(Primitive::Rise), _, b_span)),
Some(Primitive::Select),
) => {
self.emit_diagnostic(
format!(
"Prefer {} over {}{}{}",
Primitive::Sort.format(),
Primitive::Select,
Primitive::By,
Primitive::Rise
),
DiagnosticKind::Advice,
b_span.clone().merge(span.clone()),
);
}
_ => {}
}
let node = self.word(word)?;
let sig = node.sig().ok();
nodes.push(node);
a = b;
b = Some(PrevWord(modif, prim, sig, span));
}
if let Some(sem) = sem {
nodes = self.semantic_comment(sem.value, sem.span, nodes);
}
Ok(nodes)
}
fn word_sig(&mut self, word: Sp<Word>) -> UiuaResult<SigNode> {
let span = word.span.clone();
let node = self.word(word)?;
let sig = self.sig_of(&node, &span)?;
Ok(SigNode::new(sig, node))
}
fn check_depth(&mut self, span: &CodeSpan) -> UiuaResult {
const MAX_RECURSION_DEPTH: usize =
match (cfg!(target_arch = "wasm32"), cfg!(debug_assertions)) {
(false, false) => (512 + 256 + 64) * 1024 * 2,
(false, true) => (512 + 256 + 128) * 1024,
(true, false) => 640 * 1024,
(true, true) => 640 * 1024,
};
let start_addr = *self.start_addrs.first().unwrap();
let curr = 0u8;
let curr_addr = &curr as *const u8 as usize;
let diff = curr_addr.abs_diff(start_addr);
if diff > MAX_RECURSION_DEPTH {
return Err(self.error(span.clone(), "Compilation recursion limit reached"));
}
Ok(())
}
fn word(&mut self, word: Sp<Word>) -> UiuaResult<Node> {
self.check_depth(&word.span)?;
Ok(match word.value {
Word::Number(NumWord::Real(n), _) => Node::new_push(n),
Word::Number(NumWord::Infinity(false), _) => Node::new_push(f64::INFINITY),
Word::Number(NumWord::Infinity(true), _) => Node::new_push(f64::NEG_INFINITY),
Word::Number(NumWord::Complex(c), _) => Node::new_push(c),
Word::Number(NumWord::Err(s), _) => {
self.add_error(word.span.clone(), format!("Invalid number `{s}`"));
Node::new_push(0.0)
}
Word::Char(c) => {
let val: Value = if c.chars().count() == 1 {
c.chars().next().unwrap().into()
} else {
c.into()
};
Node::new_push(val)
}
Word::String(s) => Node::new_push(s),
Word::MultilineString(lines) => {
let mut s = EcoVec::new();
for (i, line) in lines.into_iter().enumerate() {
if i > 0 {
s.push('\n');
}
s.extend(line.value.chars());
}
Node::new_push(s)
}
Word::Label(label) => Node::Label(label.into(), self.add_span(word.span.clone())),
Word::FormatString(frags) => {
let parts = frags.into_iter().map(Into::into).collect();
let span = self.add_span(word.span.clone());
Node::Format(parts, span)
}
Word::MultilineFormatString(lines) => {
let span = self.add_span(word.span.clone());
let mut curr_part = EcoString::new();
let mut parts = EcoVec::new();
for (l, line) in lines.into_iter().enumerate() {
if l > 0 {
curr_part.push('\n');
}
for (f, frag) in line.value.into_iter().enumerate() {
if f > 0 {
parts.push(take(&mut curr_part));
}
curr_part.push_str(&frag);
}
}
parts.push(curr_part);
Node::Format(parts, span)
}
Word::Ref(r, chained) => r.chain_refs(chained).map(|r| self.reference(r)).collect(),
Word::IncompleteRef(path) => 'blk: {
if let Some((_, locals)) = self.ref_path(&path)? {
self.add_error(
path.last().unwrap().tilde_span.clone(),
"Incomplete module reference",
);
for (local, comp) in locals.iter().zip(path) {
self.validate_local(&comp.module.value, *local, &comp.module.span);
self.code_meta
.global_references
.insert(comp.module.span, local.index);
}
let index = locals.last().unwrap().index;
let names = match &self.asm.bindings[index].kind {
BindingKind::Module(module) => &module.names,
_ => break 'blk Node::empty(),
};
let mut completions = Vec::new();
for (name, local) in names.visible_iter() {
if !local.public {
continue;
}
completions.push(Completion {
text: name.into(),
index: local.index,
replace: false,
});
}
if !completions.is_empty() {
self.code_meta
.completions
.insert(word.span.clone(), completions);
}
}
Node::empty()
}
Word::Strand(items) => {
if (items.iter()).all(|w| !w.value.is_code() || matches!(w.value, Word::Char(_))) {
self.emit_diagnostic(
"Stranded characters should instead be written as a string",
DiagnosticKind::Advice,
word.span.clone(),
);
}
let just_spans: Vec<_> = items.iter().map(|w| w.span.clone()).collect();
let op_nodes = (items.into_iter().rev())
.map(|word| self.word_sig(word))
.collect::<UiuaResult<Vec<_>>>()?;
let has_functions = op_nodes.iter().any(|sn| sn.sig.args() > 0);
if has_functions {
return Err(
self.error(word.span.clone(), "Functions are not allowed in strands")
);
}
self.code_meta.strands.insert(word.span.clone(), just_spans);
let inner = Node::from_iter(op_nodes.into_iter().map(|sn| sn.node));
let span = self.add_span(word.span.clone());
if inner.iter().all(|instr| matches!(instr, Node::Push(_))) {
let values: Vec<_> = inner
.iter()
.rev()
.map(|instr| match instr {
Node::Push(v) => v.clone(),
_ => unreachable!(),
})
.collect();
match Value::from_row_values(values, &(&word.span, &self.asm.inputs)) {
Ok(val) => return Ok(Node::new_push(val)),
Err(e) if e.meta.is_fill => {}
Err(e) => return Err(e),
}
}
let sig = self.sig_of(&inner, &word.span)?;
Node::Array {
len: sig.outputs(),
inner: inner.into(),
boxed: false,
allow_ext: false,
prim: None,
span,
}
}
Word::Array(arr) => {
if let Some(down_span) = &arr.down_span {
self.experimental_error(down_span, || {
"Lexical ordering is experimental. To use it, \
add `# Experimental!` to the top of the file."
});
}
let line_count = arr.lines.len();
if line_count <= 1
&& arr.lines.iter().any(|item| match item {
Item::Words(words) => {
words.iter().any(|w| matches!(w.value, Word::Char(_)))
}
_ => false,
})
&& arr.lines.iter().all(|item| match item {
Item::Words(words) => words
.iter()
.all(|w| !w.value.is_code() || matches!(w.value, Word::Char(_))),
_ => false,
})
{
self.emit_diagnostic(
"Characters in an array should instead be written as a string",
DiagnosticKind::Advice,
word.span.clone(),
);
}
if !arr.boxes
&& (arr.word_lines().flatten())
.filter(|w| w.value.is_code())
.all(|w| w.value.is_literal() && !matches!(w.value, Word::Strand(_)))
{
let just_spans: Vec<_> = (arr.word_lines().rev().flatten())
.filter(|w| w.value.is_code())
.map(|w| w.span.clone())
.collect();
self.code_meta
.array_inner_spans
.insert(word.span.clone(), just_spans);
}
let any_contents = arr.word_lines().flatten().any(|w| w.value.is_code());
let (mut word_lines, item_lines): (Vec<_>, Vec<_>) = arr
.lines
.into_iter()
.partition(|item| matches!(item, Item::Words(_)));
if arr.down_span.is_none() {
word_lines.reverse();
}
let root_start = self.asm.root.len();
self.in_scope(ScopeKind::Function, |comp| {
comp.items(item_lines, ItemCompMode::Function)?;
comp.items(word_lines, ItemCompMode::Function)
})?;
let inner = self.asm.root.split_off(root_start);
let len = match inner.sig() {
Ok(sig) => {
if sig.outputs() == 0 && any_contents {
self.emit_diagnostic(
"Array wraps function with no outputs. This is probably not what you want.",
DiagnosticKind::Advice,
word.span.clone(),
)
}
sig.outputs()
}
Err(e) => {
return Err(self.error(
word.span.clone(),
format!("Cannot infer array signature: {e}"),
));
}
};
let span = self.add_span(word.span.clone());
if inner.iter().all(|instr| matches!(instr, Node::Push(_))) {
let empty = inner.is_empty();
let values = inner.iter().rev().map(|instr| match instr {
Node::Push(v) => v.clone(),
_ => unreachable!(),
});
let res = if arr.boxes {
if empty {
Ok(Array::<Boxed>::default().into())
} else {
Value::from_row_values(
values
.map(|v| Value::Box(Boxed(v).into()))
.collect::<Vec<_>>(),
&(&word.span, &self.asm.inputs),
)
}
} else {
Value::from_row_values(
values.collect::<Vec<_>>(),
&(&word.span, &self.asm.inputs),
)
};
match res {
Ok(val) => {
self.code_meta
.array_shapes
.insert(word.span.clone(), val.shape.clone());
return Ok(Node::new_push(val));
}
Err(e) if e.meta.is_fill => {}
Err(e) => return Err(e),
}
}
Node::Array {
len,
inner: inner.into(),
boxed: arr.boxes,
allow_ext: false,
prim: None,
span,
}
}
Word::Func(func) => self.func(func, word.span)?,
Word::Pack(pack) => {
self.add_error(
word.span.clone(),
"Function packs are not allowed without a modifier",
);
if let Some(first) = pack.into_lexical_order().next() {
self.func(first.value, first.span)?
} else {
Node::empty()
}
}
Word::Primitive(p) => self.primitive(p, word.span),
Word::Modified(m) => self.modified(*m, None)?,
Word::Placeholder(_) => {
Node::empty()
}
Word::SemanticComment(_) => {
Node::empty()
}
Word::OutputComment { i, n } => Node::SetOutputComment { i, n },
Word::Subscripted(sub) => self.subscript(*sub, word.span)?,
Word::Comment(_) | Word::Spaces | Word::BreakLine | Word::FlipLine => Node::empty(),
Word::InlineMacro(_) => {
self.add_error(
word.span.clone(),
"Inline macro was not parsed as a modifier. \
This is a bug in the interpreter",
);
Node::empty()
}
})
}
fn force_sig(
&mut self,
mut node: Node,
new_sig: Signature,
span: &CodeSpan,
) -> UiuaResult<Node> {
let Ok(sig) = node.sig() else {
return Ok(node);
};
if new_sig == sig {
return Ok(node);
}
let delta = sig.outputs() as isize - sig.args() as isize;
let new_delta = new_sig.outputs() as isize - new_sig.args() as isize;
match delta.cmp(&new_delta) {
Ordering::Equal => {
if sig.args() < new_sig.args() {
let spandex = self.add_span(span.clone());
let mut dip = Node::empty();
for _ in 0..new_sig.args() {
dip = Node::Mod(Primitive::Dip, eco_vec![dip.sig_node().unwrap()], spandex);
}
node.prepend(dip);
}
self.emit_diagnostic(
format!("Signature mismatch: declared {new_sig} but inferred {sig}"),
DiagnosticKind::Warning,
span.clone(),
);
}
Ordering::Less => {
if new_sig.outputs() > 10 {
return Err(self.error(
span.clone(),
format!(
"Signature mismatch: declared {new_sig} but inferred {sig}. \
Signatures with more than 10 outputs cannot be forced."
),
));
}
let diff = (new_delta - delta).unsigned_abs();
let spandex = self.add_span(span.clone());
let mut extra = Node::from_iter(
(0..diff).map(|i| Node::new_push(Boxed(Value::from(format!("dbg-{}", i + 1))))),
);
for _ in 0..sig.outputs() {
extra = Node::Mod(Primitive::Dip, eco_vec![extra.sig_node().unwrap()], spandex);
}
node.push(extra);
self.emit_diagnostic(
format!(
"Signature mismatch: declared {new_sig} but inferred {sig}. \
{diff} debug output{} will be generated.",
if diff == 1 { "" } else { "s" }
),
DiagnosticKind::Warning,
span.clone(),
);
}
Ordering::Greater => {
if new_sig.args() > 10 {
return Err(self.error(
span.clone(),
format!(
"Signature mismatch: declared {new_sig} but inferred {sig}. \
Signatures with more than 10 arguments cannot be forced."
),
));
}
let diff = (delta - new_delta).unsigned_abs();
let spandex = self.add_span(span.clone());
let mut pops =
Node::from_iter((0..diff).map(|_| Node::Prim(Primitive::Pop, spandex)));
for _ in 0..new_sig.outputs() {
pops = Node::Mod(Primitive::Dip, eco_vec![pops.sig_node().unwrap()], spandex);
}
node.push(pops);
self.emit_diagnostic(
format!(
"Signature mismatch: declared {new_sig} but inferred {sig}. \
Additional arguments will be popped."
),
DiagnosticKind::Warning,
span.clone(),
);
}
}
Ok(node)
}
#[must_use]
fn semantic_comment(&mut self, comment: SemanticComment, span: CodeSpan, inner: Node) -> Node {
match comment {
SemanticComment::Experimental => {
self.scope.experimental = true;
inner
}
SemanticComment::NoInline => Node::NoInline(inner.into()),
SemanticComment::TrackCaller => Node::TrackCaller(
match self.sig_of(&inner, &span) {
Ok(sig) => SigNode::new(sig, inner),
Err(e) => {
self.errors.push(e);
SigNode::default()
}
}
.into(),
),
SemanticComment::External => inner,
SemanticComment::Deprecated(_) => inner,
SemanticComment::Boo => {
self.add_error(span, "The compiler is scared!");
inner
}
}
}
fn ref_local(&self, r: &Ref) -> UiuaResult<Option<(Vec<LocalIndex>, LocalIndex)>> {
if let Some((names, path_locals)) = self.ref_path(&r.path)? {
if let Some(local) = names
.get_prefer_function(&r.name.value, &self.asm)
.or_else(|| {
(r.name.value.strip_suffix('!')).and_then(|name| {
names.get_prefer_module(name, &self.asm).filter(|local| {
matches!(&self.asm.bindings[local.index].kind, BindingKind::Module(_))
})
})
})
{
if local.public {
Ok(Some((path_locals, local)))
} else {
Err(self.error(
r.name.span.clone(),
format!("`{}` is private", r.name.value),
))
}
} else {
Err(self.error(
r.name.span.clone(),
format!("Item `{}` not found", r.name.value),
))
}
} else if let Some(local) = self.find_name_function(&r.name.value, &r.name.span) {
Ok(Some((Vec::new(), local)))
} else if r.path.is_empty() && CONSTANTS.iter().any(|def| def.name == r.name.value) {
Ok(None)
} else {
Err(self.error(
r.name.span.clone(),
format!("Unknown identifier `{}`", r.name.value),
))
}
}
fn find_name_module(&self, name: &str, span: &CodeSpan) -> Option<LocalIndex> {
self.find_name_impl(name, span, true, false)
}
fn find_name_function(&self, name: &str, span: &CodeSpan) -> Option<LocalIndex> {
self.find_name_impl(name, span, false, false)
}
fn find_name_impl(
&self,
name: &str,
span: &CodeSpan,
prefer_module: bool,
stop_at_binding: bool,
) -> Option<LocalIndex> {
let mut only_modules = [false, true];
if prefer_module {
only_modules.reverse();
}
for only_modules in only_modules {
let mut hit_stop = false;
for scope in self.scopes() {
if matches!(scope.kind, ScopeKind::File(_))
|| stop_at_binding && matches!(scope.kind, ScopeKind::Binding)
{
if hit_stop {
break;
}
hit_stop = true;
}
let local = if only_modules {
scope.names.get_only_module(name, &self.asm)
} else {
scope.names.get_prefer_function(name, &self.asm)
};
if let Some(local) = local {
return Some(local);
}
if let ScopeKind::Macro(Some(mac_local)) = &scope.kind {
let mac = &self.asm.index_macros[&mac_local.macro_index];
if let Some(index) = (mac.locals.iter())
.find(|(sp, _)| sp == span)
.map(|(_, i)| *i)
{
return Some(LocalIndex {
index,
public: true,
});
}
}
}
}
let as_non_macro =
self.find_name_impl(name.strip_suffix('!')?, span, true, stop_at_binding)?;
if let BindingKind::Module(_) | BindingKind::Scope(_) =
self.asm.bindings[as_non_macro.index].kind
{
Some(as_non_macro)
} else {
None
}
}
fn ref_path(
&self,
path: &[RefComponent],
) -> UiuaResult<Option<(&LocalNames, Vec<LocalIndex>)>> {
let Some(first) = path.first() else {
return Ok(None);
};
let mut path_locals = Vec::new();
let module_local = self
.find_name_module(&first.module.value, &first.module.span)
.ok_or_else(|| {
self.error(
first.module.span.clone(),
format!("Unknown import `{}`", first.module.value),
)
})?;
path_locals.push(module_local);
let bkind = &self.asm.bindings[module_local.index].kind;
let mut names = match bkind {
BindingKind::Module(module) => &module.names,
BindingKind::Scope(i) => &self.higher_scopes.get(*i).unwrap_or(&self.scope).names,
BindingKind::Func(_) => {
return Err(self.error(
first.module.span.clone(),
format!("`{}` is a function, not a module", first.module.value),
));
}
BindingKind::Const(_) => {
return Err(self.error(
first.module.span.clone(),
format!("`{}` is a constant, not a module", first.module.value),
));
}
BindingKind::IndexMacro(_) => {
return Err(self.error(
first.module.span.clone(),
format!("`{}` is an index macro, not a module", first.module.value),
));
}
BindingKind::CodeMacro(_) => {
return Err(self.error(
first.module.span.clone(),
format!("`{}` is a code macro, not a module", first.module.value),
));
}
BindingKind::Error => return Ok(None),
};
for comp in path.iter().skip(1) {
let submod_local = names
.get_prefer_module(&comp.module.value, &self.asm)
.ok_or_else(|| {
self.error(
comp.module.span.clone(),
format!("Module `{}` not found", comp.module.value),
)
})?;
path_locals.push(submod_local);
let global = &self.asm.bindings[submod_local.index].kind;
names = match global {
BindingKind::Module(module) => &module.names,
BindingKind::Scope(i) => &self.higher_scopes.get(*i).unwrap_or(&self.scope).names,
BindingKind::Func(_) => {
return Err(self.error(
comp.module.span.clone(),
format!("`{}` is a function, not a module", comp.module.value),
));
}
BindingKind::Const(_) => {
return Err(self.error(
comp.module.span.clone(),
format!("`{}` is a constant, not a module", comp.module.value),
));
}
BindingKind::IndexMacro(_) => {
return Err(self.error(
comp.module.span.clone(),
format!("`{}` is an index macro, not a module", comp.module.value),
));
}
BindingKind::CodeMacro(_) => {
return Err(self.error(
comp.module.span.clone(),
format!("`{}` is a code macro, not a module", comp.module.value),
));
}
BindingKind::Error => return Ok(None),
};
}
Ok(Some((names, path_locals)))
}
fn reference(&mut self, r: Ref) -> Node {
if r.path.is_empty() {
self.ident(r.name.value, r.name.span)
} else {
match self.ref_local(&r) {
Ok(Some((path_locals, local))) => {
self.validate_local(&r.name.value, local, &r.name.span);
for (local, comp) in path_locals.into_iter().zip(&r.path) {
self.validate_local(&comp.module.value, local, &comp.module.span);
(self.code_meta.global_references)
.insert(comp.module.span.clone(), local.index);
}
self.code_meta
.global_references
.insert(r.name.span.clone(), local.index);
self.global_index(local.index, r.name.span)
}
Ok(None) => self.ident(r.name.value, r.name.span),
Err(e) => {
self.errors.push(e);
Node::new_push(Value::default())
}
}
}
}
fn completions(&self, prefix: &str, names: &LocalNames, public_only: bool) -> Vec<Completion> {
let mut completions = Vec::new();
for (name, local) in names.visible_iter() {
if public_only && !local.public {
continue;
}
if name.starts_with(prefix) {
completions.push(Completion {
text: name.into(),
index: local.index,
replace: true,
});
}
let subnames = match &self.asm.bindings[local.index].kind {
BindingKind::Module(m) => &m.names,
_ => continue,
};
let subcompletions = self.completions(prefix, subnames, true);
completions.extend(subcompletions.into_iter().map(|c| Completion {
text: format!("{name}~{}", c.text),
..c
}));
}
completions
}
fn ident(&mut self, ident: Ident, span: CodeSpan) -> Node {
let completions = self.completions(&ident, &self.scope.names, false);
if !completions.is_empty() {
self.code_meta.completions.insert(span.clone(), completions);
}
if let Some(local) = self.find_name_impl(&ident, &span, false, true) {
self.validate_local(&ident, local, &span);
(self.code_meta.global_references).insert(span.clone(), local.index);
self.global_index(local.index, span)
} else if let Some(i) = (self.current_bindings.iter()).position(|curr| curr.name == ident) {
for curr_binding in &mut self.current_bindings[i..] {
curr_binding.recurses += 1;
}
let curr = &mut self.current_bindings[i];
let global_index = curr.global_index;
(self.code_meta.global_references).insert(span.clone(), global_index);
if let Some(sig) = curr.signature.filter(|sig| sig.outputs() <= 10) {
Node::CallGlobal(global_index, sig)
} else {
Node::empty()
}
} else if let Some(local) = self.find_name_function(&ident, &span) {
self.validate_local(&ident, local, &span);
(self.code_meta.global_references).insert(span.clone(), local.index);
self.global_index(local.index, span)
} else if let Some(constant) = CONSTANTS.iter().find(|c| c.name == ident) {
if let Some(suggestion) = constant.deprecation
&& self.deprecated_const_errors.insert(constant.name)
{
let suggestion = if suggestion.is_empty() {
String::new()
} else {
format!(". {suggestion}")
};
let message = format!(
"{} is deprecated and will be removed in a future version{}",
constant.name, suggestion
);
self.emit_diagnostic(message, DiagnosticKind::Warning, span.clone());
}
self.code_meta
.constant_references
.insert(span.clone().sp(ident));
let value = (constant.value)
.resolve(self.scope_file_path(), self.backend().clone())
.unwrap_or_else(|e| {
self.add_error(span, e);
Value::default()
});
Node::Push(value)
} else {
self.add_error(span, format!("Unknown identifier `{ident}`"));
Node::new_push(Value::default())
}
}
fn scope_file_path(&self) -> Option<&Path> {
for scope in self.scopes() {
if let Some(file_path) = &scope.file_path {
return Some(file_path);
}
}
None
}
fn global_index(&mut self, index: usize, span: CodeSpan) -> Node {
let binfo = &mut self.asm.bindings.make_mut()[index];
binfo.used = true;
let bkind = binfo.kind.clone();
match bkind {
BindingKind::Const(Some(val)) => Node::new_push(val),
BindingKind::Const(None) => Node::CallGlobal(index, Signature::new(0, 1)),
BindingKind::Func(f) => Node::Call(f, self.add_span(span.clone())),
global @ (BindingKind::Module(_) | BindingKind::Scope(_)) => {
let names = match &global {
BindingKind::Module(m) => &m.names,
BindingKind::Scope(i) => {
&self.higher_scopes.get(*i).unwrap_or(&self.scope).names
}
_ => unreachable!(),
};
if let Some(local) = names.get_last("Call").or_else(|| names.get_last("New")) {
self.code_meta.global_references.remove(&span);
(self.code_meta.global_references).insert(span.clone(), local.index);
self.global_index(local.index, span.clone())
} else {
self.add_error(
span,
"Module cannot be called here as \
it has no `Call` or `New` function.",
);
Node::empty()
}
}
BindingKind::IndexMacro(_) | BindingKind::CodeMacro(_) => {
Node::empty()
}
BindingKind::Error => Node::empty(),
}
}
fn func(&mut self, func: Func, span: CodeSpan) -> UiuaResult<Node> {
let root_start = self.asm.root.len();
self.in_scope(ScopeKind::Function, |comp| {
comp.items(func.lines, ItemCompMode::Function)
})?;
let mut root = self.asm.root.split_off(root_start);
let sig = match root.sig() {
Ok(mut sig) => {
if let Some(declared_sig) = &func.signature {
root = self.force_sig(root, declared_sig.value, &declared_sig.span)?;
sig = declared_sig.value;
}
Some(sig)
}
Err(e) => return Err(self.error(span, format!("Cannot infer function signature: {e}"))),
};
if let Some(sig) = sig {
self.code_meta.function_sigs.insert(
span.clone(),
SigDecl {
sig,
explicit: func.signature.is_some(),
inline: true,
set_inverses: Default::default(),
},
);
}
Ok(root)
}
fn switch(&mut self, branches: Vec<Sp<Word>>, span: CodeSpan) -> UiuaResult<Node> {
let count = branches.len();
let mut br = EcoVec::with_capacity(count);
let mut rigid_indices = Vec::new();
let mut flex_indices = Vec::new();
for (i, branch) in branches.into_iter().enumerate() {
let span = branch.span.clone();
let SigNode { node, sig } = self.word_sig(branch)?;
let is_flex = node
.iter()
.rposition(|node| matches!(node, Node::Prim(Primitive::Assert, _)))
.is_some_and(|end| {
(0..end).rev().any(|start| {
let sub = node.slice(start..end);
match sub.as_slice() {
[Node::Push(val), Node::Prim(Primitive::Dup, _)]
| [Node::Push(val), Node::Push(..)]
if val != &Value::from(1) =>
{
return true;
}
[Node::Format(..), Node::Prim(Primitive::Dup, _)] => return true,
_ => (),
}
sub.is_pure(&self.asm) || !sub.sig().is_ok_and(|sig| sig == (0, 2))
})
});
br.push((SigNode::new(sig, node), span));
if is_flex {
flex_indices.push(i);
} else {
rigid_indices.push(i);
}
}
let mut rigid_funcs = rigid_indices.into_iter().map(|i| &br[i]);
let mut sig = None;
if let Some((arg, _)) = rigid_funcs.next() {
sig = Some(arg.sig);
let sig = sig.as_mut().unwrap();
for (arg, span) in rigid_funcs {
if arg.sig.is_compatible_with(*sig) {
*sig = sig.max_with(arg.sig);
} else if arg.sig.outputs() == sig.outputs() {
sig.update_args(|a| a.max(arg.sig.args()));
sig.update_under_args(|a| a.max(arg.sig.under_args()));
} else {
self.add_error(
span.clone(),
format!(
"Switch branch's signature {} is \
incompatible with previous branches {sig}",
arg.sig
),
);
}
}
}
let mut flex_funcs = flex_indices.into_iter().map(|i| &br[i]);
let mut sig = sig.unwrap_or_else(|| flex_funcs.next().unwrap().0.sig);
for (arg, _) in flex_funcs {
sig.update_args(|a| a.max(arg.sig.args()));
sig.update_under_args(|a| a.max(arg.sig.under_args()));
}
let span = self.add_span(span.clone());
Ok(Node::Switch {
branches: br.into_iter().map(|(arg, _)| arg).collect(),
sig,
span,
under_cond: false,
})
}
fn try_(&mut self, branches: Vec<Sp<Word>>, span: CodeSpan) -> UiuaResult<Node> {
let in_try = replace(&mut self.in_try, true);
let mut branches = branches.into_iter();
let tried = self.word_sig(branches.next().unwrap());
self.in_try = in_try;
let mut ops = eco_vec![tried?];
let span = self.add_span(span);
for handler in branches {
let tried = ops.make_mut().last_mut().unwrap();
let handler_span = handler.span.clone();
let mut handler = self.word_sig(handler)?;
if tried.node.is_noreturn(&self.asm) {
tried.sig.update_args_outputs(|a, o| {
(
a.max(handler.sig.args().saturating_sub(1)),
o.max(handler.sig.outputs()),
)
});
}
if handler.sig.outputs() < tried.sig.outputs() && !handler.node.is_noreturn(&self.asm) {
let diff = tried.sig.outputs() - handler.sig.outputs();
(handler.sig).update_args_outputs(|a, o| (a + diff, o + diff));
}
if tried.sig.args() + 1 < handler.sig.args() {
let arg_diff = handler.sig.args() - tried.sig.args() - 1;
let pre = SigNode::new(
(arg_diff, 0),
eco_vec![Node::Prim(Primitive::Pop, span); arg_diff],
)
.dipped(tried.sig.args(), span);
tried.sig.update_args(|a| a + arg_diff);
tried.node.prepend(pre.node);
} else if tried.sig.outputs() < handler.sig.outputs() {
let diff = handler.sig.outputs() - tried.sig.outputs();
tried.sig.update_args_outputs(|a, o| (a + diff, o + diff));
}
if handler.sig.args() < tried.sig.args() {
let arg_diff = tried.sig.args() - handler.sig.args();
if handler.sig.outputs() <= tried.sig.outputs() {
let output_diff = tried.sig.outputs() - handler.sig.outputs();
let diff_diff = arg_diff.saturating_sub(output_diff);
if diff_diff > 0 {
let pre = SigNode::new(
(diff_diff, 0),
eco_vec![Node::Prim(Primitive::Pop, span); diff_diff],
)
.dipped(handler.sig.args() + arg_diff - diff_diff, span);
(handler.sig).update_args_outputs(|a, o| (a + arg_diff, o + output_diff));
handler.node.prepend(pre.node);
} else {
(handler.sig).update_args_outputs(|a, o| (a + arg_diff, o + arg_diff));
}
} else {
(handler.sig).update_args_outputs(|a, o| (a + arg_diff, o + arg_diff));
}
}
if handler.sig.args() == tried.sig.args()
&& handler.sig.outputs() + 1 == tried.sig.outputs()
{
handler.sig.update_args_outputs(|a, o| (a + 1, o + 1));
}
if handler.sig.args() > tried.sig.args() + 1 {
self.add_error(
handler_span,
format!(
"Handler function must have at most \
one more argument than the tried function, \
but their signatures are {} and \
{} respectively.",
handler.sig, tried.sig
),
);
}
ops.push(handler);
}
Ok(Node::Mod(Primitive::Try, ops, span))
}
fn handle_primitive_deprecation(&mut self, prim: Primitive, span: &CodeSpan) {
if let Some(suggestion) = prim.deprecation_suggestion() {
if !self.deprecated_prim_errors.insert(prim) {
return;
}
let suggestion = if suggestion.is_empty() {
String::new()
} else {
format!(", {suggestion}")
};
self.emit_diagnostic(
format!(
"{} is deprecated and will be removed in a future version{}",
prim.format(),
suggestion
),
DiagnosticKind::Warning,
span.clone(),
);
}
}
fn handle_primitive_experimental(&mut self, prim: Primitive, span: &CodeSpan) {
if prim.is_experimental() {
self.experimental_error_it(span, || prim.format());
}
}
fn validate_primitive(&mut self, prim: Primitive, span: &CodeSpan) {
self.handle_primitive_experimental(prim, span);
self.handle_primitive_deprecation(prim, span);
}
fn primitive(&mut self, prim: Primitive, span: CodeSpan) -> Node {
self.validate_primitive(prim, &span);
let spandex = self.add_span(span.clone());
Node::Prim(prim, spandex)
}
fn validate_binding_name(&mut self, name: &str, span: &CodeSpan) {
if name.contains('&') {
self.add_error(span.clone(), "User-defined names may not contain `&`");
}
}
#[allow(clippy::match_single_binding)]
fn subscript(&mut self, sub: Subscripted, span: CodeSpan) -> UiuaResult<Node> {
let scr = sub.script;
Ok(match sub.word.value {
Word::Modified(m) => match m.modifier.value {
Modifier::Ref(_) | Modifier::Macro(..) => {
self.add_error(
m.modifier.span.clone().merge(scr.span.clone()),
"Subscripts are not implemented for macros",
);
self.modified(*m, Some(scr.map(Into::into)))?
}
Modifier::Primitive(prim) => {
if prim.subscript_margs(Some(&scr.value)).is_none() {
self.add_error(
m.modifier.span.clone().merge(scr.span.clone()),
format!("Subscripts are not implemented for {}", prim.format()),
);
}
self.modified(*m, Some(scr.map(Into::into)))?
}
},
Word::Primitive(prim) => self.subscript_prim(prim, sub.word.span, scr)?,
_ => {
self.add_error(span.clone(), "Subscripts are not allowed in this context");
self.word(sub.word)?
}
})
}
fn subscript_prim(
&mut self,
prim: Primitive,
span: CodeSpan,
scr: Sp<Subscript>,
) -> UiuaResult<Node> {
use Primitive::*;
Ok(match prim {
prim if prim.class() == PrimClass::DyadicPervasive => {
let Some(nos) = self.subscript_n_or_side(&scr, prim.format()) else {
return Ok(self.primitive(prim, span));
};
self.validate_primitive(prim, &span);
match nos {
SubNOrSide::N(n) => {
Node::from_iter([Node::new_push(n), self.primitive(prim, span)])
}
SubNOrSide::Side(side) => {
self.experimental_error_it(&scr.span, || {
format!("Sided {}", prim.format())
});
let sub_span = self.add_span(scr.span);
let mut node = Node::Prim(Primitive::Fix, sub_span);
if side == SubSide::Right {
node = Node::Mod(
Primitive::Dip,
eco_vec![node.sig_node().unwrap()],
sub_span,
);
}
node.push(self.primitive(prim, span));
node
}
}
}
EncodeBytes => {
let Some(side) = self.subscript_side_only(&scr, EncodeBytes.format()) else {
return Ok(self.primitive(EncodeBytes, span));
};
self.validate_primitive(prim, &span);
let span = self.add_span(span);
Node::ImplPrim(ImplPrimitive::SidedEncodeBytes(side), span)
}
Join => {
let Some(nos) = self.subscript_n_or_side(&scr, Join.format()) else {
return Ok(self.primitive(Join, span));
};
match nos {
SubNOrSide::N(n) => match self.positive_subscript(n, Join, &span) {
0 => Node::new_push(Value::default()),
1 => Node::Prim(Identity, self.add_span(span)),
2 => Node::Prim(Join, self.add_span(span)),
n => Node::ImplPrim(ImplPrimitive::MultiJoin(n), self.add_span(span)),
},
SubNOrSide::Side(side) => {
Node::ImplPrim(ImplPrimitive::SidedJoin(side), self.add_span(span))
}
}
}
prim => {
let Some(n) = self.subscript_n_only(&scr, prim.format()) else {
return Ok(self.primitive(prim, span));
};
self.validate_primitive(prim, &span);
match prim {
prim if prim.sig().is_some_and(|sig| sig == (2, 1))
&& prim
.subscript_sig(Some(&Subscript::numeric(2)))
.is_some_and(|sig| sig == (1, 1)) =>
{
Node::from_iter([Node::new_push(n), self.primitive(prim, span)])
}
Deshape => {
if n == 1 {
self.emit_diagnostic(
format!("{Deshape}₁ is equivalent to just {}", Deshape.format()),
DiagnosticKind::Advice,
span.clone().merge(scr.span),
);
Node::Prim(Deshape, self.add_span(span))
} else {
Node::ImplPrim(ImplPrimitive::DeshapeSub(n), self.add_span(span))
}
}
Transpose => {
self.subscript_experimental(prim, &span);
if n == 0 {
return Ok(Node::empty());
}
Node::from([
Node::new_push(n - 1),
Node::ImplPrim(ImplPrimitive::AntiOrient, self.add_span(span)),
])
}
Neg => {
use crate::Complex;
let rotation = match n {
-1..=1 => Complex::ONE,
2 | -2 => -Complex::ONE,
4 => Complex::I,
-4 => -Complex::I,
_ => Complex::from_polar(1.0, std::f64::consts::TAU / n as f64),
};
Node::from_iter([Node::new_push(rotation), self.primitive(Mul, span)])
}
Sqrt => {
if n == 0 {
self.add_error(span.clone(), "Cannot take 0th root");
}
Node::from_iter([Node::new_push(1.0 / n as f64), self.primitive(Pow, span)])
}
Exp => {
let span = self.add_span(span);
match n {
2 => Node::ImplPrim(ImplPrimitive::Exp2, span),
10 => Node::ImplPrim(ImplPrimitive::Exp10, span),
n => Node::from_iter([
Node::new_push(n as f64),
Node::Prim(Flip, span),
Node::Prim(Pow, span),
]),
}
}
Floor | Ceil => {
self.subscript_experimental(prim, &span);
let mul = 10f64.powi(n);
Node::from_iter([
Node::new_push(mul),
self.primitive(Mul, span.clone()),
self.primitive(prim, span.clone()),
Node::new_push(mul),
self.primitive(Div, span),
])
}
Round => {
let mul = 10f64.powi(n);
Node::from_iter([
Node::new_push(mul),
self.primitive(Mul, span.clone()),
self.primitive(prim, span.clone()),
Node::new_push(mul),
self.primitive(Div, span),
])
}
Rand => Node::from_iter([
self.primitive(Rand, span.clone()),
Node::new_push(n),
self.primitive(Mul, span.clone()),
self.primitive(Floor, span),
]),
Json => match n {
5 => Node::ImplPrim(ImplPrimitive::Json5, self.add_span(span)),
_ => {
self.add_error(span.clone(), "Only json and json₅ are supported");
self.primitive(Json, span)
}
},
Utf8 => match n {
8 => self.primitive(Utf8, span),
16 => Node::ImplPrim(ImplPrimitive::Utf16, self.add_span(span)),
_ => {
self.add_error(span.clone(), "Only UTF-8 and UTF-16 are supported");
self.primitive(Utf8, span)
}
},
Couple => match n {
1 => self.primitive(Fix, span),
2 => self.primitive(Couple, span),
n => Node::Array {
len: self.positive_subscript(n, Couple, &span),
inner: Node::empty().into(),
boxed: false,
allow_ext: true,
prim: Some(Couple),
span: self.add_span(span),
},
},
Box => Node::Array {
len: self.positive_subscript(n, Box, &span),
inner: Node::empty().into(),
boxed: true,
allow_ext: false,
prim: Some(Box),
span: self.add_span(span),
},
Parse => {
if !matches!(n, 1..=36 | 64) {
self.add_error(span.clone(), format!("Cannot parse base {n}"));
}
Node::ImplPrim(ImplPrimitive::ParseSub(n as usize), self.add_span(span))
}
Args => Node::ImplPrim(
ImplPrimitive::StackN {
n: self.positive_subscript(n, Args, &span),
inverse: false,
},
self.add_span(span),
),
First | Last => {
let n = self.positive_subscript(n, prim, &span);
let span = self.add_span(span);
match n {
0 => Node::Prim(Pop, span),
1 => Node::Prim(prim, span),
n if prim == First => Node::from_iter([
Node::new_push(n),
Node::Prim(Take, span),
Node::Unpack {
count: n,
unbox: false,
allow_ext: false,
prim: Some(First),
span,
},
]),
n => Node::from_iter([
Node::new_push(-(n as i32)),
Node::Prim(Take, span),
Node::Prim(Reverse, span),
Node::Unpack {
count: n,
unbox: false,
allow_ext: false,
prim: Some(Last),
span,
},
]),
}
}
Bits => {
let n = self.positive_subscript(n, Bits, &span);
let span = self.add_span(span);
Node::ImplPrim(ImplPrimitive::NBits(n), span)
}
Len => {
let span = self.add_span(span);
Node::from_iter([
Node::Prim(Shape, span),
Node::new_push(n),
Node::Prim(Select, span),
])
}
Shape => {
let span = self.add_span(span);
Node::from_iter([
Node::Prim(Shape, span),
Node::new_push(n),
Node::Prim(Take, span),
])
}
Range => Node::ImplPrim(ImplPrimitive::RangeSub(n), self.add_span(span)),
Keep => {
let n = self.positive_subscript(n, Keep, &span);
let span = self.add_span(span);
Node::ImplPrim(ImplPrimitive::MultiKeep(n), span)
}
Occurrences => {
let n = self.positive_subscript(n, Occurrences, &span);
let span = self.add_span(span);
Node::from([
Node::Prim(Occurrences, span),
Node::new_push(n),
Node::Prim(Lt, span),
])
}
Classify => {
let n = self.positive_subscript(n, Classify, &span);
Node::ImplPrim(ImplPrimitive::ClassifySub(n), self.add_span(span))
}
_ => {
self.add_error(
span.clone(),
format!("Subscripts are not implemented for {}", prim.format()),
);
self.primitive(prim, span)
}
}
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SubNOrSide<N = i32> {
N(N),
Side(SubSide),
}
impl<N: PartialEq> PartialEq<N> for SubNOrSide<N> {
fn eq(&self, other: &N) -> bool {
match self {
SubNOrSide::N(n) => *n == *other,
SubNOrSide::Side(_) => false,
}
}
}
impl Compiler {
fn validate_subscript(&mut self, sub: Sp<Subscript>) -> Sp<Subscript<i32>> {
let side = sub.value.side;
let num = (sub.value.num.as_ref()).and_then(|num| self.numeric_subscript_n(num, &sub.span));
if num.is_none() && side.is_none() {
self.add_error(sub.span.clone(), "Subscript is incomplete");
}
sub.span.sp(Subscript { num, side })
}
fn numeric_subscript_n(&mut self, num: &NumericSubscript, span: &CodeSpan) -> Option<i32> {
match num {
NumericSubscript::N(n) => Some(*n),
NumericSubscript::NegOnly => {
self.add_error(span.clone(), "Subscript is incomplete");
None
}
NumericSubscript::TooLarge(_) => {
self.add_error(span.clone(), "Subscript is too large");
None
}
}
}
fn subscript_n_or_side(
&mut self,
sub: &Sp<Subscript>,
for_what: impl fmt::Display,
) -> Option<SubNOrSide> {
match (&sub.value.num, sub.value.side) {
(None, None) => {
self.add_error(sub.span.clone(), "Subscript is incomplete");
None
}
(Some(num), None) => self.numeric_subscript_n(num, &sub.span).map(SubNOrSide::N),
(None, Some(side)) => {
if side.n.is_some() {
self.add_error(
sub.span.clone(),
format!("Sided subscript quantifiers are not allowed for {for_what}"),
);
}
Some(SubNOrSide::Side(side.side))
}
(Some(_), Some(_)) => {
self.add_error(
sub.span.clone(),
format!("Mixed subscripts are not allowed for {for_what}"),
);
None
}
}
}
fn subscript_n_only(
&mut self,
sub: &Sp<Subscript>,
for_what: impl fmt::Display + Copy,
) -> Option<i32> {
let nos = self.subscript_n_or_side(sub, for_what)?;
match nos {
SubNOrSide::N(n) => Some(n),
SubNOrSide::Side(_) => {
self.add_error(
sub.span.clone(),
format!("Sided subscripts are not allowed for {for_what}"),
);
None
}
}
}
fn subscript_side_only(
&mut self,
sub: &Sp<Subscript>,
for_what: impl fmt::Display + Copy,
) -> Option<SubSide> {
let nos = self.subscript_n_or_side(sub, for_what)?;
match nos {
SubNOrSide::N(_) => {
self.add_error(
sub.span.clone(),
format!("Numeric subscripts are not allowed for {for_what}"),
);
None
}
SubNOrSide::Side(side) => Some(side),
}
}
fn positive_subscript(&mut self, n: i32, prim: Primitive, span: &CodeSpan) -> usize {
if n < 0 {
self.add_error(
span.clone(),
format!("Subscript for {} must be positive", prim.format()),
);
}
n.unsigned_abs() as usize
}
fn subscript_experimental(&mut self, prim: Primitive, span: &CodeSpan) {
self.experimental_error(span, || {
format!(
"Subcripted {} is experimental. To use it, \
add `# Experimental!` to the top of the file.",
prim.format()
)
});
}
pub fn diagnostics(&self) -> &BTreeSet<Diagnostic> {
&self.diagnostics
}
pub fn diagnostics_mut(&mut self) -> &mut BTreeSet<Diagnostic> {
&mut self.diagnostics
}
pub fn take_diagnostics(&mut self) -> BTreeSet<Diagnostic> {
take(&mut self.diagnostics)
}
pub fn emit_diagnostic(
&mut self,
message: impl Into<String>,
kind: DiagnosticKind,
span: impl Into<Span>,
) {
let inputs = self.asm.inputs.clone();
self.emit_diagnostic_impl(Diagnostic::new(message.into(), span, kind, inputs));
}
#[allow(clippy::print_stdout)]
fn emit_diagnostic_impl(&mut self, diagnostic: Diagnostic) {
if self.print_diagnostics {
eprintln!("{}", diagnostic.report());
} else {
self.diagnostics.insert(diagnostic);
}
}
fn add_error(&mut self, span: impl Into<Span>, message: impl ToString) {
let e = self.error(span, message);
self.errors.push(e);
}
fn experimental_error<S>(&mut self, span: &CodeSpan, message: impl FnOnce() -> S)
where
S: fmt::Display,
{
if !self.allow_experimental() {
self.scope.experimental_error = true;
self.add_error(span.clone(), message().to_string());
}
}
fn experimental_error_it<S>(&mut self, span: &CodeSpan, thing: impl FnOnce() -> S)
where
S: fmt::Display,
{
self.experimental_error(span, || {
format!(
"{} is experimental. Add `# Experimental!` \
to the top of the file to use it.",
thing()
)
})
}
fn allow_experimental(&self) -> bool {
let take = self
.scopes()
.position(|sc| matches!(sc.kind, ScopeKind::File(_)))
.map(|i| i + 1)
.unwrap_or(usize::MAX);
self.scopes()
.take(take)
.any(|sc| sc.experimental || sc.experimental_error)
}
fn error(&self, span: impl Into<Span>, message: impl ToString) -> UiuaError {
UiuaErrorKind::Run {
message: span.into().sp(message.to_string()),
info: Vec::new(),
inputs: self.asm.inputs.clone().into(),
}
.into()
}
fn error_with_info<S, M>(
&self,
span: impl Into<Span>,
message: impl ToString,
info: impl IntoIterator<Item = (S, M)>,
) -> UiuaError
where
S: Into<Span>,
M: ToString,
{
UiuaErrorKind::Run {
message: span.into().sp(message.to_string()),
info: info
.into_iter()
.map(|(s, m)| s.into().sp(m.to_string()))
.collect(),
inputs: self.asm.inputs.clone().into(),
}
.into()
}
fn validate_local(&mut self, name: &str, local: LocalIndex, span: &CodeSpan) {
if let Some(suggestion) = &self.asm.bindings[local.index].meta.deprecation {
let mut message = format!("{name} is deprecated");
if !suggestion.is_empty() {
message.push_str(". ");
message.push_str(suggestion);
if message.ends_with(char::is_alphanumeric) {
message.push('.');
}
}
self.emit_diagnostic(message, DiagnosticKind::Warning, span.clone());
}
if local.public {
return;
}
let get = |scope: &Scope| {
(scope.names.get_last(name)).or_else(|| scope.names.get_last(name.strip_suffix('!')?))
};
if !local.public
&& get(&self.scope)
.filter(|l| l.public || !matches!(self.scope.kind, ScopeKind::AllInModule))
.or_else(|| self.scopes_to_file().skip(1).find_map(get))
.is_none_or(|l| l.index != local.index)
{
self.add_error(span.clone(), format!("`{name}` is private"));
}
}
pub fn get_span(&self, span: usize) -> Span {
self.asm.spans[span].clone()
}
pub fn add_span(&mut self, span: impl Into<Span>) -> usize {
let span = span.into();
if let Some(i) = self.asm.spans.iter().position(|s| *s == span) {
return i;
}
let idx = self.asm.spans.len();
self.asm.spans.push(span);
idx
}
pub fn create_function(
&mut self,
signature: impl Into<Signature>,
f: impl Fn(&mut Uiua) -> UiuaResult + SendSyncNative + 'static,
) -> Function {
let sig = signature.into();
let df = self.create_dynamic_function(sig, f);
self.asm.add_function(
FunctionId::Unnamed,
sig,
Node::Dynamic(df),
FunctionOrigin::Dynamic,
)
}
fn create_dynamic_function(
&mut self,
sig: Signature,
f: impl Fn(&mut Uiua) -> UiuaResult + SendSyncNative + 'static,
) -> DynamicFunction {
let index = self.asm.dynamic_functions.len();
self.asm.dynamic_functions.push(Arc::new(f));
DynamicFunction { index, sig }
}
pub fn bind_function(&mut self, name: impl Into<EcoString>, function: Function) -> UiuaResult {
self.bind_function_with_meta(name, function, BindingMeta::default())
}
fn bind_function_with_meta(
&mut self,
name: impl Into<EcoString>,
function: Function,
meta: BindingMeta,
) -> UiuaResult {
let name = name.into();
let local = LocalIndex {
index: self.next_global,
public: true,
};
self.next_global += 1;
self.compile_bind_function(name, local, function, 0, meta)?;
Ok(())
}
pub fn create_bind_function(
&mut self,
name: impl Into<EcoString>,
signature: impl Into<Signature>,
f: impl Fn(&mut Uiua) -> UiuaResult + SendSyncNative + 'static,
) -> UiuaResult {
let name = name.into();
if let Some(index) = self.externals.get(&name).copied() {
let df = self.create_dynamic_function(signature.into(), f);
self.asm.functions.make_mut()[index] = Node::Dynamic(df);
Ok(())
} else {
let function = self.create_function(signature, f);
let meta = BindingMeta {
external: true,
..Default::default()
};
self.bind_function_with_meta(name, function, meta)
}
}
fn sig_of(&self, node: &Node, span: &CodeSpan) -> UiuaResult<Signature> {
node.sig().map_err(|e| {
self.error(
span.clone(),
format!("Cannot infer function signature: {e}"),
)
})
}
}
fn words_look_pervasive(words: &[Sp<Word>]) -> bool {
use Primitive::*;
words.iter().all(|word| match &word.value {
Word::Primitive(p) if p.class().is_pervasive() => true,
Word::Primitive(Dup | Dip | Identity | Fork | Under | Each) => true,
Word::Func(func) if func.word_lines().all(words_look_pervasive) => true,
Word::Number(..) | Word::Char(..) => true,
Word::Modified(m) if m.modifier.value == Modifier::Primitive(Primitive::Each) => true,
_ => false,
})
}
fn recurse_words(words: &[Sp<Word>], f: &mut dyn FnMut(&Sp<Word>)) {
for word in words {
f(word);
match &word.value {
Word::Strand(items) => recurse_words(items, f),
Word::Array(arr) => arr.word_lines().for_each(|line| {
recurse_words(line, f);
}),
Word::Func(func) => func.word_lines().for_each(|line| {
recurse_words(line, f);
}),
Word::Modified(m) => recurse_words(&m.operands, f),
Word::Pack(pack) => pack.branches.iter().for_each(|branch| {
(branch.value.word_lines()).for_each(|line| recurse_words(line, f))
}),
Word::Subscripted(sub) => recurse_words(slice::from_ref(&sub.word), f),
_ => {}
}
}
}
fn recurse_words_mut(words: &mut [Sp<Word>], f: &mut dyn FnMut(&mut Sp<Word>)) {
recurse_words_mut_impl(words, &|_| true, f);
}
fn recurse_words_mut_impl(
words: &mut [Sp<Word>],
recurse_word: &dyn Fn(&Sp<Word>) -> bool,
f: &mut dyn FnMut(&mut Sp<Word>),
) {
for word in words {
if !recurse_word(word) {
continue;
}
match &mut word.value {
Word::Strand(items) => recurse_words_mut(items, f),
Word::Array(arr) => arr.word_lines_mut().for_each(|line| {
recurse_words_mut(line, f);
}),
Word::Func(func) => func.word_lines_mut().for_each(|line| {
recurse_words_mut(line, f);
}),
Word::Modified(m) => recurse_words_mut(&mut m.operands, f),
Word::Pack(pack) => pack.branches.iter_mut().for_each(|branch| {
(branch.value.word_lines_mut()).for_each(|line| recurse_words_mut(line, f))
}),
Word::Subscripted(sub) => recurse_words_mut(slice::from_mut(&mut sub.word), f),
_ => {}
}
f(word);
}
}
fn line_sig(line: &[Sp<Word>]) -> Option<Sp<DocCommentSig>> {
line.split_last()
.and_then(|(last, mut rest)| match &last.value {
Word::Comment(c) => c.parse::<DocCommentSig>().ok().map(|sig| {
while rest.last().is_some_and(|w| matches!(w.value, Word::Spaces)) {
rest = &rest[..rest.len() - 1];
}
let span = (rest.first())
.zip(rest.last())
.map(|(f, l)| f.span.clone().merge(l.span.clone()))
.unwrap_or_else(|| last.span.clone());
span.sp(sig)
}),
_ => None,
})
}
#[cfg(not(target_arch = "wasm32"))]
pub trait SendSyncNative: Send + Sync {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send + Sync> SendSyncNative for T {}
#[cfg(target_arch = "wasm32")]
pub trait SendSyncNative {}
#[cfg(target_arch = "wasm32")]
impl<T> SendSyncNative for T {}