Add string literals, fix instruction encoding
This commit is contained in:
@@ -1 +1,10 @@
|
||||
;; vi:ft=lisp:sw=2:ts=2
|
||||
|
||||
(assert-equal 2 (+ 1 #t #f))
|
||||
(assert-equal "test1" (+ "test" 1))
|
||||
(assert-equal "1test2" (+ 1 "test" 2))
|
||||
(assert-equal 2 (+ #t 1))
|
||||
(assert-equal 1 (+ #t #f))
|
||||
|
||||
(assert-equal "-1234" (int->string -1234) "int->string")
|
||||
(assert-equal -1234 (string->int "-1234"))
|
||||
|
||||
+18
-9
@@ -12,7 +12,7 @@ use crate::{
|
||||
},
|
||||
value::{BuiltinFunction, CompileConstant, CompileValue},
|
||||
},
|
||||
vm::instruction::{Instruction, MathInstruction, U},
|
||||
vm::instruction::{Instruction, LocalId, MathInstruction, U},
|
||||
};
|
||||
|
||||
pub struct CompiledFunction {
|
||||
@@ -40,7 +40,11 @@ pub struct LocalBlock<'a> {
|
||||
}
|
||||
|
||||
impl LocalScope {
|
||||
pub fn get_or_insert(&mut self, value: Rc<str>, visible: bool) -> Result<U<16>, CompileError> {
|
||||
pub fn get_or_insert(
|
||||
&mut self,
|
||||
value: Rc<str>,
|
||||
visible: bool,
|
||||
) -> Result<LocalId, CompileError> {
|
||||
let index = if let Some(index) = self.locals.iter().position(|v| v.0 == value) {
|
||||
self.locals[index].1 = visible;
|
||||
index
|
||||
@@ -52,11 +56,11 @@ impl LocalScope {
|
||||
Ok(U::new(index as u32).unwrap())
|
||||
}
|
||||
|
||||
pub fn make_visible(&mut self, index: U<16>) {
|
||||
pub fn make_visible(&mut self, index: LocalId) {
|
||||
self.locals[usize::from(index) - self.start_index as usize].1 = true;
|
||||
}
|
||||
|
||||
pub fn get(&self, value: &str) -> Option<U<16>> {
|
||||
pub fn get(&self, value: &str) -> Option<LocalId> {
|
||||
self.locals
|
||||
.iter()
|
||||
.position(|v| v.0.as_ref() == value && v.1)
|
||||
@@ -116,7 +120,7 @@ impl FunctionBlock {
|
||||
self.locals_stack.last()
|
||||
}
|
||||
|
||||
fn lookup_local(&self, identifier: &str) -> Option<U<16>> {
|
||||
fn lookup_local(&self, identifier: &str) -> Option<LocalId> {
|
||||
for scope in self.locals_stack.iter().rev() {
|
||||
if let Some(index) = scope.get(identifier) {
|
||||
return Some(index);
|
||||
@@ -202,7 +206,7 @@ impl<'a> LocalBlock<'a> {
|
||||
identifier: &Rc<str>,
|
||||
value: CompileValue,
|
||||
visible: bool,
|
||||
) -> Result<U<16>, CompileError> {
|
||||
) -> Result<LocalId, CompileError> {
|
||||
let index = self
|
||||
.function
|
||||
.local_scope_mut()
|
||||
@@ -225,6 +229,10 @@ impl<'a> LocalBlock<'a> {
|
||||
self.function.emit(Instruction::PushConstant(value));
|
||||
self.function.emit(Instruction::GetGlobal);
|
||||
}
|
||||
CompileValue::String(value) => {
|
||||
let index = self.module.constant(CompileConstant::String(value))?;
|
||||
self.function.emit(Instruction::PushConstant(index));
|
||||
}
|
||||
CompileValue::Local(index) => {
|
||||
self.function.emit(Instruction::GetLocal(index));
|
||||
}
|
||||
@@ -232,11 +240,11 @@ impl<'a> LocalBlock<'a> {
|
||||
self.function.emit(Instruction::PushBool(value));
|
||||
}
|
||||
CompileValue::Integer(value) => {
|
||||
// TODO signed/unsigned
|
||||
if let Some(value) = U::new(value as u32) {
|
||||
if let Some(value) = U::from_signed(value) {
|
||||
self.function.emit(Instruction::PushInteger(value));
|
||||
} else {
|
||||
todo!()
|
||||
let index = self.module.constant(CompileConstant::Integer(value))?;
|
||||
self.function.emit(Instruction::PushConstant(index));
|
||||
}
|
||||
}
|
||||
CompileValue::Argument(index) => {
|
||||
@@ -452,6 +460,7 @@ impl<'a> LocalBlock<'a> {
|
||||
) -> Result<CompileValue, CompileError> {
|
||||
match expression {
|
||||
Expression::Nil => Ok(CompileValue::Nil),
|
||||
Expression::StringLiteral(value) => Ok(CompileValue::String(value.clone())),
|
||||
Expression::BooleanLiteral(value) => Ok(CompileValue::Boolean(*value)),
|
||||
Expression::IntegerLiteral(value) => Ok(CompileValue::Integer(*value)),
|
||||
Expression::Identifier(identifier) => self.compile_identifier(identifier),
|
||||
|
||||
@@ -77,6 +77,7 @@ impl CompilationModule {
|
||||
let address = *function_offsets.get(&index).unwrap();
|
||||
ModuleConstant::LocalFunction(address, required_count)
|
||||
}
|
||||
CompileConstant::String(value) => ModuleConstant::String(value),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::value::{ConsCell, Keyword, Value};
|
||||
use crate::vm::value::{ConsCell, Keyword, Value, ValueString};
|
||||
|
||||
mod binding;
|
||||
mod call;
|
||||
@@ -20,6 +20,7 @@ pub use lambda::*;
|
||||
pub enum Expression {
|
||||
Nil,
|
||||
BooleanLiteral(bool),
|
||||
StringLiteral(ValueString),
|
||||
IntegerLiteral(i64),
|
||||
Identifier(Rc<str>),
|
||||
Lambda(LambdaExpression),
|
||||
@@ -55,6 +56,8 @@ impl Expression {
|
||||
Value::Nil => Self::Nil,
|
||||
Value::Boolean(value) => Self::BooleanLiteral(*value),
|
||||
Value::Integer(value) => Self::IntegerLiteral(*value),
|
||||
Value::String(value) => Self::StringLiteral(value.clone()),
|
||||
Value::Vector(_vector) => todo!(),
|
||||
Value::Identifier(value) => Self::Identifier(value.clone()),
|
||||
Value::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
@@ -102,9 +105,11 @@ impl CollectErrors<ParseError> for Expression {
|
||||
Self::Call(call) => call.collect_errors(errors),
|
||||
Self::Let(let_) => let_.collect_errors(errors),
|
||||
Self::Setq(setq) => setq.collect_errors(errors),
|
||||
Self::Nil | Self::IntegerLiteral(_) | Self::Identifier(_) | Self::BooleanLiteral(_) => {
|
||||
false
|
||||
}
|
||||
Self::Nil
|
||||
| Self::IntegerLiteral(_)
|
||||
| Self::Identifier(_)
|
||||
| Self::BooleanLiteral(_)
|
||||
| Self::StringLiteral(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::instruction::{MathInstruction, U};
|
||||
use crate::vm::{
|
||||
instruction::{LocalId, MathInstruction},
|
||||
value::ValueString,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CompileValue {
|
||||
Integer(i64),
|
||||
String(ValueString),
|
||||
Boolean(bool),
|
||||
LocalFunction(u32, usize),
|
||||
Local(U<16>),
|
||||
Local(LocalId),
|
||||
Argument(usize),
|
||||
Global(Rc<str>),
|
||||
Stack,
|
||||
@@ -19,6 +23,7 @@ pub enum CompileConstant {
|
||||
Integer(i64),
|
||||
LocalFunction(u32, usize),
|
||||
Identifier(Rc<str>),
|
||||
String(ValueString),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
|
||||
+24
-1
@@ -1,7 +1,7 @@
|
||||
use crate::vm::{
|
||||
machine::{Machine, MachineError},
|
||||
native::NativeFunction,
|
||||
value::{BytecodeFunction, ConsCell, Keyword, Value},
|
||||
value::{BytecodeFunction, ConsCell, Keyword, Value, ValueString},
|
||||
};
|
||||
|
||||
pub enum AnyFunction {
|
||||
@@ -41,6 +41,8 @@ macro_rules! impl_primitive_value {
|
||||
match value {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
&Value::Integer(value) if let Ok(value) = value.try_into() => Ok(value),
|
||||
&Value::Boolean(true) => Ok(1),
|
||||
&Value::Boolean(false) => Ok(0),
|
||||
_ => Err(MachineError::InvalidArgument)
|
||||
}
|
||||
}
|
||||
@@ -72,6 +74,8 @@ impl TryFromValue<'_> for bool {
|
||||
match value {
|
||||
Value::Nil => Ok(false),
|
||||
Value::Cons(_) => Ok(true),
|
||||
Value::Vector(vector) => Ok(!vector.is_empty()),
|
||||
Value::String(value) => Ok(!value.is_empty()),
|
||||
Value::Boolean(value) => Ok(*value),
|
||||
Value::Integer(value) => Ok(*value != 0),
|
||||
Value::Keyword(_)
|
||||
@@ -82,6 +86,25 @@ impl TryFromValue<'_> for bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<bool> for Value {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Boolean(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for ValueString {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, MachineError> {
|
||||
match value {
|
||||
Value::String(value) => Ok(value.clone()),
|
||||
_ => Err(MachineError::InvalidArgument),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ValueString> for Value {
|
||||
fn from(value: ValueString) -> Self {
|
||||
Self::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for Value {
|
||||
fn try_from_value(value: &Value) -> Result<Self, MachineError> {
|
||||
|
||||
+56
-3
@@ -1,14 +1,14 @@
|
||||
use nom::{
|
||||
AsChar, FindToken, IResult, Input, Parser,
|
||||
branch::alt,
|
||||
bytes::streaming::tag,
|
||||
bytes::streaming::{is_not, tag},
|
||||
character::{
|
||||
anychar,
|
||||
streaming::{char, one_of},
|
||||
},
|
||||
combinator::{map, map_res, opt, recognize, value},
|
||||
combinator::{map, map_res, opt, recognize, value, verify},
|
||||
error::{Error, ErrorKind, FromExternalError, ParseError},
|
||||
multi::{fold_many1, many0},
|
||||
multi::{fold, fold_many1, many0},
|
||||
sequence::{delimited, preceded},
|
||||
};
|
||||
|
||||
@@ -190,6 +190,58 @@ fn parse_boolean(input: &str) -> IResult<&str, Value> {
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
enum StringSegment<'a> {
|
||||
Literal(&'a str),
|
||||
Escape(char),
|
||||
}
|
||||
|
||||
fn parse_literal_string_segment(input: &str) -> IResult<&str, &str> {
|
||||
verify(is_not("\"\\"), |s: &str| !s.is_empty()).parse(input)
|
||||
}
|
||||
|
||||
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
|
||||
preceded(
|
||||
char('\\'),
|
||||
alt((
|
||||
value('\n', char('n')),
|
||||
value('\r', char('r')),
|
||||
value('\t', char('t')),
|
||||
value('\\', char('\\')),
|
||||
value('"', char('"')),
|
||||
)),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_string_segment(input: &str) -> IResult<&str, StringSegment<'_>> {
|
||||
alt((
|
||||
map(parse_literal_string_segment, StringSegment::Literal),
|
||||
map(parse_escaped_char, StringSegment::Escape),
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_string(input: &str) -> IResult<&str, Value> {
|
||||
delimited(
|
||||
char('"'),
|
||||
fold(
|
||||
0..,
|
||||
parse_string_segment,
|
||||
String::new,
|
||||
|mut string, segment| {
|
||||
match segment {
|
||||
StringSegment::Literal(s) => string.push_str(s),
|
||||
StringSegment::Escape(c) => string.push(c),
|
||||
};
|
||||
string
|
||||
},
|
||||
),
|
||||
char('"'),
|
||||
)
|
||||
.map(|s| Value::String(s.into()))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
pub fn skip_comment_and_whitespace(mut input: &str) -> IResult<&str, ()> {
|
||||
let mut in_comment = false;
|
||||
while let Some(ch) = input.chars().next() {
|
||||
@@ -225,6 +277,7 @@ pub fn parse_value(input: &str) -> IResult<&str, Value> {
|
||||
parse_list_or_nil,
|
||||
parse_boolean,
|
||||
parse_integer,
|
||||
parse_string,
|
||||
parse_identifier_or_keyword_or_nil,
|
||||
))
|
||||
.parse(input)
|
||||
|
||||
+88
-50
@@ -73,31 +73,39 @@ primitive_enum! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Instruction {
|
||||
PushNil,
|
||||
PushInteger(U<24>),
|
||||
PushInteger(PackedInteger),
|
||||
PushBool(bool),
|
||||
PushConstant(ConstantId),
|
||||
PushArgument(U<6>),
|
||||
PushArgument(ArgumentId),
|
||||
Drop,
|
||||
SetGlobal,
|
||||
GetGlobal,
|
||||
SetLocal(U<16>),
|
||||
GetLocal(U<16>),
|
||||
Call(U<6>),
|
||||
SetLocal(LocalId),
|
||||
GetLocal(LocalId),
|
||||
Call(ArgumentCount),
|
||||
Return,
|
||||
Math(MathInstruction, U<6>),
|
||||
Branch(U<12>),
|
||||
Jump(U<12>),
|
||||
Math(MathInstruction, ArgumentCount),
|
||||
Branch(FunctionOffset),
|
||||
Jump(FunctionOffset),
|
||||
}
|
||||
|
||||
pub type ConstantId = U<24>;
|
||||
pub type ConstantId = U<12>;
|
||||
pub type FunctionOffset = U<12>;
|
||||
pub type LocalId = U<8>;
|
||||
pub type ArgumentId = U<6>;
|
||||
pub type ArgumentCount = U<6>;
|
||||
pub type PackedInteger = U<12>;
|
||||
|
||||
impl<const N: usize> U<N> {
|
||||
pub const BITS: usize = N;
|
||||
pub const ZERO: Self = Self(0);
|
||||
|
||||
const UNSIGNED_MASK: i64 = (1 << (N - 1)) - 1;
|
||||
const SIGNED_MASK: i64 = (1 << N) - 1;
|
||||
|
||||
pub fn sign_extend_i64(&self) -> i64 {
|
||||
if self.0 & (1 << N) != 0 {
|
||||
todo!()
|
||||
if self.0 & (1 << (N - 1)) != 0 {
|
||||
self.0 as i64 | !Self::SIGNED_MASK
|
||||
} else {
|
||||
self.0 as i64
|
||||
}
|
||||
@@ -111,6 +119,16 @@ impl<const N: usize> U<N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_signed(value: i64) -> Option<Self> {
|
||||
if value > 0 && value & !Self::UNSIGNED_MASK == 0 {
|
||||
Some(Self(value as u32))
|
||||
} else if value < 0 && value & !Self::SIGNED_MASK == !Self::SIGNED_MASK {
|
||||
Some(Self((value & Self::SIGNED_MASK) as u32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn truncate(value: u32) -> Self {
|
||||
Self(value & ((1 << N) - 1))
|
||||
}
|
||||
@@ -141,22 +159,22 @@ impl<const N: usize> From<U<N>> for u32 {
|
||||
impl From<Instruction> for u32 {
|
||||
fn from(instruction: Instruction) -> u32 {
|
||||
match instruction {
|
||||
Instruction::Drop => 0b0000_0000_0000_0000,
|
||||
Instruction::PushNil => 0b0000_0000_0000_0001,
|
||||
Instruction::PushBool(value) => 0b0000_0000_0000_0010 | (value as u32),
|
||||
Instruction::Return => 0b0000_0000_0000_0100,
|
||||
Instruction::SetGlobal => 0b0000_0000_0000_0101,
|
||||
Instruction::GetGlobal => 0b0000_0000_0000_0110,
|
||||
Instruction::GetLocal(value) => 0b0000_0010_0000_0000 | value.0,
|
||||
Instruction::SetLocal(value) => 0b0000_0011_0000_0000 | value.0,
|
||||
Instruction::Call(count) => 0b0000_0000_0100_0000 | count.0,
|
||||
Instruction::PushInteger(value) => 0b0001_0000_0000_0000 | value.0,
|
||||
Instruction::PushConstant(index) => 0b0010_0000_0000_0000 | index.0,
|
||||
Instruction::PushArgument(index) => 0b0000_0000_1000_0000 | index.0,
|
||||
Instruction::Branch(offset) => 0b0000_1000_0000_0000 | offset.0,
|
||||
Instruction::Jump(offset) => 0b0000_1100_0000_0000 | offset.0,
|
||||
Instruction::Drop => 0b00000000_00000000,
|
||||
Instruction::PushNil => 0b00000000_00000001,
|
||||
Instruction::PushBool(value) => 0b00000000_00000010 | (value as u32),
|
||||
Instruction::Return => 0b00000000_00000100,
|
||||
Instruction::SetGlobal => 0b00000000_00000101,
|
||||
Instruction::GetGlobal => 0b00000000_00000110,
|
||||
Instruction::GetLocal(value) => 0b00000010_00000000 | value.0,
|
||||
Instruction::SetLocal(value) => 0b00000011_00000000 | value.0,
|
||||
Instruction::Call(count) => 0b00000000_01000000 | count.0,
|
||||
Instruction::PushInteger(value) => 0b00010000_00000000 | value.0,
|
||||
Instruction::PushConstant(index) => 0b00100000_00000000 | index.0,
|
||||
Instruction::PushArgument(index) => 0b00000000_10000000 | index.0,
|
||||
Instruction::Branch(offset) => 0b00001000_00000000 | offset.0,
|
||||
Instruction::Jump(offset) => 0b00001100_00000000 | offset.0,
|
||||
Instruction::Math(math, count) => {
|
||||
0b0000_0100_0000_0000 | (u32::from(math) << 6) | count.0
|
||||
0b00000100_00000000 | (u32::from(math) << 6) | count.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,33 +187,53 @@ impl TryFrom<u32> for Instruction {
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
#[bitmatch]
|
||||
match value {
|
||||
"0000_0000_0000_0000" => Ok(Instruction::Drop),
|
||||
"0000_0000_0000_0001" => Ok(Instruction::PushNil),
|
||||
"0000_0000_0000_001x" => Ok(Instruction::PushBool(x != 0)),
|
||||
"0000_0000_0000_0100" => Ok(Instruction::Return),
|
||||
"0000_0000_0000_0101" => Ok(Instruction::SetGlobal),
|
||||
"0000_0000_0000_0110" => Ok(Instruction::GetGlobal),
|
||||
"0000_0000_0000_0111" => todo!(),
|
||||
"0000_0000_0000_1???" => todo!(),
|
||||
"00000000_00000000" => Ok(Instruction::Drop),
|
||||
"00000000_00000001" => Ok(Instruction::PushNil),
|
||||
"00000000_0000001x" => Ok(Instruction::PushBool(x != 0)),
|
||||
"00000000_00000100" => Ok(Instruction::Return),
|
||||
"00000000_00000101" => Ok(Instruction::SetGlobal),
|
||||
"00000000_00000110" => Ok(Instruction::GetGlobal),
|
||||
"00000000_00000111" => todo!(),
|
||||
"00000000_00001???" => todo!(),
|
||||
|
||||
"0000_0000_0001_????" => todo!(),
|
||||
"0000_0000_001?_????" => todo!(),
|
||||
"0000_0000_01xx_xxxx" => Ok(Instruction::Call(U(x))),
|
||||
"0000_0000_10xx_xxxx" => Ok(Instruction::PushArgument(U(x))),
|
||||
"0000_0000_11??_????" => todo!(),
|
||||
"00000000_0001????" => todo!(),
|
||||
"00000000_001?????" => todo!(),
|
||||
"00000000_01xxxxxx" => Ok(Instruction::Call(U(x))),
|
||||
"00000000_10xxxxxx" => Ok(Instruction::PushArgument(U(x))),
|
||||
"00000000_11??????" => todo!(),
|
||||
|
||||
"0000_0001_????_????" => todo!(),
|
||||
"0000_0010_xxxx_xxxx" => Ok(Instruction::GetLocal(U(x))),
|
||||
"0000_0011_xxxx_xxxx" => Ok(Instruction::SetLocal(U(x))),
|
||||
"0000_01xx_xxyy_yyyy" => Ok(Instruction::Math(x.try_into()?, U(y))),
|
||||
"0000_10xx_xxxx_xxxx" => Ok(Instruction::Branch(U(x))),
|
||||
"0000_11xx_xxxx_xxxx" => Ok(Instruction::Jump(U(x))),
|
||||
"00000001_????????" => todo!(),
|
||||
"00000010_xxxxxxxx" => Ok(Instruction::GetLocal(U(x))),
|
||||
"00000011_xxxxxxxx" => Ok(Instruction::SetLocal(U(x))),
|
||||
"000001xx_xxyyyyyy" => Ok(Instruction::Math(x.try_into()?, U(y))),
|
||||
"000010xx_xxxxxxxx" => Ok(Instruction::Branch(U(x))),
|
||||
"000011xx_xxxxxxxx" => Ok(Instruction::Jump(U(x))),
|
||||
|
||||
"0001_xxxx_xxxx_xxxx" => Ok(Instruction::PushInteger(U(x))),
|
||||
"0010_xxxx_xxxx_xxxx" => Ok(Instruction::PushConstant(U(x))),
|
||||
"0011_????_????_????" => todo!(),
|
||||
"01??_????_????_????" => todo!(),
|
||||
"1???_????_????_????" => todo!(),
|
||||
"0001xxxx_xxxxxxxx" => Ok(Instruction::PushInteger(U(x))),
|
||||
"0010xxxx_xxxxxxxx" => Ok(Instruction::PushConstant(U(x))),
|
||||
"0011????_????????" => todo!(),
|
||||
"01??????_????????" => todo!(),
|
||||
"1???????_????????" => todo!("{value:032b}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vm::instruction::U;
|
||||
|
||||
#[test]
|
||||
fn test_u_convert() {
|
||||
type T = U<24>;
|
||||
let t = T::from_signed(-1234).unwrap();
|
||||
assert_eq!(t.0, (-1234i64 & 0xFFFFFF) as u32);
|
||||
assert_eq!(t.sign_extend_i64(), -1234);
|
||||
let t = T::from_signed(1234).unwrap();
|
||||
assert_eq!(t.0, 1234);
|
||||
|
||||
let t = T::from_signed(0x800000);
|
||||
assert!(t.is_none());
|
||||
let t = T::from_signed(-0x1000001);
|
||||
assert!(t.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@ impl Machine {
|
||||
address,
|
||||
})
|
||||
}
|
||||
ModuleConstant::String(value) => Value::String(value),
|
||||
ModuleConstant::Integer(value) => Value::Integer(value),
|
||||
ModuleConstant::Identifier(identifier) => Value::Identifier(identifier),
|
||||
};
|
||||
|
||||
+3
-1
@@ -5,7 +5,7 @@ use crate::{
|
||||
vm::{
|
||||
instruction::{ConstantId, Instruction},
|
||||
pool::Pool,
|
||||
value::Value,
|
||||
value::{Value, ValueString},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum ModuleConstant {
|
||||
Integer(i64),
|
||||
LocalFunction(usize, usize),
|
||||
Identifier(Rc<str>),
|
||||
String(ValueString),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -188,6 +189,7 @@ impl ModuleBuilder {
|
||||
impl fmt::Display for ModuleConstant {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::String(value) => write!(f, "string {value:?}"),
|
||||
Self::Integer(value) => fmt::Display::fmt(value, f),
|
||||
Self::Identifier(ident) => write!(f, "ident {ident:?}"),
|
||||
Self::LocalFunction(address, _) => write!(f, "label {address}"),
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{BitAnd, BitOr, BitXor},
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
convert::{AnyFunction, TryFromValue},
|
||||
util::IteratorExt,
|
||||
convert::TryFromValue,
|
||||
vm::{
|
||||
machine::{Machine, MachineError},
|
||||
value::Value,
|
||||
},
|
||||
};
|
||||
|
||||
fn value_add(a: &Value, b: &Value) -> Result<Value, MachineError> {
|
||||
match (a, b) {
|
||||
(Value::String(a), _) => {
|
||||
let b = b.stringify()?;
|
||||
Ok(Value::String(format!("{a}{b}").into()))
|
||||
}
|
||||
(_, Value::String(b)) => {
|
||||
let a = a.stringify()?;
|
||||
Ok(Value::String(format!("{a}{b}").into()))
|
||||
}
|
||||
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_add(*b))),
|
||||
(Value::Integer(a), _) => Ok(Value::Integer(a.wrapping_add(i64::try_from_value(b)?))),
|
||||
(_, Value::Integer(b)) => Ok(Value::Integer(i64::try_from_value(a)?.wrapping_add(*b))),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Integer(*a as i64 + *b as i64)),
|
||||
_ => Err(MachineError::InvalidArgument),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum CompareOperation {
|
||||
Eq,
|
||||
Ne,
|
||||
@@ -22,41 +38,38 @@ pub(crate) enum CompareOperation {
|
||||
Ge,
|
||||
}
|
||||
|
||||
fn builtin_fold_integer<F>(
|
||||
fold: F,
|
||||
mut accumulator: i64,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError>
|
||||
fn builtin_fold<F>(fold: F, mut accumulator: Value, args: &[Value]) -> Result<Value, MachineError>
|
||||
where
|
||||
F: Fn(i64, i64) -> i64,
|
||||
F: Fn(&Value, &Value) -> Result<Value, MachineError>,
|
||||
{
|
||||
for arg in args {
|
||||
match arg {
|
||||
&Value::Integer(value) => {
|
||||
accumulator = fold(accumulator, value);
|
||||
}
|
||||
_ => todo!("Math for {arg}"),
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i == 0 {
|
||||
accumulator = arg.clone();
|
||||
} else {
|
||||
accumulator = fold(&accumulator, arg)?;
|
||||
}
|
||||
}
|
||||
Ok(Value::Integer(accumulator))
|
||||
Ok(accumulator)
|
||||
}
|
||||
fn builtin_fold_bool<F>(
|
||||
|
||||
fn builtin_fold_t<'a, T, F>(
|
||||
fold: F,
|
||||
mut accumulator: bool,
|
||||
args: &[Value],
|
||||
mut accumulator: T,
|
||||
args: &'a [Value],
|
||||
) -> Result<Value, MachineError>
|
||||
where
|
||||
F: Fn(bool, bool) -> bool,
|
||||
F: Fn(T, T) -> T,
|
||||
T: TryFromValue<'a> + Into<Value>,
|
||||
{
|
||||
for arg in args {
|
||||
let arg = bool::try_from_value(arg)?;
|
||||
let arg = T::try_from_value(arg)?;
|
||||
accumulator = fold(accumulator, arg);
|
||||
}
|
||||
Ok(Value::Boolean(accumulator))
|
||||
Ok(accumulator.into())
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_add(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_fold_integer(i64::wrapping_add, 0, args)
|
||||
builtin_fold(value_add, Value::Integer(0), args)
|
||||
}
|
||||
pub(crate) fn builtin_sub(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
match args {
|
||||
@@ -81,7 +94,7 @@ pub(crate) fn builtin_sub(_vm: &mut Machine, args: &[Value]) -> Result<Value, Ma
|
||||
}
|
||||
}
|
||||
pub(crate) fn builtin_mul(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_fold_integer(i64::wrapping_mul, 1, args)
|
||||
builtin_fold_t(i64::wrapping_mul, 1, args)
|
||||
}
|
||||
pub(crate) fn builtin_mod(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
let [x, y] = args else {
|
||||
@@ -116,25 +129,25 @@ pub(crate) fn builtin_div(_vm: &mut Machine, args: &[Value]) -> Result<Value, Ma
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_and(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_fold_bool(BitAnd::bitand, true, args)
|
||||
builtin_fold_t(BitAnd::bitand, true, args)
|
||||
}
|
||||
pub(crate) fn builtin_or(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_fold_bool(BitOr::bitor, false, args)
|
||||
builtin_fold_t(BitOr::bitor, false, args)
|
||||
}
|
||||
pub(crate) fn builtin_bitwise_and(
|
||||
_vm: &mut Machine,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_fold_integer(BitAnd::bitand, 1, args)
|
||||
builtin_fold_t(BitAnd::bitand, 1, args)
|
||||
}
|
||||
pub(crate) fn builtin_bitwise_or(_vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_fold_integer(BitOr::bitor, 0, args)
|
||||
builtin_fold_t(BitOr::bitor, 0, args)
|
||||
}
|
||||
pub(crate) fn builtin_bitwise_xor(
|
||||
_vm: &mut Machine,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_fold_integer(BitXor::bitxor, 0, args)
|
||||
builtin_fold_t(BitXor::bitxor, 0, args)
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_cmp(
|
||||
@@ -146,7 +159,8 @@ pub(crate) fn builtin_cmp(
|
||||
match (a, b) {
|
||||
(Value::Integer(a), Value::Integer(b)) => Ord::cmp(a, b),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => Ord::cmp(a, b),
|
||||
_ => todo!(),
|
||||
(Value::String(a), Value::String(b)) => Ord::cmp(a, b),
|
||||
_ => Ordering::Less,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,104 +202,3 @@ pub(crate) fn builtin_cmp_ge(vm: &mut Machine, args: &[Value]) -> Result<Value,
|
||||
pub(crate) fn builtin_cmp_le(vm: &mut Machine, args: &[Value]) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Le)
|
||||
}
|
||||
|
||||
pub fn load(vm: &mut Machine) {
|
||||
// math
|
||||
vm.defun_native("+", builtin_add);
|
||||
vm.defun_native("-", builtin_sub);
|
||||
vm.defun_native("*", builtin_mul);
|
||||
vm.defun_native("%", builtin_mod);
|
||||
vm.defun_native("/", builtin_div);
|
||||
|
||||
vm.defun_native("&&", builtin_and);
|
||||
vm.defun_native("||", builtin_or);
|
||||
vm.defun_native("&", builtin_bitwise_and);
|
||||
vm.defun_native("|", builtin_bitwise_or);
|
||||
vm.defun_native("^", builtin_bitwise_xor);
|
||||
|
||||
vm.defun_native(">", builtin_cmp_gt);
|
||||
vm.defun_native("<", builtin_cmp_lt);
|
||||
vm.defun_native("=", builtin_cmp_eq);
|
||||
vm.defun_native("/=", builtin_cmp_ne);
|
||||
vm.defun_native(">=", builtin_cmp_ge);
|
||||
vm.defun_native("<=", builtin_cmp_le);
|
||||
|
||||
// lists
|
||||
vm.defun_native("map", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let xs = xs.proper_iter(MachineError::InvalidArgument);
|
||||
let out = Value::try_list_or_nil(xs.map(|v| f.invoke(vm, slice::from_ref(v?))))?;
|
||||
Ok(out)
|
||||
});
|
||||
vm.defun_native("filter", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let xs = xs
|
||||
.proper_iter(MachineError::InvalidArgument)
|
||||
.map(|x| x.cloned());
|
||||
let out = Value::try_list_or_nil(xs.try_filter(|v| {
|
||||
let result = f.invoke(vm, slice::from_ref(v))?;
|
||||
bool::try_from_value(&result)
|
||||
}))?;
|
||||
Ok(out)
|
||||
});
|
||||
vm.defun_native("list", |_, args| {
|
||||
let out = Value::list_or_nil(args.iter().cloned());
|
||||
Ok(out)
|
||||
});
|
||||
|
||||
// functional
|
||||
vm.defun_native("identity", |_, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
Ok(arg.clone())
|
||||
});
|
||||
|
||||
// eval
|
||||
vm.defun_native("apply", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let args = xs
|
||||
.proper_iter(MachineError::InvalidArgument)
|
||||
.map(|x| x.cloned())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
f.invoke(vm, &args[..])
|
||||
});
|
||||
vm.defun_native("assert", |vm, args| match args {
|
||||
[] => Err(MachineError::InvalidArgument),
|
||||
[cond] => {
|
||||
let cond = bool::try_from_value(cond)?;
|
||||
if !cond {
|
||||
let ip = vm.ip();
|
||||
if let Some(ip) = ip {
|
||||
eprintln!("Assertion failed at {ip}:");
|
||||
eprintln!();
|
||||
ip.module.dump(Some(ip.address), 8);
|
||||
}
|
||||
panic!("Assertion failed");
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
_ => todo!(),
|
||||
});
|
||||
|
||||
// io
|
||||
vm.defun_native("print", |_, args| {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
print!(" ");
|
||||
}
|
||||
print!("{arg}");
|
||||
}
|
||||
println!();
|
||||
Ok(Value::Nil)
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
use std::slice;
|
||||
|
||||
use crate::{
|
||||
convert::{AnyFunction, TryFromValue},
|
||||
util::IteratorExt,
|
||||
vm::{
|
||||
machine::{Machine, MachineError},
|
||||
value::{Value, ValueString},
|
||||
},
|
||||
};
|
||||
|
||||
mod math;
|
||||
pub(crate) use math::*;
|
||||
|
||||
pub fn load(vm: &mut Machine) {
|
||||
// math
|
||||
vm.defun_native("+", builtin_add);
|
||||
vm.defun_native("-", builtin_sub);
|
||||
vm.defun_native("*", builtin_mul);
|
||||
vm.defun_native("%", builtin_mod);
|
||||
vm.defun_native("/", builtin_div);
|
||||
|
||||
vm.defun_native("&&", builtin_and);
|
||||
vm.defun_native("||", builtin_or);
|
||||
vm.defun_native("&", builtin_bitwise_and);
|
||||
vm.defun_native("|", builtin_bitwise_or);
|
||||
vm.defun_native("^", builtin_bitwise_xor);
|
||||
|
||||
vm.defun_native(">", builtin_cmp_gt);
|
||||
vm.defun_native("<", builtin_cmp_lt);
|
||||
vm.defun_native("=", builtin_cmp_eq);
|
||||
vm.defun_native("/=", builtin_cmp_ne);
|
||||
vm.defun_native(">=", builtin_cmp_ge);
|
||||
vm.defun_native("<=", builtin_cmp_le);
|
||||
|
||||
// conversion
|
||||
vm.defun_native("string->int", |_vm, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let arg = ValueString::try_from_value(arg)?;
|
||||
let result = arg.parse::<i64>().map(Value::Integer).unwrap_or(Value::Nil);
|
||||
Ok(result)
|
||||
});
|
||||
vm.defun_native("int->string", |_vm, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let arg = i64::try_from_value(arg)?;
|
||||
let result = Value::String(format!("{arg}").into());
|
||||
Ok(result)
|
||||
});
|
||||
|
||||
// lists
|
||||
vm.defun_native("map", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let xs = xs.proper_iter(MachineError::InvalidArgument);
|
||||
let out = Value::try_list_or_nil(xs.map(|v| f.invoke(vm, slice::from_ref(v?))))?;
|
||||
Ok(out)
|
||||
});
|
||||
vm.defun_native("filter", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let xs = xs
|
||||
.proper_iter(MachineError::InvalidArgument)
|
||||
.map(|x| x.cloned());
|
||||
let out = Value::try_list_or_nil(xs.try_filter(|v| {
|
||||
let result = f.invoke(vm, slice::from_ref(v))?;
|
||||
bool::try_from_value(&result)
|
||||
}))?;
|
||||
Ok(out)
|
||||
});
|
||||
vm.defun_native("list", |_, args| {
|
||||
let out = Value::list_or_nil(args.iter().cloned());
|
||||
Ok(out)
|
||||
});
|
||||
|
||||
// functional
|
||||
vm.defun_native("identity", |_, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
Ok(arg.clone())
|
||||
});
|
||||
|
||||
// eval
|
||||
vm.defun_native("apply", |vm, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgument);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let args = xs
|
||||
.proper_iter(MachineError::InvalidArgument)
|
||||
.map(|x| x.cloned())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
f.invoke(vm, &args[..])
|
||||
});
|
||||
vm.defun_native("assert", |vm, args| match args {
|
||||
[] => Err(MachineError::InvalidArgument),
|
||||
[cond] => {
|
||||
let cond = bool::try_from_value(cond)?;
|
||||
if !cond {
|
||||
let ip = vm.ip();
|
||||
if let Some(ip) = ip {
|
||||
eprintln!("Assertion failed at {ip}:");
|
||||
eprintln!();
|
||||
ip.module.dump(Some(ip.address), 8);
|
||||
}
|
||||
panic!("Assertion failed");
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
_ => todo!(),
|
||||
});
|
||||
vm.defun_native("assert-equal", |vm, args| match args {
|
||||
[] | [_] => Err(MachineError::InvalidArgument),
|
||||
[a, b] => {
|
||||
if a != b {
|
||||
let ip = vm.ip();
|
||||
if let Some(ip) = ip {
|
||||
eprintln!("Assertion failed at {ip}:");
|
||||
eprintln!();
|
||||
eprintln!(":: {a} ≠ {b}");
|
||||
eprintln!();
|
||||
ip.module.dump(Some(ip.address), 8);
|
||||
}
|
||||
panic!("Assertion failed");
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
[a, b, msg] => {
|
||||
if a != b {
|
||||
let ip = vm.ip();
|
||||
if let Some(ip) = ip {
|
||||
eprintln!("Assertion failed at {ip}: {msg}");
|
||||
eprintln!();
|
||||
eprintln!(":: {a} ≠ {b}");
|
||||
eprintln!();
|
||||
ip.module.dump(Some(ip.address), 8);
|
||||
}
|
||||
panic!("Assertion failed");
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
_ => todo!(),
|
||||
});
|
||||
|
||||
// io
|
||||
vm.defun_native("print", |_, args| {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
print!(" ");
|
||||
}
|
||||
print!("{arg}");
|
||||
}
|
||||
println!();
|
||||
Ok(Value::Nil)
|
||||
});
|
||||
}
|
||||
+76
-1
@@ -1,4 +1,4 @@
|
||||
use std::{any::Any, fmt, rc::Rc};
|
||||
use std::{any::Any, cell::RefCell, fmt, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
compile::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
@@ -22,6 +22,12 @@ pub struct ProperListIter<'a, E> {
|
||||
error: Option<E>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Vector(RefCell<Vec<Value>>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ValueString(Rc<str>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
// "Expression" values
|
||||
@@ -31,6 +37,8 @@ pub enum Value {
|
||||
Identifier(Rc<str>),
|
||||
Cons(Rc<ConsCell>),
|
||||
Keyword(Keyword),
|
||||
String(ValueString),
|
||||
Vector(Rc<Vector>),
|
||||
// "Runtime" values
|
||||
BytecodeFunction(BytecodeFunction),
|
||||
NativeFunction(NativeFunction),
|
||||
@@ -167,6 +175,22 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stringify(&self) -> Result<String, MachineError> {
|
||||
match self {
|
||||
Self::Integer(value) => Ok(format!("{value}")),
|
||||
Self::Boolean(true) => Ok("#t".into()),
|
||||
Self::Boolean(false) => Ok("#f".into()),
|
||||
Self::Identifier(value) => Ok(format!("{value}")),
|
||||
Self::String(value) => Ok(format!("{value}")),
|
||||
Self::Nil => todo!(),
|
||||
Self::Cons(_) => todo!(),
|
||||
Self::Vector(_) => todo!(),
|
||||
Self::Keyword(_) => todo!(),
|
||||
Self::OpaqueValue(_) => todo!(),
|
||||
Self::NativeFunction(_) | Self::BytecodeFunction(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cons(self, cdr: Value) -> Self {
|
||||
Self::Cons(Rc::new(ConsCell(self, cdr)))
|
||||
}
|
||||
@@ -198,6 +222,36 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ValueString {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ValueString {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ValueString {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.borrow().is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.borrow().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConsCell {
|
||||
pub fn end(value: Value) -> Self {
|
||||
Self(value, Value::Nil)
|
||||
@@ -231,6 +285,25 @@ impl fmt::Display for BytecodeFunction {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Vector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "[")?;
|
||||
for (i, element) in self.0.borrow().iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{element}")?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ValueString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self.0.as_ref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -244,6 +317,8 @@ impl fmt::Display for Value {
|
||||
Self::BytecodeFunction(bytecode) => write!(f, "{bytecode}"),
|
||||
Self::NativeFunction(native) => write!(f, "{native}"),
|
||||
Self::OpaqueValue(opaque) => write!(f, "{opaque}"),
|
||||
Self::Vector(vector) => write!(f, "{vector}"),
|
||||
Self::String(value) => write!(f, "{value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user