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
6 changes: 6 additions & 0 deletions bytecode/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,12 @@ impl PrimitiveFunction {
pub(crate) fn callback_state(&self) -> &Option<Rc<VariableMapping>> {
&self.callback_state
}

pub(crate) fn true_eq(&self, other: &Self) -> bool {
self.location == other.location
&& self.callback_state.as_ref().map(Rc::as_ptr)
== other.callback_state.as_ref().map(Rc::as_ptr)
}
}

impl PartialEq for PrimitiveFunction {
Expand Down
19 changes: 16 additions & 3 deletions bytecode/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ pub mod implementations {
("&", ..) => left & right,
("<<", ..) => left << right,
(">>", ..) => left >> right,
("is", ..) => left.runtime_addr_check(&right),
_ => bail!("unknown operation: {symbols} (got &{left}, &{right})"),
}
.context("invalid binary operation")?;
Expand Down Expand Up @@ -266,6 +267,9 @@ pub mod implementations {
let Some(indexable) = ctx.pop() else {
bail!("the stack is empty");
};

let indexable = indexable.move_out_of_heap_primitive();

// this manual byte slice to usize conversion is more performant
let idx: usize = 'index_gen: {
let mut idx = 0;
Expand Down Expand Up @@ -321,7 +325,10 @@ pub mod implementations {
match op_name.as_str() {
"reverse" => {
let Some(Primitive::Vector(vector)) = ctx.get_last_op_item() else {
bail!("Cannot perform a vector operation on a non-vector")
bail!(
"Cannot perform a vector operation on a non-vector (found: {:?})",
ctx.get_last_op_item()
)
};

let mut vector = vector.borrow_mut();
Expand All @@ -340,8 +347,14 @@ pub mod implementations {

let new_item = ctx.pop().context("could not pop first item")?;

let Some(Primitive::Vector(vector)) = ctx.get_last_op_item() else {
bail!("Cannot perform a vector operation on a non-vector")
let Some(primitive) = ctx.get_last_op_item() else {
bail!("Stack is empty")
};

let primitive = primitive.move_out_of_heap_primitive_borrow();

let Primitive::Vector(vector) = primitive.as_ref() else {
bail!("Cannot perform a vector operation on a non-vector (found: {primitive})")
};

let mut vector = vector.borrow_mut();
Expand Down
44 changes: 30 additions & 14 deletions bytecode/src/variables/primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
};
use anyhow::{bail, Result};
use std::{
borrow::Cow,
cell::{Ref, RefCell, RefMut},
fmt::{Debug, Display},
ops::Deref,
Expand Down Expand Up @@ -228,27 +229,34 @@ impl Primitive {
impl_eq!(each Optional, Str, Bool, Function with itself);
}

pub fn runtime_addr_check(&self, rhs: &Self) -> Result<Primitive> {
match (self, rhs) {
(Self::BuiltInFunction(id1), Self::BuiltInFunction(id2)) => {
return Ok(Primitive::Bool(id1 == id2))
}
(Self::Object(o1), Self::Object(o2)) => {
return Ok(Primitive::Bool(o1.as_ptr() == o2.as_ptr()))
}
(Self::Module(m1), Self::Module(m2)) => {
return Ok(Primitive::Bool(m1.as_ptr() == m2.as_ptr()))
}
(Self::Function(f1), Self::Function(f2)) => return Ok(Primitive::Bool(f1.true_eq(f2))),
(Self::Vector(v1), Self::Vector(v2)) => {
return Ok(Primitive::Bool(v1.as_ptr() == v2.as_ptr()))
}
_ => (),
}

self.equals(rhs).map(Primitive::Bool)
}

/// Returns whether this primitive is numeric.
pub fn is_numeric(&self) -> bool {
use Type::*;

matches!(self.ty(), Int | Float | Byte | BigInt)
}

/// This function *must* be called before storing a primitive to any sort of
/// long-term storage, as [`Primitive::HeapPrimitive`]s are inherently dangerous
/// and should only be used for optimization/temporary register purposes.
///
/// This code will blow up the VM if the HP ptr is not valid. If this happens, though,
/// it is a bug with the compiler. Users will NEVER encounter a stale mutable pointer on
/// their own, as it is a private type known only to the compiler used for array tricks.
///
/// # Safety
/// This function assumes that if `self` is a [HeapPrimitive]
/// that points to memory inside a vector/list, the vector/list
/// is "pinned" and has not been modified since this pointer's creation.
/// Otherwise, this function will access a dangling reference and construct
/// a Primitive from binary garbage.
pub fn move_out_of_heap_primitive(self) -> Self {
if let Self::HeapPrimitive(primitive) = self {
primitive.to_owned_primitive()
Expand All @@ -257,6 +265,14 @@ impl Primitive {
}
}

pub fn move_out_of_heap_primitive_borrow(&self) -> Cow<Self> {
if let Self::HeapPrimitive(primitive) = self {
Cow::Owned(primitive.to_owned_primitive())
} else {
Cow::Borrowed(self)
}
}

pub fn lookup(self, property: &str) -> std::result::Result<PrimitiveFlagsPair, Self> {
use Primitive as P;
match self.move_out_of_heap_primitive() {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/ast/assignment/assignment_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ impl Parser {
)
.unwrap_or_default();

let message = if value.is_callable().to_err_vec()? {
format!("declaration wanted `{ty}`, but value is a function that returns `{assignment_ty}`{hint}")
let message = if let Some(as_callable) = assignment_ty.is_callable() {
format!("declaration wanted `{ty}`, but value is a function that returns `{}`{hint}", as_callable.return_type())
} else {
// TODO: special check for function types.
format!("declaration wanted `{ty}`, but value is `{assignment_ty}`{hint}")
Expand Down
43 changes: 28 additions & 15 deletions compiler/src/ast/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use super::{
pub struct Class {
ident: Ident,
body: ClassBody,
class_type: ClassType,
flags: ClassFlags,
path_str: Arc<PathBuf>,
}
Expand All @@ -46,11 +47,7 @@ impl Compile for Class {
fn compile(&self, state: &super::CompilationState) -> Result<Vec<CompiledItem>, anyhow::Error> {
let body_compiled = self.body.compile(state)?;

let TypeLayout::Class(ty) = self.ident.ty()?.as_ref() else {
unreachable!()
};

let id = CompiledFunctionId::Custom(ty.name().to_owned());
let id = CompiledFunctionId::Custom(self.class_type.name().to_owned());

let compiled_class = CompiledItem::Function {
id: id.clone(),
Expand Down Expand Up @@ -207,6 +204,20 @@ impl ClassType {
}
}

pub fn new_callable(name: Arc<String>, fields: Arc<[Ident]>, path_str: Arc<PathBuf>) -> Self {
Self {
name,
fields,
path_str,
debug_lock: Arc::new(DebugPrintableLock::new()),
}
}

pub fn with_new_fields(mut self, fields: Arc<[Ident]>) -> Self {
self.fields = fields;
self
}

pub fn constructor(&self) -> FunctionType {
let return_type = ScopeReturnStatus::Should(Cow::Owned(TypeLayout::Class(self.clone())));

Expand All @@ -225,7 +236,7 @@ impl ClassType {

let empty_parameters = Rc::new(FunctionParameters::TypesOnly(vec![]));

FunctionType::new(empty_parameters, return_type, false)
FunctionType::new(empty_parameters, return_type, false, true)
}

pub fn name(&self) -> &str {
Expand Down Expand Up @@ -319,41 +330,43 @@ impl Parser {

let body_node = children.next().unwrap();

let body = {
let (body, class_type) = {
let _class_scope = input.user_data().push_class_unknown_self();

let fields = ClassBody::get_members(&body_node).to_err_vec()?;

let class_type = ClassType::new(
let class_type = ClassType::new_callable(
Arc::new(ident.name().to_owned()),
fields,
input.user_data().bytecode_path(),
);

let class_type = input.user_data().set_self_type_of_class(class_type);

ident
.link_force_no_inherit(
input.user_data(),
Cow::Owned(TypeLayout::Class(class_type.clone())),
Cow::Owned(TypeLayout::Function(class_type.constructor())),
)
.to_err_vec()?;

log::trace!("class {} {{ ... }}", ident.name());

input.user_data().set_self_type_of_class(class_type);

Self::class_body(body_node)?
(Self::class_body(body_node)?, class_type)
};

input
.user_data()
.add_type(ident.name().into(), ident.ty().unwrap().clone());
input.user_data().add_type(
ident.name().into(),
Cow::Owned(TypeLayout::Class(class_type.clone())),
);

input.user_data().add_dependency(&ident);

let result = Class {
ident,
body,
flags,
class_type,
path_str: input.user_data().bytecode_path(),
};

Expand Down
11 changes: 8 additions & 3 deletions compiler/src/ast/class/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ impl WalkForType for Constructor {
let parameters = input.children().next().unwrap();
let parameters = Parser::function_parameters(parameters, false, true, true)?;

let function_type = FunctionType::new(Rc::new(parameters), ScopeReturnStatus::Void, false);
let function_type =
FunctionType::new(Rc::new(parameters), ScopeReturnStatus::Void, false, true);

let ident = Ident::new(
"$constructor".to_owned(),
Expand All @@ -130,8 +131,12 @@ impl WalkForType for Constructor {

impl IntoType for Constructor {
fn for_type(&self) -> Result<crate::ast::TypeLayout> {
let function_type =
FunctionType::new(self.parameters.clone(), ScopeReturnStatus::Void, false);
let function_type = FunctionType::new(
self.parameters.clone(),
ScopeReturnStatus::Void,
false,
true,
);

Ok(TypeLayout::Function(function_type))
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/ast/class/member_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl WalkForType for MemberFunction {
ScopeReturnStatus::Void
};

let function_type = FunctionType::new(Rc::new(parameters), return_type, true);
let function_type = FunctionType::new(Rc::new(parameters), return_type, true, false);

ident.link_force_no_inherit(
input.user_data(),
Expand Down Expand Up @@ -148,7 +148,7 @@ impl Parser {
let parameters =
Rc::new(Self::function_parameters(parameters, true, true, true).to_err_vec()?);
let body = Self::block(body)?;
let function_type = FunctionType::new(parameters.clone(), return_type, true);
let function_type = FunctionType::new(parameters.clone(), return_type, true, false);

ident.set_type_no_link(Cow::Owned(TypeLayout::Function(function_type)));

Expand Down
11 changes: 6 additions & 5 deletions compiler/src/ast/dot_lookup.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use anyhow::Result;
use anyhow::{Context, Result};

use crate::{
instruction,
Expand Down Expand Up @@ -167,7 +167,7 @@ impl Parser {

match input.as_rule() {
Rule::dot_function_call => {
let Some(function_type) = type_of_property.is_callable() else {
let Some(function_type) = type_of_property.is_callable_allow_class(true) else {
return Err(vec![new_err(
ident_span,
&source_name,
Expand Down Expand Up @@ -195,11 +195,11 @@ impl Parser {
allow_self_type = Cow::Owned(ident_ty.clone().into_owned());
} else {
let callable_ty = ident_ty
.is_callable()
.is_callable_allow_class(true)
.details(
input.as_span(),
&input.user_data().get_source_file_name(),
"this is not callable",
format!("`{ident_ty}` is not callable"),
)
.to_err_vec()?;

Expand Down Expand Up @@ -229,7 +229,8 @@ impl Parser {

let output_type = output_type
.get_type()
.unwrap_or(&Cow::Owned(TypeLayout::Void));
.context("this scope does not return a value, not even `void`")
.to_err_vec()?;

let output_type = if output_type.is_class_self() {
lhs_ty_cow
Expand Down
Loading