Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions manual/src/reference/types/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ print([1.5, 2.25].fold(0, fn(acc, x) => acc + x)); // 3.75

A value that's already an `Int`, like the result of `len`, stays one.

A variable has one type, which all its uses decide, including the ones after it's read. So a later use can make a literal a `Float`, and `/` and `\` then divide `Float`s rather than truncate:

```ndc
let x = 7;
print(x / 2); // 3.5
x = 2.5; // x is a Float, so the 7 is too
```

Write `7.0` or annotate `let x: Int = 7` to say which one is meant.

Arbitrary-radix literals such as `16r2a` remain `Int` literals and do not accept `n`. The `i` and `j` suffixes create complex `Number` values:

```ndc
Expand Down
4 changes: 2 additions & 2 deletions ndc_resolver/src/name_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,8 +475,6 @@ impl NameResolver {
natives.into_iter().map(|(&symbol, _)| symbol).collect()
}

/// The symbol a resolved declaration introduced: a binding pattern identifier's
/// or a named function's, keyed by its node.
/// Declares a top-level value named `name` that no code refers to by
/// name, as no identifier can be spelled like it: an instance of a generic
/// function, which the analyser picks for the calls that use it. Being
Expand All @@ -488,6 +486,8 @@ impl NameResolver {
symbol
}

/// The symbol a resolved declaration introduced: a binding pattern identifier's
/// or a named function's, keyed by its node.
pub fn declared_symbol(&self, node: NodeId) -> SymbolId {
self.declarations[&node]
}
Expand Down
36 changes: 17 additions & 19 deletions ndc_stdlib/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,26 +985,24 @@ pub mod extra {
"zip must be called with 2 or more arguments".to_string(),
));
}
let iters: Option<Vec<Vec<Value>>> = args
// The iterators are stepped together, so an endless one like `1..`
// stops with the others.
let mut iters = args
.iter()
.map(|arg| match arg {
Value::Object(obj) => match obj.as_ref() {
Object::List(l) => Some(l.borrow().clone()),
Object::Tuple(t) => Some(t.clone()),
_ => None,
},
_ => None,
})
.collect();
let iters = iters.ok_or_else(|| VmError::native("zip requires sequences".to_string()))?;
let min_len = iters.iter().map(|v| v.len()).min().unwrap_or(0);
let result: Vec<Value> = (0..min_len)
.map(|i| {
Value::Object(Rc::new(Object::Tuple(
iters.iter().map(|v| v[i].clone()).collect(),
)))
})
.collect();
.map(|arg| arg.clone().try_into_iter())
.collect::<Option<Vec<_>>>()
.ok_or_else(|| VmError::native("zip requires sequences".to_string()))?;
let mut result = Vec::new();
'zipping: loop {
let mut tuple = Vec::with_capacity(iters.len());
for iter in &mut iters {
let Some(value) = iter.next() else {
break 'zipping;
};
tuple.push(value);
}
result.push(Value::Object(Rc::new(Object::Tuple(tuple))));
}
Ok(Value::Object(Rc::new(Object::list(result))))
}
}
30 changes: 21 additions & 9 deletions ndc_vm/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ use ndc_parser::{
use std::cell::{Cell, RefCell};
use std::rc::Rc;

/// Where an instance of a generic function is in [`Analysis::instances`]:
/// the declaration it's an instance of, and which of its instances it is.
#[derive(Clone, Copy)]
struct InstanceAt {
declaration: NodeId,
index: usize,
}

pub struct Compiler {
ir: OptimizerIr,
source_locals: usize,
Expand All @@ -25,7 +33,7 @@ pub struct Compiler {
analysis: Rc<Analysis>,
/// The instance of a generic function whose body is being compiled,
/// whose picks take the place of the function's own.
instance: Option<Rc<Instance>>,
instance: Option<InstanceAt>,
/// Whether the code compiled so far used a pick of `instance` that
/// differs from the function's own, so it differs from what the
/// function's own body compiles to.
Expand Down Expand Up @@ -633,7 +641,10 @@ impl Compiler {
&'a self,
pick: impl FnOnce(&'a Instance) -> Option<&'a T>,
) -> Option<&'a T> {
let found = self.instance.as_deref().and_then(pick);
let found = self
.instance
.map(|at| &self.analysis.instances[&at.declaration][at.index])
.and_then(pick);
if found.is_some() {
self.used_instance.set(true);
}
Expand Down Expand Up @@ -879,9 +890,12 @@ impl Compiler {
(self.prototype(id, name, body)?, Vec::new())
} else {
let compiled = self.prototype(id, name.clone(), body.clone())?;
let instance_bodies = instances
.iter()
.map(|instance| {
let instance_bodies = (0..instances.len())
.map(|index| {
let instance = InstanceAt {
declaration: id,
index,
};
self.compile_function_body(id, name.clone(), body.clone(), Some(instance))
.map(|(compiled, _)| compiled)
})
Expand Down Expand Up @@ -991,7 +1005,7 @@ impl Compiler {
id: NodeId,
name: Option<String>,
body: ExpressionLocation,
instance: Option<&Instance>,
instance: Option<InstanceAt>,
) -> Result<(CompiledFunction, bool), CompileError> {
let static_type = self.analysis.function_type(id).clone();
let mut fn_compiler = Self {
Expand All @@ -1003,9 +1017,7 @@ impl Compiler {
allow_return: true,
optimize: self.optimize,
analysis: Rc::clone(&self.analysis),
instance: instance
.map(|instance| Rc::new(instance.clone()))
.or_else(|| self.instance.clone()),
instance: instance.or(self.instance),
used_instance: Cell::new(false),
prototypes: Rc::clone(&self.prototypes),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// `zip` takes any sequence, and stops at the shortest, so an endless range
// can be one of them.
// expect-output: [("a","c"),("b","d")] [(1,4),(2,5)] [(1,3,"x"),(2,4,"y")]
print(zip("ab", "cd"), zip(1..3, 4..), zip([1, 2], (3, 4), "xy"));
Loading