Begin large refactor to enhance type handling
This commit is contained in:
parent
d82aed1a93
commit
8af8e48ebd
@ -11,7 +11,7 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
|
use crate::{Disassembler, Instruction, Span, Type, Value};
|
||||||
|
|
||||||
/// In-memory representation of a Dust program or function.
|
/// In-memory representation of a Dust program or function.
|
||||||
///
|
///
|
||||||
@ -24,6 +24,7 @@ pub struct Chunk {
|
|||||||
constants: Vec<Value>,
|
constants: Vec<Value>,
|
||||||
locals: Vec<Local>,
|
locals: Vec<Local>,
|
||||||
|
|
||||||
|
return_type: Type,
|
||||||
current_scope: Scope,
|
current_scope: Scope,
|
||||||
block_index: u8,
|
block_index: u8,
|
||||||
}
|
}
|
||||||
@ -35,6 +36,7 @@ impl Chunk {
|
|||||||
instructions: Vec::new(),
|
instructions: Vec::new(),
|
||||||
constants: Vec::new(),
|
constants: Vec::new(),
|
||||||
locals: Vec::new(),
|
locals: Vec::new(),
|
||||||
|
return_type: Type::None,
|
||||||
current_scope: Scope::default(),
|
current_scope: Scope::default(),
|
||||||
block_index: 0,
|
block_index: 0,
|
||||||
}
|
}
|
||||||
@ -51,6 +53,7 @@ impl Chunk {
|
|||||||
instructions,
|
instructions,
|
||||||
constants,
|
constants,
|
||||||
locals,
|
locals,
|
||||||
|
return_type: Type::None,
|
||||||
current_scope: Scope::default(),
|
current_scope: Scope::default(),
|
||||||
block_index: 0,
|
block_index: 0,
|
||||||
}
|
}
|
||||||
@ -172,78 +175,26 @@ impl Chunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
|
pub fn get_constant_type(&self, constant_index: u8) -> Result<Type, ChunkError> {
|
||||||
self.constants
|
self.constants
|
||||||
.get(constant_index as usize)
|
.get(constant_index as usize)
|
||||||
.map(|value| value.r#type())
|
.map(|value| value.r#type())
|
||||||
}
|
.ok_or(ChunkError::ConstantIndexOutOfBounds {
|
||||||
|
index: constant_index as usize,
|
||||||
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
|
|
||||||
self.locals.get(local_index as usize)?.r#type.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
|
|
||||||
self.instructions
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find_map(|(index, (instruction, _))| {
|
|
||||||
if let Operation::LoadList = instruction.operation() {
|
|
||||||
if instruction.a() == register_index {
|
|
||||||
let mut length = (instruction.c() - instruction.b() + 1) as usize;
|
|
||||||
let mut item_type = Type::Any;
|
|
||||||
let distance_to_end = self.len() - index;
|
|
||||||
|
|
||||||
for (instruction, _) in self
|
|
||||||
.instructions()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.skip(distance_to_end)
|
|
||||||
.take(length)
|
|
||||||
{
|
|
||||||
if let Operation::Close = instruction.operation() {
|
|
||||||
length -= (instruction.c() - instruction.b()) as usize;
|
|
||||||
} else if let Type::Any = item_type {
|
|
||||||
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(Type::List {
|
|
||||||
item_type: Box::new(item_type),
|
|
||||||
length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if instruction.yields_value() && instruction.a() == register_index {
|
|
||||||
instruction.yielded_type(self)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn return_type(&self) -> Option<Type> {
|
pub fn get_local_type(&self, local_index: u8) -> Result<&Type, ChunkError> {
|
||||||
let returns_value = self
|
self.locals
|
||||||
.instructions()
|
.get(local_index as usize)
|
||||||
.last()
|
.map(|local| &local.r#type)
|
||||||
.map(|(instruction, _)| {
|
.ok_or(ChunkError::LocalIndexOutOfBounds {
|
||||||
debug_assert!(matches!(instruction.operation(), Operation::Return));
|
index: local_index as usize,
|
||||||
|
|
||||||
instruction.b_as_boolean()
|
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
}
|
||||||
|
|
||||||
if returns_value {
|
pub fn return_type(&self) -> &Type {
|
||||||
self.instructions.iter().rev().find_map(|(instruction, _)| {
|
&self.return_type
|
||||||
if instruction.yields_value() {
|
|
||||||
instruction.yielded_type(self)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disassembler(&self) -> Disassembler {
|
pub fn disassembler(&self) -> Disassembler {
|
||||||
@ -288,7 +239,7 @@ pub struct Local {
|
|||||||
pub identifier_index: u8,
|
pub identifier_index: u8,
|
||||||
|
|
||||||
/// The expected type of the local's value.
|
/// The expected type of the local's value.
|
||||||
pub r#type: Option<Type>,
|
pub r#type: Type,
|
||||||
|
|
||||||
/// Whether the local is mutable.
|
/// Whether the local is mutable.
|
||||||
pub is_mutable: bool,
|
pub is_mutable: bool,
|
||||||
@ -299,7 +250,7 @@ pub struct Local {
|
|||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
/// Creates a new Local instance.
|
/// Creates a new Local instance.
|
||||||
pub fn new(identifier_index: u8, r#type: Option<Type>, mutable: bool, scope: Scope) -> Self {
|
pub fn new(identifier_index: u8, r#type: Type, mutable: bool, scope: Scope) -> Self {
|
||||||
Self {
|
Self {
|
||||||
identifier_index,
|
identifier_index,
|
||||||
r#type,
|
r#type,
|
||||||
@ -350,7 +301,6 @@ pub enum ChunkError {
|
|||||||
ConstantIndexOutOfBounds { index: usize },
|
ConstantIndexOutOfBounds { index: usize },
|
||||||
InstructionIndexOutOfBounds { index: usize },
|
InstructionIndexOutOfBounds { index: usize },
|
||||||
LocalIndexOutOfBounds { index: usize },
|
LocalIndexOutOfBounds { index: usize },
|
||||||
PoisonedChunk,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ChunkError {
|
impl Display for ChunkError {
|
||||||
@ -365,7 +315,6 @@ impl Display for ChunkError {
|
|||||||
ChunkError::LocalIndexOutOfBounds { index } => {
|
ChunkError::LocalIndexOutOfBounds { index } => {
|
||||||
write!(f, "Local index {} out of bounds", index)
|
write!(f, "Local index {} out of bounds", index)
|
||||||
}
|
}
|
||||||
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ use std::{
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
optimize, AnnotatedError, Chunk, ChunkError, DustError, FunctionType, Instruction, LexError,
|
AnnotatedError, Chunk, ChunkError, DustError, FunctionType, Instruction, LexError, Lexer,
|
||||||
Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, TokenOwned, Type,
|
Local, NativeFunction, Operation, Optimizer, Scope, Span, Token, TokenKind, TokenOwned, Type,
|
||||||
Value,
|
TypeConflict, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compiles the input and returns a chunk.
|
/// Compiles the input and returns a chunk.
|
||||||
@ -51,7 +51,7 @@ pub struct Compiler<'src> {
|
|||||||
|
|
||||||
local_definitions: Vec<u8>,
|
local_definitions: Vec<u8>,
|
||||||
optimization_count: usize,
|
optimization_count: usize,
|
||||||
previous_is_expression: bool,
|
previous_expression_type: Type,
|
||||||
minimum_register: u8,
|
minimum_register: u8,
|
||||||
|
|
||||||
current_token: Token<'src>,
|
current_token: Token<'src>,
|
||||||
@ -77,7 +77,7 @@ impl<'src> Compiler<'src> {
|
|||||||
lexer,
|
lexer,
|
||||||
local_definitions: Vec::new(),
|
local_definitions: Vec::new(),
|
||||||
optimization_count: 0,
|
optimization_count: 0,
|
||||||
previous_is_expression: false,
|
previous_expression_type: Type::None,
|
||||||
minimum_register: 0,
|
minimum_register: 0,
|
||||||
current_token,
|
current_token,
|
||||||
current_position,
|
current_position,
|
||||||
@ -167,10 +167,10 @@ impl<'src> Compiler<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn declare_local(
|
fn declare_local(
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: &str,
|
identifier: &str,
|
||||||
r#type: Option<Type>,
|
r#type: Type,
|
||||||
is_mutable: bool,
|
is_mutable: bool,
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
register_index: u8,
|
register_index: u8,
|
||||||
@ -230,35 +230,27 @@ impl<'src> Compiler<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_value_operation(&self) -> Option<Operation> {
|
fn get_last_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
||||||
self.chunk
|
let mut n_operations = [Operation::Return; COUNT];
|
||||||
.instructions()
|
|
||||||
.iter()
|
|
||||||
.last()
|
|
||||||
.map(|(instruction, _)| instruction.operation())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_last_instructions<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
for (nth, operation) in n_operations.iter_mut().rev().zip(
|
||||||
let mut operations = [Operation::Return; COUNT];
|
self.chunk
|
||||||
|
.instructions()
|
||||||
for (index, (instruction, _)) in self
|
.iter()
|
||||||
.chunk
|
.rev()
|
||||||
.instructions()
|
.map(|(instruction, _)| instruction.operation()),
|
||||||
.iter()
|
) {
|
||||||
.rev()
|
*nth = operation;
|
||||||
.take(COUNT)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
operations[index] = instruction.operation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(operations)
|
Some(n_operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_jumpable_mut(&mut self) -> Option<&mut Instruction> {
|
fn get_last_jumpable_mut(&mut self) -> Option<&mut Instruction> {
|
||||||
self.chunk
|
self.chunk
|
||||||
.instructions_mut()
|
.instructions_mut()
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
.rev()
|
||||||
.find_map(|(instruction, _)| {
|
.find_map(|(instruction, _)| {
|
||||||
if let Operation::LoadBoolean | Operation::LoadConstant = instruction.operation() {
|
if let Operation::LoadBoolean | Operation::LoadConstant = instruction.operation() {
|
||||||
Some(instruction)
|
Some(instruction)
|
||||||
@ -268,6 +260,101 @@ impl<'src> Compiler<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_instruction_type(&self, instruction: &Instruction) -> Result<Type, CompileError> {
|
||||||
|
use Operation::*;
|
||||||
|
|
||||||
|
match instruction.operation() {
|
||||||
|
Add | Divide | Modulo | Multiply | Subtract => {
|
||||||
|
if instruction.b_is_constant() {
|
||||||
|
self.chunk
|
||||||
|
.get_constant_type(instruction.b())
|
||||||
|
.map_err(|error| CompileError::Chunk {
|
||||||
|
error,
|
||||||
|
position: self.current_position,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.get_register_type(instruction.b())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoadBoolean | Not => Ok(Type::Boolean),
|
||||||
|
Negate => {
|
||||||
|
if instruction.b_is_constant() {
|
||||||
|
self.chunk
|
||||||
|
.get_constant_type(instruction.b())
|
||||||
|
.map_err(|error| CompileError::Chunk {
|
||||||
|
error,
|
||||||
|
position: self.current_position,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.get_register_type(instruction.b())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoadConstant => self
|
||||||
|
.chunk
|
||||||
|
.get_constant_type(instruction.b())
|
||||||
|
.map_err(|error| CompileError::Chunk {
|
||||||
|
error,
|
||||||
|
position: self.current_position,
|
||||||
|
}),
|
||||||
|
LoadList => self.get_register_type(instruction.a()),
|
||||||
|
GetLocal => self
|
||||||
|
.chunk
|
||||||
|
.get_local_type(instruction.b())
|
||||||
|
.cloned()
|
||||||
|
.map_err(|error| CompileError::Chunk {
|
||||||
|
error,
|
||||||
|
position: self.current_position,
|
||||||
|
}),
|
||||||
|
CallNative => {
|
||||||
|
let native_function = NativeFunction::from(instruction.b());
|
||||||
|
|
||||||
|
Ok(*native_function.r#type().return_type)
|
||||||
|
}
|
||||||
|
_ => Ok(Type::None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_register_type(&self, register_index: u8) -> Result<Type, CompileError> {
|
||||||
|
for (index, (instruction, _)) in self.chunk.instructions().iter().enumerate() {
|
||||||
|
if let Operation::LoadList = instruction.operation() {
|
||||||
|
if instruction.a() == register_index {
|
||||||
|
let mut length = (instruction.c() - instruction.b() + 1) as usize;
|
||||||
|
let mut item_type = Type::Any;
|
||||||
|
let distance_to_end = self.chunk.len() - index;
|
||||||
|
|
||||||
|
for (instruction, _) in self
|
||||||
|
.chunk
|
||||||
|
.instructions()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(distance_to_end)
|
||||||
|
.take(length)
|
||||||
|
{
|
||||||
|
if let Operation::Close = instruction.operation() {
|
||||||
|
length -= (instruction.c() - instruction.b()) as usize;
|
||||||
|
} else if let Type::Any = item_type {
|
||||||
|
item_type = self.get_instruction_type(instruction)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Type::List {
|
||||||
|
item_type: Box::new(item_type),
|
||||||
|
length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if instruction.yields_value() && instruction.a() == register_index {
|
||||||
|
return self.get_instruction_type(instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(CompileError::CannotResolveRegisterType {
|
||||||
|
register_index: register_index as usize,
|
||||||
|
position: self.current_position,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_constant(&mut self, value: Value, position: Span) -> Result<(), CompileError> {
|
fn emit_constant(&mut self, value: Value, position: Span) -> Result<(), CompileError> {
|
||||||
let constant_index = self.chunk.push_or_get_constant(value);
|
let constant_index = self.chunk.push_or_get_constant(value);
|
||||||
let register = self.next_register();
|
let register = self.next_register();
|
||||||
@ -294,7 +381,7 @@ impl<'src> Compiler<'src> {
|
|||||||
position,
|
position,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Boolean;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -318,7 +405,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_constant(value, position)?;
|
self.emit_constant(value, position)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Byte;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -340,7 +427,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_constant(value, position)?;
|
self.emit_constant(value, position)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Character;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -368,7 +455,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_constant(value, position)?;
|
self.emit_constant(value, position)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Float;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -396,7 +483,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_constant(value, position)?;
|
self.emit_constant(value, position)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Integer;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -418,7 +505,9 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_constant(value, position)?;
|
self.emit_constant(value, position)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::String {
|
||||||
|
length: Some(text.len()),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -435,8 +524,6 @@ impl<'src> Compiler<'src> {
|
|||||||
self.parse_expression()?;
|
self.parse_expression()?;
|
||||||
self.expect(Token::RightParenthesis)?;
|
self.expect(Token::RightParenthesis)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,7 +573,9 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_instruction(instruction, operator_position);
|
self.emit_instruction(instruction, operator_position);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
if let TokenKind::Bang = operator.kind() {
|
||||||
|
self.previous_expression_type = Type::Boolean;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -640,17 +729,17 @@ impl<'src> Compiler<'src> {
|
|||||||
| Token::SlashEqual
|
| Token::SlashEqual
|
||||||
| Token::PercentEqual = operator
|
| Token::PercentEqual = operator
|
||||||
{
|
{
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
} else {
|
} else {
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = self.get_instruction_type(&left_instruction)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
|
fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
|
||||||
if let Some(Operation::Equal | Operation::Less | Operation::LessEqual) =
|
if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _, _]) =
|
||||||
self.get_last_value_operation()
|
self.get_last_operations()
|
||||||
{
|
{
|
||||||
return Err(CompileError::CannotChainComparison {
|
return Err(CompileError::CannotChainComparison {
|
||||||
position: self.current_position,
|
position: self.current_position,
|
||||||
@ -726,7 +815,6 @@ impl<'src> Compiler<'src> {
|
|||||||
let register = self.next_register();
|
let register = self.next_register();
|
||||||
|
|
||||||
self.emit_instruction(instruction, operator_position);
|
self.emit_instruction(instruction, operator_position);
|
||||||
|
|
||||||
self.emit_instruction(Instruction::jump(1, true), operator_position);
|
self.emit_instruction(Instruction::jump(1, true), operator_position);
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::load_boolean(register, true, true),
|
Instruction::load_boolean(register, true, true),
|
||||||
@ -737,7 +825,7 @@ impl<'src> Compiler<'src> {
|
|||||||
operator_position,
|
operator_position,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Boolean;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -770,7 +858,7 @@ impl<'src> Compiler<'src> {
|
|||||||
self.emit_instruction(Instruction::jump(jump_distance, true), operator_position);
|
self.emit_instruction(Instruction::jump(jump_distance, true), operator_position);
|
||||||
self.parse_sub_expression(&rule.precedence)?;
|
self.parse_sub_expression(&rule.precedence)?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Boolean;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -797,9 +885,9 @@ impl<'src> Compiler<'src> {
|
|||||||
let scope = self.chunk.current_scope();
|
let scope = self.chunk.current_scope();
|
||||||
|
|
||||||
self.emit_instruction(Instruction::load_self(register), start_position);
|
self.emit_instruction(Instruction::load_self(register), start_position);
|
||||||
self.declare_local(identifier, None, false, scope, register);
|
self.declare_local(identifier, Type::SelfChunk, false, scope, register);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::SelfChunk;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
@ -842,18 +930,29 @@ impl<'src> Compiler<'src> {
|
|||||||
start_position,
|
start_position,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
} else {
|
|
||||||
let register = self.next_register();
|
|
||||||
|
|
||||||
self.emit_instruction(
|
let mut optimizer = Optimizer::new(self.chunk.instructions_mut());
|
||||||
Instruction::get_local(register, local_index),
|
let optimized = Optimizer::optimize_set_local(&mut optimizer);
|
||||||
self.previous_position,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
if optimized {
|
||||||
|
self.optimization_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let register = self.next_register();
|
||||||
|
|
||||||
|
self.emit_instruction(
|
||||||
|
Instruction::get_local(register, local_index),
|
||||||
|
self.previous_position,
|
||||||
|
);
|
||||||
|
|
||||||
|
let local = self.get_local(local_index)?;
|
||||||
|
|
||||||
|
self.previous_expression_type = local.r#type.clone();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,12 +994,23 @@ impl<'src> Compiler<'src> {
|
|||||||
self.advance()?;
|
self.advance()?;
|
||||||
|
|
||||||
let start_register = self.next_register();
|
let start_register = self.next_register();
|
||||||
|
let mut item_type = Type::Any;
|
||||||
|
|
||||||
while !self.allow(Token::RightBracket)? && !self.is_eof() {
|
while !self.allow(Token::RightBracket)? && !self.is_eof() {
|
||||||
let expected_register = self.next_register();
|
let expected_register = self.next_register();
|
||||||
|
|
||||||
self.parse_expression()?;
|
self.parse_expression()?;
|
||||||
|
|
||||||
|
if expected_register > start_register {
|
||||||
|
if let Err(conflict) = item_type.check(&self.previous_expression_type) {
|
||||||
|
return Err(CompileError::ListItemTypeConflict {
|
||||||
|
conflict,
|
||||||
|
position: self.previous_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item_type = self.previous_expression_type.clone();
|
||||||
let actual_register = self.next_register() - 1;
|
let actual_register = self.next_register() - 1;
|
||||||
|
|
||||||
if expected_register < actual_register {
|
if expected_register < actual_register {
|
||||||
@ -921,7 +1031,10 @@ impl<'src> Compiler<'src> {
|
|||||||
Span(start, end),
|
Span(start, end),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::List {
|
||||||
|
item_type: Box::new(item_type),
|
||||||
|
length: (to_register - start_register) as usize,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -931,12 +1044,12 @@ impl<'src> Compiler<'src> {
|
|||||||
self.parse_expression()?;
|
self.parse_expression()?;
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
self.get_last_instructions(),
|
self.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::LoadBoolean,
|
Operation::Equal | Operation::Less | Operation::LessEqual,
|
||||||
Operation::LoadBoolean,
|
|
||||||
Operation::Jump,
|
Operation::Jump,
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual
|
Operation::LoadBoolean,
|
||||||
|
Operation::LoadBoolean,
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
self.chunk.instructions_mut().pop();
|
self.chunk.instructions_mut().pop();
|
||||||
@ -959,24 +1072,10 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
let if_block_end = self.chunk.len();
|
let if_block_end = self.chunk.len();
|
||||||
let mut if_block_distance = (if_block_end - if_block_start) as u8;
|
let mut if_block_distance = (if_block_end - if_block_start) as u8;
|
||||||
|
let if_block_type = self.previous_expression_type.clone();
|
||||||
let if_block_is_expression = self
|
|
||||||
.chunk
|
|
||||||
.instructions()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.find_map(|(instruction, _)| {
|
|
||||||
if !matches!(instruction.operation(), Operation::Jump | Operation::Move) {
|
|
||||||
Some(instruction.yields_value())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
let if_last_register = self.next_register().saturating_sub(1);
|
let if_last_register = self.next_register().saturating_sub(1);
|
||||||
|
|
||||||
if let Token::Else = self.current_token {
|
let has_else_statement = if let Token::Else = self.current_token {
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
|
|
||||||
if let Token::LeftBrace = self.current_token {
|
if let Token::LeftBrace = self.current_token {
|
||||||
@ -988,18 +1087,29 @@ impl<'src> Compiler<'src> {
|
|||||||
position: self.current_position,
|
position: self.current_position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.previous_is_expression = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.previous_is_expression = if_block_is_expression && self.previous_is_expression;
|
true
|
||||||
|
} else if self.previous_expression_type != Type::None {
|
||||||
|
return Err(CompileError::IfMissingElse {
|
||||||
|
position: Span(if_block_start_position.0, self.current_position.1),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let else_block_end = self.chunk.len();
|
let else_block_end = self.chunk.len();
|
||||||
let else_block_distance = (else_block_end - if_block_end) as u8;
|
let else_block_distance = (else_block_end - if_block_end) as u8;
|
||||||
|
|
||||||
|
if let Err(conflict) = if_block_type.check(&self.previous_expression_type) {
|
||||||
|
return Err(CompileError::IfElseBranchMismatch {
|
||||||
|
conflict,
|
||||||
|
position: Span(if_block_start_position.0, self.current_position.1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
match else_block_distance {
|
match else_block_distance {
|
||||||
0 => {}
|
0 => {}
|
||||||
1 => {
|
1 if !has_else_statement => {
|
||||||
if let Some(skippable) = self.get_last_jumpable_mut() {
|
if let Some(skippable) = self.get_last_jumpable_mut() {
|
||||||
skippable.set_c_to_boolean(true);
|
skippable.set_c_to_boolean(true);
|
||||||
} else {
|
} else {
|
||||||
@ -1014,6 +1124,7 @@ impl<'src> Compiler<'src> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1 => {}
|
||||||
2.. => {
|
2.. => {
|
||||||
if_block_distance += 1;
|
if_block_distance += 1;
|
||||||
|
|
||||||
@ -1036,13 +1147,12 @@ impl<'src> Compiler<'src> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if self.chunk.len() >= 4 {
|
if self.chunk.len() >= 4 {
|
||||||
let possible_comparison_statement = {
|
let mut optimizer = Optimizer::new(self.chunk.instructions_mut());
|
||||||
let start = self.chunk.len() - 4;
|
let optimized = optimizer.optimize_comparison();
|
||||||
|
|
||||||
&mut self.chunk.instructions_mut()[start..]
|
if optimized {
|
||||||
};
|
self.optimization_count += 1
|
||||||
|
}
|
||||||
self.optimization_count += optimize(possible_comparison_statement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let else_last_register = self.next_register().saturating_sub(1);
|
let else_last_register = self.next_register().saturating_sub(1);
|
||||||
@ -1065,12 +1175,12 @@ impl<'src> Compiler<'src> {
|
|||||||
self.parse_expression()?;
|
self.parse_expression()?;
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
self.get_last_instructions(),
|
self.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::LoadBoolean,
|
Operation::Equal | Operation::Less | Operation::LessEqual,
|
||||||
Operation::LoadBoolean,
|
|
||||||
Operation::Jump,
|
Operation::Jump,
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual
|
Operation::LoadBoolean,
|
||||||
|
Operation::LoadBoolean,
|
||||||
],)
|
],)
|
||||||
) {
|
) {
|
||||||
self.chunk.instructions_mut().pop();
|
self.chunk.instructions_mut().pop();
|
||||||
@ -1097,7 +1207,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_instruction(jump_back, self.current_position);
|
self.emit_instruction(jump_back, self.current_position);
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1128,7 +1238,8 @@ impl<'src> Compiler<'src> {
|
|||||||
let end = self.previous_position.1;
|
let end = self.previous_position.1;
|
||||||
let to_register = self.next_register();
|
let to_register = self.next_register();
|
||||||
let argument_count = to_register - start_register;
|
let argument_count = to_register - start_register;
|
||||||
self.previous_is_expression = function.r#type().return_type.is_some();
|
|
||||||
|
self.previous_expression_type = Type::Function(function.r#type());
|
||||||
|
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::call_native(to_register, function, argument_count),
|
Instruction::call_native(to_register, function, argument_count),
|
||||||
@ -1154,7 +1265,7 @@ impl<'src> Compiler<'src> {
|
|||||||
fn parse_expression(&mut self) -> Result<(), CompileError> {
|
fn parse_expression(&mut self) -> Result<(), CompileError> {
|
||||||
self.parse(Precedence::None)?;
|
self.parse(Precedence::None)?;
|
||||||
|
|
||||||
if !self.previous_is_expression || self.chunk.is_empty() {
|
if self.previous_expression_type == Type::None || self.chunk.is_empty() {
|
||||||
return Err(CompileError::ExpectedExpression {
|
return Err(CompileError::ExpectedExpression {
|
||||||
found: self.previous_token.to_owned(),
|
found: self.previous_token.to_owned(),
|
||||||
position: self.current_position,
|
position: self.current_position,
|
||||||
@ -1185,7 +1296,7 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
self.emit_instruction(Instruction::r#return(has_return_value), Span(start, end));
|
self.emit_instruction(Instruction::r#return(has_return_value), Span(start, end));
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1195,7 +1306,7 @@ impl<'src> Compiler<'src> {
|
|||||||
self.emit_instruction(Instruction::r#return(false), self.current_position);
|
self.emit_instruction(Instruction::r#return(false), self.current_position);
|
||||||
} else {
|
} else {
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
Instruction::r#return(self.previous_is_expression),
|
Instruction::r#return(self.previous_expression_type != Type::None),
|
||||||
self.current_position,
|
self.current_position,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1220,11 +1331,11 @@ impl<'src> Compiler<'src> {
|
|||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let r#type = if self.allow(Token::Colon)? {
|
let explicit_type = if self.allow(Token::Colon)? {
|
||||||
let r#type = self.parse_type_from(self.current_token, self.current_position)?;
|
|
||||||
|
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
|
|
||||||
|
let r#type = self.parse_type_from(self.current_token, self.current_position)?;
|
||||||
|
|
||||||
Some(r#type)
|
Some(r#type)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -1233,7 +1344,12 @@ impl<'src> Compiler<'src> {
|
|||||||
self.expect(Token::Equal)?;
|
self.expect(Token::Equal)?;
|
||||||
self.parse_expression()?;
|
self.parse_expression()?;
|
||||||
|
|
||||||
let register = self.next_register().saturating_sub(1);
|
let register = self.next_register() - 1;
|
||||||
|
let r#type = if let Some(r#type) = explicit_type {
|
||||||
|
r#type
|
||||||
|
} else {
|
||||||
|
self.get_register_type(register)?
|
||||||
|
};
|
||||||
let (local_index, _) = self.declare_local(identifier, r#type, is_mutable, scope, register);
|
let (local_index, _) = self.declare_local(identifier, r#type, is_mutable, scope, register);
|
||||||
|
|
||||||
self.emit_instruction(
|
self.emit_instruction(
|
||||||
@ -1241,7 +1357,7 @@ impl<'src> Compiler<'src> {
|
|||||||
position,
|
position,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1292,7 +1408,7 @@ impl<'src> Compiler<'src> {
|
|||||||
let scope = function_compiler.chunk.current_scope();
|
let scope = function_compiler.chunk.current_scope();
|
||||||
let (_, identifier_index) = function_compiler.declare_local(
|
let (_, identifier_index) = function_compiler.declare_local(
|
||||||
parameter,
|
parameter,
|
||||||
Some(r#type.clone()),
|
r#type.clone(),
|
||||||
is_mutable,
|
is_mutable,
|
||||||
scope,
|
scope,
|
||||||
register,
|
register,
|
||||||
@ -1319,9 +1435,9 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
function_compiler.advance()?;
|
function_compiler.advance()?;
|
||||||
|
|
||||||
Some(Box::new(r#type))
|
Box::new(r#type)
|
||||||
} else {
|
} else {
|
||||||
None
|
Box::new(Type::None)
|
||||||
};
|
};
|
||||||
|
|
||||||
function_compiler.expect(Token::LeftBrace)?;
|
function_compiler.expect(Token::LeftBrace)?;
|
||||||
@ -1347,7 +1463,7 @@ impl<'src> Compiler<'src> {
|
|||||||
let scope = self.chunk.current_scope();
|
let scope = self.chunk.current_scope();
|
||||||
let (local_index, _) = self.declare_local(
|
let (local_index, _) = self.declare_local(
|
||||||
identifier,
|
identifier,
|
||||||
Some(Type::Function(function_type)),
|
Type::Function(function_type),
|
||||||
false,
|
false,
|
||||||
scope,
|
scope,
|
||||||
register,
|
register,
|
||||||
@ -1359,11 +1475,11 @@ impl<'src> Compiler<'src> {
|
|||||||
identifier_position,
|
identifier_position,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
} else {
|
} else {
|
||||||
self.emit_constant(function, Span(function_start, function_end))?;
|
self.emit_constant(function, Span(function_start, function_end))?;
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = Type::Function(function_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1387,6 +1503,15 @@ impl<'src> Compiler<'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let function_register = last_instruction.a();
|
let function_register = last_instruction.a();
|
||||||
|
let function_return_type =
|
||||||
|
if let Type::Function(function_type) = self.get_register_type(function_register)? {
|
||||||
|
*function_type.return_type
|
||||||
|
} else {
|
||||||
|
return Err(CompileError::ExpectedFunction {
|
||||||
|
found: self.previous_token.to_owned(),
|
||||||
|
position: self.previous_position,
|
||||||
|
});
|
||||||
|
};
|
||||||
let start = self.current_position.0;
|
let start = self.current_position.0;
|
||||||
|
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
@ -1417,7 +1542,7 @@ impl<'src> Compiler<'src> {
|
|||||||
Span(start, end),
|
Span(start, end),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.previous_is_expression = true;
|
self.previous_expression_type = function_return_type;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1425,7 +1550,7 @@ impl<'src> Compiler<'src> {
|
|||||||
fn parse_semicolon(&mut self) -> Result<(), CompileError> {
|
fn parse_semicolon(&mut self) -> Result<(), CompileError> {
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
|
|
||||||
self.previous_is_expression = false;
|
self.previous_expression_type = Type::None;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1817,6 +1942,10 @@ pub enum CompileError {
|
|||||||
found: TokenOwned,
|
found: TokenOwned,
|
||||||
position: Span,
|
position: Span,
|
||||||
},
|
},
|
||||||
|
ExpectedFunction {
|
||||||
|
found: TokenOwned,
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
InvalidAssignmentTarget {
|
InvalidAssignmentTarget {
|
||||||
found: TokenOwned,
|
found: TokenOwned,
|
||||||
position: Span,
|
position: Span,
|
||||||
@ -1845,6 +1974,27 @@ pub enum CompileError {
|
|||||||
position: Span,
|
position: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Type errors
|
||||||
|
CannotResolveRegisterType {
|
||||||
|
register_index: usize,
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
|
CannotResolveVariableType {
|
||||||
|
identifier: String,
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
|
IfElseBranchMismatch {
|
||||||
|
conflict: TypeConflict,
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
|
IfMissingElse {
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
|
ListItemTypeConflict {
|
||||||
|
conflict: TypeConflict,
|
||||||
|
position: Span,
|
||||||
|
},
|
||||||
|
|
||||||
// Wrappers around foreign errors
|
// Wrappers around foreign errors
|
||||||
Chunk {
|
Chunk {
|
||||||
error: ChunkError,
|
error: ChunkError,
|
||||||
@ -1868,15 +2018,21 @@ impl AnnotatedError for CompileError {
|
|||||||
|
|
||||||
fn description(&self) -> &'static str {
|
fn description(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::CannotChainComparison { .. } => "Cannot chain comparison",
|
Self::CannotChainComparison { .. } => "Cannot chain comparison operations",
|
||||||
Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable",
|
Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable",
|
||||||
|
Self::CannotResolveRegisterType { .. } => "Cannot resolve register type",
|
||||||
|
Self::CannotResolveVariableType { .. } => "Cannot resolve type",
|
||||||
Self::Chunk { .. } => "Chunk error",
|
Self::Chunk { .. } => "Chunk error",
|
||||||
Self::ExpectedExpression { .. } => "Expected an expression",
|
Self::ExpectedExpression { .. } => "Expected an expression",
|
||||||
|
Self::ExpectedFunction { .. } => "Expected a function",
|
||||||
Self::ExpectedMutableVariable { .. } => "Expected a mutable variable",
|
Self::ExpectedMutableVariable { .. } => "Expected a mutable variable",
|
||||||
Self::ExpectedToken { .. } => "Expected a specific token",
|
Self::ExpectedToken { .. } => "Expected a specific token",
|
||||||
Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens",
|
Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens",
|
||||||
|
Self::IfElseBranchMismatch { .. } => "Type mismatch in if/else branches",
|
||||||
|
Self::IfMissingElse { .. } => "If statement missing else branch",
|
||||||
Self::InvalidAssignmentTarget { .. } => "Invalid assignment target",
|
Self::InvalidAssignmentTarget { .. } => "Invalid assignment target",
|
||||||
Self::Lex(error) => error.description(),
|
Self::Lex(error) => error.description(),
|
||||||
|
Self::ListItemTypeConflict { .. } => "List item type conflict",
|
||||||
Self::ParseFloatError { .. } => "Failed to parse float",
|
Self::ParseFloatError { .. } => "Failed to parse float",
|
||||||
Self::ParseIntError { .. } => "Failed to parse integer",
|
Self::ParseIntError { .. } => "Failed to parse integer",
|
||||||
Self::UndeclaredVariable { .. } => "Undeclared variable",
|
Self::UndeclaredVariable { .. } => "Undeclared variable",
|
||||||
@ -1887,14 +2043,14 @@ impl AnnotatedError for CompileError {
|
|||||||
|
|
||||||
fn details(&self) -> Option<String> {
|
fn details(&self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
Self::CannotChainComparison { .. } => {
|
|
||||||
Some("Cannot chain comparison operations".to_string())
|
|
||||||
}
|
|
||||||
Self::CannotMutateImmutableVariable { identifier, .. } => {
|
Self::CannotMutateImmutableVariable { identifier, .. } => {
|
||||||
Some(format!("Cannot mutate immutable variable {identifier}"))
|
Some(format!("{identifier} is immutable"))
|
||||||
}
|
}
|
||||||
Self::Chunk { error, .. } => Some(error.to_string()),
|
Self::Chunk { error, .. } => Some(error.to_string()),
|
||||||
Self::ExpectedExpression { found, .. } => Some(format!("Found {found}")),
|
Self::ExpectedExpression { found, .. } => Some(format!("Found {found}")),
|
||||||
|
Self::ExpectedFunction { found, .. } => {
|
||||||
|
Some(format!("Expected \"{found}\" to be a function"))
|
||||||
|
}
|
||||||
Self::ExpectedToken {
|
Self::ExpectedToken {
|
||||||
expected, found, ..
|
expected, found, ..
|
||||||
} => Some(format!("Expected {expected} but found {found}")),
|
} => Some(format!("Expected {expected} but found {found}")),
|
||||||
@ -1919,23 +2075,31 @@ impl AnnotatedError for CompileError {
|
|||||||
|
|
||||||
Some(details)
|
Some(details)
|
||||||
}
|
}
|
||||||
Self::ExpectedMutableVariable { found, .. } => {
|
Self::ExpectedMutableVariable { found, .. } => Some(format!("Found {found}")),
|
||||||
Some(format!("Expected mutable variable, found {found}"))
|
Self::IfElseBranchMismatch {
|
||||||
}
|
conflict: TypeConflict { expected, actual },
|
||||||
|
..
|
||||||
|
} => Some(
|
||||||
|
format!("This if block evaluates to type \"{expected}\" but the else block evaluates to \"{actual}\"")
|
||||||
|
),
|
||||||
|
Self::IfMissingElse { .. } => Some(
|
||||||
|
"This \"if\" expression evaluates to a value but is missing an else block"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
Self::InvalidAssignmentTarget { found, .. } => {
|
Self::InvalidAssignmentTarget { found, .. } => {
|
||||||
Some(format!("Invalid assignment target, found {found}"))
|
Some(format!("Cannot assign to {found}"))
|
||||||
}
|
}
|
||||||
Self::Lex(error) => error.details(),
|
Self::Lex(error) => error.details(),
|
||||||
|
|
||||||
Self::ParseFloatError { error, .. } => Some(error.to_string()),
|
Self::ParseFloatError { error, .. } => Some(error.to_string()),
|
||||||
Self::ParseIntError { error, .. } => Some(error.to_string()),
|
Self::ParseIntError { error, .. } => Some(error.to_string()),
|
||||||
Self::UndeclaredVariable { identifier, .. } => {
|
Self::UndeclaredVariable { identifier, .. } => {
|
||||||
Some(format!("Undeclared variable {identifier}"))
|
Some(format!("{identifier} has not been declared"))
|
||||||
}
|
}
|
||||||
Self::UnexpectedReturn { .. } => None,
|
Self::UnexpectedReturn { .. } => None,
|
||||||
Self::VariableOutOfScope { identifier, .. } => {
|
Self::VariableOutOfScope { identifier, .. } => {
|
||||||
Some(format!("Variable {identifier} is out of scope"))
|
Some(format!("{identifier} is out of scope"))
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1943,13 +2107,19 @@ impl AnnotatedError for CompileError {
|
|||||||
match self {
|
match self {
|
||||||
Self::CannotChainComparison { position } => *position,
|
Self::CannotChainComparison { position } => *position,
|
||||||
Self::CannotMutateImmutableVariable { position, .. } => *position,
|
Self::CannotMutateImmutableVariable { position, .. } => *position,
|
||||||
|
Self::CannotResolveRegisterType { position, .. } => *position,
|
||||||
|
Self::CannotResolveVariableType { position, .. } => *position,
|
||||||
Self::Chunk { position, .. } => *position,
|
Self::Chunk { position, .. } => *position,
|
||||||
Self::ExpectedExpression { position, .. } => *position,
|
Self::ExpectedExpression { position, .. } => *position,
|
||||||
|
Self::ExpectedFunction { position, .. } => *position,
|
||||||
Self::ExpectedMutableVariable { position, .. } => *position,
|
Self::ExpectedMutableVariable { position, .. } => *position,
|
||||||
Self::ExpectedToken { position, .. } => *position,
|
Self::ExpectedToken { position, .. } => *position,
|
||||||
Self::ExpectedTokenMultiple { position, .. } => *position,
|
Self::ExpectedTokenMultiple { position, .. } => *position,
|
||||||
|
Self::IfElseBranchMismatch { position, .. } => *position,
|
||||||
|
Self::IfMissingElse { position } => *position,
|
||||||
Self::InvalidAssignmentTarget { position, .. } => *position,
|
Self::InvalidAssignmentTarget { position, .. } => *position,
|
||||||
Self::Lex(error) => error.position(),
|
Self::Lex(error) => error.position(),
|
||||||
|
Self::ListItemTypeConflict { position, .. } => *position,
|
||||||
Self::ParseFloatError { position, .. } => *position,
|
Self::ParseFloatError { position, .. } => *position,
|
||||||
Self::ParseIntError { position, .. } => *position,
|
Self::ParseIntError { position, .. } => *position,
|
||||||
Self::UndeclaredVariable { position, .. } => *position,
|
Self::UndeclaredVariable { position, .. } => *position,
|
||||||
|
@ -64,8 +64,8 @@ const CONSTANT_HEADER: [&str; 4] = [
|
|||||||
const LOCAL_HEADER: [&str; 4] = [
|
const LOCAL_HEADER: [&str; 4] = [
|
||||||
"Locals",
|
"Locals",
|
||||||
"------",
|
"------",
|
||||||
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
|
" i IDENTIFIER TYPE MUTABLE SCOPE ",
|
||||||
"--- ---------- ---------------- ------- ------- --------",
|
"--- ---------- ---------------- ------- -------",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Builder that constructs a human-readable representation of a chunk.
|
/// Builder that constructs a human-readable representation of a chunk.
|
||||||
@ -222,7 +222,16 @@ impl<'a> Disassembler<'a> {
|
|||||||
.map(|identifier| identifier.to_string())
|
.map(|identifier| identifier.to_string())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
current_exe()
|
current_exe()
|
||||||
.map(|path| path.to_string_lossy().to_string())
|
.map(|path| {
|
||||||
|
let path_string = path.to_string_lossy();
|
||||||
|
let file_name = path_string
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.map(|slice| slice.to_string())
|
||||||
|
.unwrap_or(path_string.to_string());
|
||||||
|
|
||||||
|
file_name
|
||||||
|
})
|
||||||
.unwrap_or("Chunk Disassembly".to_string())
|
.unwrap_or("Chunk Disassembly".to_string())
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -245,10 +254,7 @@ impl<'a> Disassembler<'a> {
|
|||||||
self.chunk.len(),
|
self.chunk.len(),
|
||||||
self.chunk.constants().len(),
|
self.chunk.constants().len(),
|
||||||
self.chunk.locals().len(),
|
self.chunk.locals().len(),
|
||||||
self.chunk
|
self.chunk.return_type()
|
||||||
.return_type()
|
|
||||||
.map(|r#type| r#type.to_string())
|
|
||||||
.unwrap_or("none".to_string())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.push(&info_line, true, false, true, true);
|
self.push(&info_line, true, false, true, true);
|
||||||
@ -262,23 +268,10 @@ impl<'a> Disassembler<'a> {
|
|||||||
let bytecode = format!("{:02X}", u32::from(instruction));
|
let bytecode = format!("{:02X}", u32::from(instruction));
|
||||||
let operation = instruction.operation().to_string();
|
let operation = instruction.operation().to_string();
|
||||||
let info = instruction.disassembly_info(self.chunk);
|
let info = instruction.disassembly_info(self.chunk);
|
||||||
let type_display = instruction
|
|
||||||
.yielded_type(self.chunk)
|
|
||||||
.map(|r#type| {
|
|
||||||
let type_string = r#type.to_string();
|
|
||||||
|
|
||||||
if type_string.len() > 16 {
|
|
||||||
format!("{type_string:.13}...")
|
|
||||||
} else {
|
|
||||||
type_string
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(String::with_capacity(0));
|
|
||||||
let position = position.to_string();
|
let position = position.to_string();
|
||||||
|
|
||||||
let instruction_display = format!(
|
let instruction_display =
|
||||||
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
|
format!("{index:^3} {bytecode:>8} {operation:13} {info:^20} {position:10}");
|
||||||
);
|
|
||||||
|
|
||||||
self.push_details(&instruction_display);
|
self.push_details(&instruction_display);
|
||||||
}
|
}
|
||||||
@ -305,18 +298,7 @@ impl<'a> Disassembler<'a> {
|
|||||||
.get(*identifier_index as usize)
|
.get(*identifier_index as usize)
|
||||||
.map(|value| value.to_string())
|
.map(|value| value.to_string())
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
let type_display = r#type
|
let type_display = r#type.to_string();
|
||||||
.as_ref()
|
|
||||||
.map(|r#type| {
|
|
||||||
let type_string = r#type.to_string();
|
|
||||||
|
|
||||||
if type_string.len() > 16 {
|
|
||||||
format!("{type_string:.13}...")
|
|
||||||
} else {
|
|
||||||
type_string
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or("unknown".to_string());
|
|
||||||
let local_display = format!(
|
let local_display = format!(
|
||||||
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7}"
|
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7}"
|
||||||
);
|
);
|
||||||
|
@ -362,6 +362,18 @@ impl Instruction {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn returns_or_panics(&self) -> bool {
|
||||||
|
match self.operation() {
|
||||||
|
Operation::Return => true,
|
||||||
|
Operation::CallNative => {
|
||||||
|
let native_function = NativeFunction::from(self.b());
|
||||||
|
|
||||||
|
matches!(native_function, NativeFunction::Panic)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn yields_value(&self) -> bool {
|
pub fn yields_value(&self) -> bool {
|
||||||
match self.operation() {
|
match self.operation() {
|
||||||
Operation::Add
|
Operation::Add
|
||||||
@ -380,43 +392,12 @@ impl Instruction {
|
|||||||
Operation::CallNative => {
|
Operation::CallNative => {
|
||||||
let native_function = NativeFunction::from(self.b());
|
let native_function = NativeFunction::from(self.b());
|
||||||
|
|
||||||
native_function.r#type().return_type.is_some()
|
*native_function.r#type().return_type != Type::None
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
|
|
||||||
use Operation::*;
|
|
||||||
|
|
||||||
match self.operation() {
|
|
||||||
Add | Divide | Modulo | Multiply | Subtract => {
|
|
||||||
if self.b_is_constant() {
|
|
||||||
chunk.get_constant_type(self.b())
|
|
||||||
} else {
|
|
||||||
chunk.get_register_type(self.b())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoadBoolean | Not => Some(Type::Boolean),
|
|
||||||
Negate => {
|
|
||||||
if self.b_is_constant() {
|
|
||||||
chunk.get_constant_type(self.b())
|
|
||||||
} else {
|
|
||||||
chunk.get_register_type(self.b())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LoadConstant => chunk.get_constant_type(self.b()),
|
|
||||||
LoadList => chunk.get_register_type(self.a()),
|
|
||||||
GetLocal => chunk.get_local_type(self.b()),
|
|
||||||
CallNative => {
|
|
||||||
let native_function = NativeFunction::from(self.b());
|
|
||||||
|
|
||||||
native_function.r#type().return_type.map(|boxed| *boxed)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
|
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
|
||||||
let format_arguments = || {
|
let format_arguments = || {
|
||||||
let first_argument = if self.b_is_constant() {
|
let first_argument = if self.b_is_constant() {
|
||||||
@ -629,7 +610,7 @@ impl Instruction {
|
|||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let native_function_name = native_function.as_str();
|
let native_function_name = native_function.as_str();
|
||||||
|
|
||||||
if native_function.r#type().return_type.is_some() {
|
if *native_function.r#type().return_type != Type::None {
|
||||||
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
|
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
|
||||||
} else {
|
} else {
|
||||||
output.push_str(&format!("{}(", native_function_name));
|
output.push_str(&format!("{}(", native_function_name));
|
||||||
|
@ -24,7 +24,7 @@ pub use crate::instruction::Instruction;
|
|||||||
pub use crate::lexer::{lex, LexError, Lexer};
|
pub use crate::lexer::{lex, LexError, Lexer};
|
||||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
||||||
pub use crate::operation::Operation;
|
pub use crate::operation::Operation;
|
||||||
pub use crate::optimizer::{optimize, Optimizer};
|
pub use crate::optimizer::Optimizer;
|
||||||
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
||||||
pub use crate::token::{output_token_list, Token, TokenKind, TokenOwned};
|
pub use crate::token::{output_token_list, Token, TokenKind, TokenOwned};
|
||||||
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
|
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
|
||||||
|
@ -56,7 +56,7 @@ macro_rules! define_native_function {
|
|||||||
pub fn returns_value(&self) -> bool {
|
pub fn returns_value(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
$(
|
$(
|
||||||
NativeFunction::$name => $type.return_type.is_some(),
|
NativeFunction::$name => *$type.return_type != Type::None,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: None,
|
value_parameters: None,
|
||||||
return_type: None
|
return_type: Box::new(Type::None)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
// (AssertEqual, 1_u8, "assert_equal", false),
|
// (AssertEqual, 1_u8, "assert_equal", false),
|
||||||
@ -112,7 +112,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: None,
|
value_parameters: None,
|
||||||
return_type: Some(Box::new(Type::Any))
|
return_type: Box::new(Type::None)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::Any)]),
|
value_parameters: Some(vec![(0, Type::Any)]),
|
||||||
return_type: Some(Box::new(Type::String { length: None }))
|
return_type: Box::new(Type::String { length: None })
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: None,
|
value_parameters: None,
|
||||||
return_type: Some(Box::new(Type::String { length: None }))
|
return_type: Box::new(Type::String { length: None })
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
// (ReadTo, 51_u8, "read_to", false),
|
// (ReadTo, 51_u8, "read_to", false),
|
||||||
@ -203,7 +203,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
||||||
return_type: None
|
return_type: Box::new(Type::None)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
// (WriteFile, 56_u8, "write_file", false),
|
// (WriteFile, 56_u8, "write_file", false),
|
||||||
@ -214,7 +214,7 @@ define_native_function! {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
||||||
return_type: None
|
return_type: Box::new(Type::None)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,34 +1,41 @@
|
|||||||
//! Tools used by the compiler to optimize a chunk's bytecode.
|
//! Tool used by the compiler to optimize a chunk's bytecode.
|
||||||
use std::{iter::Map, slice::Iter};
|
|
||||||
|
|
||||||
use crate::{Instruction, Operation, Span};
|
use crate::{Instruction, Operation, Span};
|
||||||
|
|
||||||
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
|
|
||||||
|
|
||||||
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
|
|
||||||
|
|
||||||
/// Performs optimizations on a subset of instructions.
|
|
||||||
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
|
|
||||||
Optimizer::new(instructions).optimize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instruction optimizer that mutably borrows instructions from a chunk.
|
/// An instruction optimizer that mutably borrows instructions from a chunk.
|
||||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct Optimizer<'chunk> {
|
pub struct Optimizer<'a> {
|
||||||
instructions: &'chunk mut [(Instruction, Span)],
|
instructions: &'a mut Vec<(Instruction, Span)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'chunk> Optimizer<'chunk> {
|
impl<'a> Optimizer<'a> {
|
||||||
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
|
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
|
||||||
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
|
pub fn new(instructions: &'a mut Vec<(Instruction, Span)>) -> Self {
|
||||||
Self { instructions }
|
Self { instructions }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Potentially mutates the instructions to optimize them.
|
/// Optimizes a comparison operation.
|
||||||
pub fn optimize(&mut self) -> usize {
|
///
|
||||||
let mut optimizations = 0;
|
/// Comparison instructions (which are always followed by a JUMP) can be optimized when the
|
||||||
|
/// next instructions are two constant or boolean loaders. The first loader is set to skip an
|
||||||
if matches!(
|
/// instruction if it is run while the second loader is modified to use the first's register.
|
||||||
|
/// This makes the following two code snippets compile to the same bytecode:
|
||||||
|
///
|
||||||
|
/// ```dust
|
||||||
|
/// 4 == 4
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```dust
|
||||||
|
/// if 4 == 4 { true } else { false }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The instructions must be in the following order:
|
||||||
|
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
|
||||||
|
/// - `Operation::Jump`
|
||||||
|
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||||
|
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||||
|
pub fn optimize_comparison(&mut self) -> bool {
|
||||||
|
if !matches!(
|
||||||
self.get_operations(),
|
self.get_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual,
|
Operation::Equal | Operation::Less | Operation::LessEqual,
|
||||||
@ -37,22 +44,9 @@ impl<'chunk> Optimizer<'chunk> {
|
|||||||
Operation::LoadBoolean | Operation::LoadConstant,
|
Operation::LoadBoolean | Operation::LoadConstant,
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
self.optimize_comparison();
|
return false;
|
||||||
|
|
||||||
optimizations += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizations
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optimizes a comparison operation.
|
|
||||||
///
|
|
||||||
/// The instructions must be in the following order:
|
|
||||||
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
|
|
||||||
/// - `Operation::Jump`
|
|
||||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
|
||||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
|
||||||
fn optimize_comparison(&mut self) {
|
|
||||||
log::debug!("Optimizing comparison");
|
log::debug!("Optimizing comparison");
|
||||||
|
|
||||||
let first_loader_register = {
|
let first_loader_register = {
|
||||||
@ -72,12 +66,30 @@ impl<'chunk> Optimizer<'chunk> {
|
|||||||
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
|
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
|
||||||
|
|
||||||
*second_loader = second_loader_new;
|
*second_loader = second_loader_new;
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operations_iter(&self) -> OperationIter {
|
pub fn optimize_set_local(&mut self) -> bool {
|
||||||
self.instructions
|
if !matches!(
|
||||||
.iter()
|
self.get_operations(),
|
||||||
.map(|(instruction, _)| instruction.operation())
|
Some([
|
||||||
|
Operation::Add
|
||||||
|
| Operation::Subtract
|
||||||
|
| Operation::Multiply
|
||||||
|
| Operation::Divide
|
||||||
|
| Operation::Modulo,
|
||||||
|
Operation::SetLocal,
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("Optimizing set local");
|
||||||
|
|
||||||
|
self.instructions.pop();
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
||||||
@ -87,7 +99,12 @@ impl<'chunk> Optimizer<'chunk> {
|
|||||||
|
|
||||||
let mut n_operations = [Operation::Return; COUNT];
|
let mut n_operations = [Operation::Return; COUNT];
|
||||||
|
|
||||||
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
|
for (nth, operation) in n_operations.iter_mut().rev().zip(
|
||||||
|
self.instructions
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|(instruction, _)| instruction.operation()),
|
||||||
|
) {
|
||||||
*nth = operation;
|
*nth = operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +33,12 @@ pub enum Type {
|
|||||||
Map {
|
Map {
|
||||||
pairs: HashMap<u8, Type>,
|
pairs: HashMap<u8, Type>,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
Number,
|
Number,
|
||||||
Range {
|
Range {
|
||||||
r#type: RangeableType,
|
r#type: RangeableType,
|
||||||
},
|
},
|
||||||
|
SelfChunk,
|
||||||
String {
|
String {
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
},
|
},
|
||||||
@ -257,8 +259,10 @@ impl Display for Type {
|
|||||||
|
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
}
|
}
|
||||||
|
Type::None => write!(f, "none"),
|
||||||
Type::Number => write!(f, "num"),
|
Type::Number => write!(f, "num"),
|
||||||
Type::Range { r#type } => write!(f, "{type} range"),
|
Type::Range { r#type } => write!(f, "{type} range"),
|
||||||
|
Type::SelfChunk => write!(f, "self"),
|
||||||
Type::String { .. } => write!(f, "str"),
|
Type::String { .. } => write!(f, "str"),
|
||||||
Type::Struct(struct_type) => write!(f, "{struct_type}"),
|
Type::Struct(struct_type) => write!(f, "{struct_type}"),
|
||||||
Type::Tuple { fields } => {
|
Type::Tuple { fields } => {
|
||||||
@ -343,12 +347,16 @@ impl Ord for Type {
|
|||||||
left_pairs.iter().cmp(right_pairs.iter())
|
left_pairs.iter().cmp(right_pairs.iter())
|
||||||
}
|
}
|
||||||
(Type::Map { .. }, _) => Ordering::Greater,
|
(Type::Map { .. }, _) => Ordering::Greater,
|
||||||
|
(Type::None, Type::None) => Ordering::Equal,
|
||||||
|
(Type::None, _) => Ordering::Greater,
|
||||||
(Type::Number, Type::Number) => Ordering::Equal,
|
(Type::Number, Type::Number) => Ordering::Equal,
|
||||||
(Type::Number, _) => Ordering::Greater,
|
(Type::Number, _) => Ordering::Greater,
|
||||||
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
||||||
left_type.cmp(right_type)
|
left_type.cmp(right_type)
|
||||||
}
|
}
|
||||||
(Type::Range { .. }, _) => Ordering::Greater,
|
(Type::Range { .. }, _) => Ordering::Greater,
|
||||||
|
(Type::SelfChunk, Type::SelfChunk) => Ordering::Equal,
|
||||||
|
(Type::SelfChunk, _) => Ordering::Greater,
|
||||||
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
|
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
|
||||||
(Type::String { .. }, _) => Ordering::Greater,
|
(Type::String { .. }, _) => Ordering::Greater,
|
||||||
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
||||||
@ -366,7 +374,7 @@ impl Ord for Type {
|
|||||||
pub struct FunctionType {
|
pub struct FunctionType {
|
||||||
pub type_parameters: Option<Vec<u8>>,
|
pub type_parameters: Option<Vec<u8>>,
|
||||||
pub value_parameters: Option<Vec<(u8, Type)>>,
|
pub value_parameters: Option<Vec<(u8, Type)>>,
|
||||||
pub return_type: Option<Box<Type>>,
|
pub return_type: Box<Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FunctionType {
|
impl Display for FunctionType {
|
||||||
@ -401,8 +409,8 @@ impl Display for FunctionType {
|
|||||||
|
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
|
|
||||||
if let Some(return_type) = &self.return_type {
|
if *self.return_type != Type::None {
|
||||||
write!(f, " -> {return_type}")?;
|
write!(f, " -> {}", self.return_type)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -7,8 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
|
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
|
||||||
Instruction, Local, NativeFunction, NativeFunctionError, Operation, Span, Type, Value,
|
Instruction, NativeFunction, NativeFunctionError, Operation, Span, Type, Value, ValueError,
|
||||||
ValueError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||||
@ -164,7 +163,7 @@ impl<'chunk, 'parent> Vm<'chunk, 'parent> {
|
|||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: None,
|
value_parameters: None,
|
||||||
return_type: None,
|
return_type: Box::new(Type::None),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -595,12 +594,6 @@ impl<'chunk, 'parent> Vm<'chunk, 'parent> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_local(&self, local_index: u8, position: Span) -> Result<&Local, VmError> {
|
|
||||||
self.chunk
|
|
||||||
.get_local(local_index)
|
|
||||||
.map_err(|error| VmError::Chunk { error, position })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_instruction(
|
fn get_instruction(
|
||||||
&self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
@ -23,15 +23,7 @@ fn equality_assignment_long() {
|
|||||||
(Instruction::r#return(true), Span(44, 44)),
|
(Instruction::r#return(true), Span(44, 44)),
|
||||||
],
|
],
|
||||||
vec![Value::integer(4), Value::string("a")],
|
vec![Value::integer(4), Value::string("a")],
|
||||||
vec![Local::new(
|
vec![Local::new(1, Type::Boolean, false, Scope::default(),)]
|
||||||
1,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
Scope {
|
|
||||||
depth: 0,
|
|
||||||
block_index: 0
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -61,7 +53,7 @@ fn equality_assignment_short() {
|
|||||||
(Instruction::r#return(true), Span(16, 16)),
|
(Instruction::r#return(true), Span(16, 16)),
|
||||||
],
|
],
|
||||||
vec![Value::integer(4), Value::string("a")],
|
vec![Value::integer(4), Value::string("a")],
|
||||||
vec![Local::new(1, None, false, Scope::default())]
|
vec![Local::new(1, Type::Boolean, false, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -119,7 +111,7 @@ fn if_else_assigment_false() {
|
|||||||
Value::integer(42),
|
Value::integer(42),
|
||||||
Value::string("a")
|
Value::string("a")
|
||||||
],
|
],
|
||||||
vec![Local::new(5, None, false, Scope::default())]
|
vec![Local::new(5, Type::Integer, false, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -177,7 +169,7 @@ fn if_else_assigment_true() {
|
|||||||
Value::integer(42),
|
Value::integer(42),
|
||||||
Value::string("a")
|
Value::string("a")
|
||||||
],
|
],
|
||||||
vec![Local::new(5, None, false, Scope::default())]
|
vec![Local::new(5, Type::Integer, false, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -302,7 +294,7 @@ fn if_else_complex() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_else_false() {
|
fn if_else_false() {
|
||||||
let source = "if 1 == 2 { panic() } else { 42 }";
|
let source = "if 1 == 2 { panic(); 0 } else { 42 }";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compile(source),
|
compile(source),
|
||||||
@ -334,7 +326,7 @@ fn if_else_false() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_else_true() {
|
fn if_else_true() {
|
||||||
let source = "if 1 == 1 { 42 } else { panic() }";
|
let source = "if 1 == 1 { 42 } else { panic(); 0 }";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compile(source),
|
compile(source),
|
||||||
@ -366,7 +358,7 @@ fn if_else_true() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_false() {
|
fn if_false() {
|
||||||
let source = "if 1 == 2 { 2 }";
|
let source = "if 1 == 2 { panic() }";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compile(source),
|
compile(source),
|
||||||
@ -380,8 +372,11 @@ fn if_false() {
|
|||||||
Span(5, 7)
|
Span(5, 7)
|
||||||
),
|
),
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
(Instruction::jump(1, true), Span(10, 11)),
|
||||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
(
|
||||||
(Instruction::r#return(false), Span(15, 15))
|
Instruction::call_native(0, NativeFunction::Panic, 0),
|
||||||
|
Span(12, 19)
|
||||||
|
),
|
||||||
|
(Instruction::r#return(false), Span(21, 21))
|
||||||
],
|
],
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
vec![Value::integer(1), Value::integer(2)],
|
||||||
vec![]
|
vec![]
|
||||||
@ -393,7 +388,7 @@ fn if_false() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_true() {
|
fn if_true() {
|
||||||
let source = "if 1 == 1 { 2 }";
|
let source = "if 1 == 1 { panic() }";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compile(source),
|
compile(source),
|
||||||
@ -407,13 +402,25 @@ fn if_true() {
|
|||||||
Span(5, 7)
|
Span(5, 7)
|
||||||
),
|
),
|
||||||
(Instruction::jump(1, true), Span(10, 11)),
|
(Instruction::jump(1, true), Span(10, 11)),
|
||||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
(
|
||||||
(Instruction::r#return(false), Span(15, 15))
|
Instruction::call_native(0, NativeFunction::Panic, 0),
|
||||||
|
Span(12, 19)
|
||||||
|
),
|
||||||
|
(Instruction::r#return(false), Span(21, 21))
|
||||||
],
|
],
|
||||||
vec![Value::integer(1), Value::integer(2)],
|
vec![Value::integer(1)],
|
||||||
vec![]
|
vec![]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(run(source), Ok(None));
|
assert_eq!(
|
||||||
|
run(source),
|
||||||
|
Err(DustError::Runtime {
|
||||||
|
error: VmError::NativeFunction(NativeFunctionError::Panic {
|
||||||
|
message: None,
|
||||||
|
position: Span(12, 19)
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,14 @@ fn function() {
|
|||||||
],
|
],
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
vec![Value::string("a"), Value::string("b"),],
|
||||||
vec![
|
vec![
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default()),
|
Local::new(0, Type::Integer, false, Scope::default()),
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default())
|
Local::new(1, Type::Integer, false, Scope::default())
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
return_type: Box::new(Type::Integer),
|
||||||
}
|
}
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
@ -53,14 +53,14 @@ fn function_call() {
|
|||||||
],
|
],
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
vec![Value::string("a"), Value::string("b"),],
|
||||||
vec![
|
vec![
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default()),
|
Local::new(0, Type::Integer, false, Scope::default()),
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default())
|
Local::new(1, Type::Integer, false, Scope::default())
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
return_type: Box::new(Type::Integer),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Value::integer(1),
|
Value::integer(1),
|
||||||
@ -97,24 +97,24 @@ fn function_declaration() {
|
|||||||
],
|
],
|
||||||
vec![Value::string("a"), Value::string("b")],
|
vec![Value::string("a"), Value::string("b")],
|
||||||
vec![
|
vec![
|
||||||
Local::new(0, Some(Type::Integer), false, Scope::default()),
|
Local::new(0, Type::Integer, false, Scope::default()),
|
||||||
Local::new(1, Some(Type::Integer), false, Scope::default())
|
Local::new(1, Type::Integer, false, Scope::default())
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
FunctionType {
|
FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
return_type: Box::new(Type::Integer),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
vec![Local::new(
|
vec![Local::new(
|
||||||
0,
|
0,
|
||||||
Some(Type::Function(FunctionType {
|
Type::Function(FunctionType {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||||
return_type: Some(Box::new(Type::Integer)),
|
return_type: Box::new(Type::Integer),
|
||||||
})),
|
}),
|
||||||
false,
|
false,
|
||||||
Scope::default(),
|
Scope::default(),
|
||||||
)],
|
)],
|
||||||
|
@ -67,8 +67,8 @@ fn variable_and() {
|
|||||||
],
|
],
|
||||||
vec![Value::string("a"), Value::string("b"),],
|
vec![Value::string("a"), Value::string("b"),],
|
||||||
vec![
|
vec![
|
||||||
Local::new(0, None, false, Scope::default()),
|
Local::new(0, Type::Boolean, false, Scope::default()),
|
||||||
Local::new(1, None, false, Scope::default()),
|
Local::new(1, Type::Boolean, false, Scope::default()),
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ fn r#while() {
|
|||||||
Span(23, 24)
|
Span(23, 24)
|
||||||
),
|
),
|
||||||
(Instruction::jump(2, true), Span(41, 42)),
|
(Instruction::jump(2, true), Span(41, 42)),
|
||||||
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
|
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(35, 36)),
|
||||||
(Instruction::jump(3, false), Span(41, 42)),
|
(Instruction::jump(3, false), Span(41, 42)),
|
||||||
(Instruction::get_local(1, 0), Span(41, 42)),
|
(Instruction::get_local(1, 0), Span(41, 42)),
|
||||||
(Instruction::r#return(true), Span(42, 42)),
|
(Instruction::r#return(true), Span(42, 42)),
|
||||||
@ -27,7 +27,7 @@ fn r#while() {
|
|||||||
Value::integer(5),
|
Value::integer(5),
|
||||||
Value::integer(1),
|
Value::integer(1),
|
||||||
],
|
],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ fn add_assign() {
|
|||||||
(Instruction::r#return(true), Span(24, 24))
|
(Instruction::r#return(true), Span(24, 24))
|
||||||
],
|
],
|
||||||
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
|
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ fn divide_assign() {
|
|||||||
(Instruction::r#return(true), Span(24, 24))
|
(Instruction::r#return(true), Span(24, 24))
|
||||||
],
|
],
|
||||||
vec![Value::integer(2), Value::string("a")],
|
vec![Value::integer(2), Value::string("a")],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ fn multiply_assign() {
|
|||||||
(Instruction::r#return(true), Span(23, 23))
|
(Instruction::r#return(true), Span(23, 23))
|
||||||
],
|
],
|
||||||
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
|
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ fn subtract_assign() {
|
|||||||
(Instruction::r#return(true), Span(25, 25)),
|
(Instruction::r#return(true), Span(25, 25)),
|
||||||
],
|
],
|
||||||
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
|
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -55,11 +55,11 @@ fn block_scope() {
|
|||||||
Value::string("e"),
|
Value::string("e"),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
Local::new(1, None, false, Scope::new(0, 0)),
|
Local::new(1, Type::Integer, false, Scope::new(0, 0)),
|
||||||
Local::new(3, None, false, Scope::new(1, 1)),
|
Local::new(3, Type::Integer, false, Scope::new(1, 1)),
|
||||||
Local::new(5, None, false, Scope::new(2, 2)),
|
Local::new(5, Type::Integer, false, Scope::new(2, 2)),
|
||||||
Local::new(7, None, false, Scope::new(1, 1)),
|
Local::new(7, Type::Integer, false, Scope::new(1, 1)),
|
||||||
Local::new(8, None, false, Scope::new(0, 0)),
|
Local::new(8, Type::Integer, false, Scope::new(0, 0)),
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@ -127,15 +127,15 @@ fn multiple_block_scopes() {
|
|||||||
Value::string("e"),
|
Value::string("e"),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
Local::new(1, None, false, Scope::new(0, 0)),
|
Local::new(1, Type::Integer, false, Scope::new(0, 0)),
|
||||||
Local::new(3, None, false, Scope::new(1, 1)),
|
Local::new(3, Type::Integer, false, Scope::new(1, 1)),
|
||||||
Local::new(5, None, false, Scope::new(2, 2)),
|
Local::new(5, Type::Integer, false, Scope::new(2, 2)),
|
||||||
Local::new(7, None, false, Scope::new(1, 1)),
|
Local::new(7, Type::Integer, false, Scope::new(1, 1)),
|
||||||
Local::new(8, None, false, Scope::new(0, 0)),
|
Local::new(8, Type::Integer, false, Scope::new(0, 0)),
|
||||||
Local::new(3, None, false, Scope::new(1, 3)),
|
Local::new(3, Type::Integer, false, Scope::new(1, 3)),
|
||||||
Local::new(5, None, false, Scope::new(2, 4)),
|
Local::new(5, Type::Integer, false, Scope::new(2, 4)),
|
||||||
Local::new(7, None, false, Scope::new(1, 3)),
|
Local::new(7, Type::Integer, false, Scope::new(1, 3)),
|
||||||
Local::new(9, None, false, Scope::new(0, 0)),
|
Local::new(9, Type::Integer, false, Scope::new(0, 0)),
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,7 @@ fn define_local() {
|
|||||||
(Instruction::r#return(false), Span(11, 11))
|
(Instruction::r#return(false), Span(11, 11))
|
||||||
],
|
],
|
||||||
vec![Value::integer(42), Value::string("x")],
|
vec![Value::integer(42), Value::string("x")],
|
||||||
vec![Local::new(1, None, false, Scope::default())]
|
vec![Local::new(1, Type::Integer, false, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ fn set_local() {
|
|||||||
(Instruction::r#return(true), Span(25, 25)),
|
(Instruction::r#return(true), Span(25, 25)),
|
||||||
],
|
],
|
||||||
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
|
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
|
||||||
vec![Local::new(1, None, true, Scope::default())]
|
vec![Local::new(1, Type::Integer, true, Scope::default())]
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user