1
0

Add the to_string, write and write_line natives

This commit is contained in:
Jeff 2024-10-30 05:16:34 -04:00
parent af4e43fc9f
commit c264aaeb13
7 changed files with 177 additions and 85 deletions

View File

@ -584,6 +584,7 @@ impl Instruction {
let native_function_name = match native_function { let native_function_name = match native_function {
NativeFunction::Panic => "PANIC", NativeFunction::Panic => "PANIC",
NativeFunction::ToString => "TO_STRING", NativeFunction::ToString => "TO_STRING",
NativeFunction::Write => "WRITE",
NativeFunction::WriteLine => "WRITE_LINE", NativeFunction::WriteLine => "WRITE_LINE",
}; };

View File

@ -567,8 +567,11 @@ impl<'src> Lexer<'src> {
"return" => Token::Return, "return" => Token::Return,
"str" => Token::Str, "str" => Token::Str,
"struct" => Token::Struct, "struct" => Token::Struct,
"to_string" => Token::ToString,
"true" => Token::Boolean("true"), "true" => Token::Boolean("true"),
"while" => Token::While, "while" => Token::While,
"write" => Token::Write,
"write_line" => Token::WriteLine,
_ => Token::Identifier(string), _ => Token::Identifier(string),
}; };
@ -1255,7 +1258,7 @@ mod tests {
assert_eq!( assert_eq!(
lex(input), lex(input),
Ok(vec![ Ok(vec![
(Token::Identifier("write_line"), Span(0, 10)), (Token::WriteLine, Span(0, 10)),
(Token::LeftParenthesis, Span(10, 11)), (Token::LeftParenthesis, Span(10, 11)),
(Token::String("Hello, world!"), Span(11, 26)), (Token::String("Hello, world!"), Span(11, 26)),
(Token::RightParenthesis, Span(26, 27)), (Token::RightParenthesis, Span(26, 27)),

View File

@ -2,12 +2,14 @@ use serde::{Deserialize, Serialize};
const PANIC: u8 = 0b0000_0000; const PANIC: u8 = 0b0000_0000;
const TO_STRING: u8 = 0b0000_0001; const TO_STRING: u8 = 0b0000_0001;
const WRITE_LINE: u8 = 0b0000_0010; const WRITE: u8 = 0b0000_0010;
const WRITE_LINE: u8 = 0b0000_0011;
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NativeFunction { pub enum NativeFunction {
Panic = PANIC as isize, Panic = PANIC as isize,
ToString = TO_STRING as isize, ToString = TO_STRING as isize,
Write = WRITE as isize,
WriteLine = WRITE_LINE as isize, WriteLine = WRITE_LINE as isize,
} }
@ -16,10 +18,11 @@ impl From<u8> for NativeFunction {
match byte { match byte {
PANIC => NativeFunction::Panic, PANIC => NativeFunction::Panic,
TO_STRING => NativeFunction::ToString, TO_STRING => NativeFunction::ToString,
WRITE => NativeFunction::Write,
WRITE_LINE => NativeFunction::WriteLine, WRITE_LINE => NativeFunction::WriteLine,
_ => { _ => {
if cfg!(test) { if cfg!(test) {
panic!("Invalid operation byte: {}", byte) panic!("Invalid native function byte: {}", byte)
} else { } else {
NativeFunction::Panic NativeFunction::Panic
} }
@ -33,6 +36,7 @@ impl From<NativeFunction> for u8 {
match native_function { match native_function {
NativeFunction::Panic => PANIC, NativeFunction::Panic => PANIC,
NativeFunction::ToString => TO_STRING, NativeFunction::ToString => TO_STRING,
NativeFunction::Write => WRITE,
NativeFunction::WriteLine => WRITE_LINE, NativeFunction::WriteLine => WRITE_LINE,
} }
} }

View File

@ -1058,7 +1058,18 @@ impl<'src> Parser<'src> {
Ok(()) Ok(())
} }
fn parse_panic(&mut self, _: Allowed) -> Result<(), ParseError> { fn parse_native_call(&mut self, _: Allowed) -> Result<(), ParseError> {
let native_function = match self.current_token {
Token::Panic => NativeFunction::Panic,
Token::ToString => NativeFunction::ToString,
Token::Write => NativeFunction::Write,
Token::WriteLine => NativeFunction::WriteLine,
_ => {
unreachable!()
}
};
let is_expression = self.current_token.is_expression();
let start = self.current_position.0; let start = self.current_position.0;
let start_register = self.next_register(); let start_register = self.next_register();
@ -1086,9 +1097,10 @@ impl<'src> Parser<'src> {
let end = self.current_position.1; let end = self.current_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.current_is_expression = is_expression;
self.emit_instruction( self.emit_instruction(
Instruction::call_native(to_register, NativeFunction::Panic, argument_count), Instruction::call_native(to_register, native_function, argument_count),
Span(start, end), Span(start, end),
); );
Ok(()) Ok(())
@ -1727,7 +1739,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
precedence: Precedence::None, precedence: Precedence::None,
}, },
Token::Panic => ParseRule { Token::Panic => ParseRule {
prefix: Some(Parser::parse_panic), prefix: Some(Parser::parse_native_call),
infix: None, infix: None,
precedence: Precedence::Call, precedence: Precedence::Call,
}, },
@ -1807,11 +1819,26 @@ impl From<&Token<'_>> for ParseRule<'_> {
precedence: Precedence::None, precedence: Precedence::None,
}, },
Token::Struct => todo!(), Token::Struct => todo!(),
Token::ToString => ParseRule {
prefix: Some(Parser::parse_native_call),
infix: None,
precedence: Precedence::Call,
},
Token::While => ParseRule { Token::While => ParseRule {
prefix: Some(Parser::parse_while), prefix: Some(Parser::parse_while),
infix: None, infix: None,
precedence: Precedence::None, precedence: Precedence::None,
}, },
Token::Write => ParseRule {
prefix: Some(Parser::parse_native_call),
infix: None,
precedence: Precedence::Call,
},
Token::WriteLine => ParseRule {
prefix: Some(Parser::parse_native_call),
infix: None,
precedence: Precedence::Call,
},
} }
} }
} }

View File

@ -3,13 +3,28 @@ use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Source code token. macro_rules! define_tokens {
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)] ($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
pub enum Token<'src> { #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
// End of file pub enum Token<'src> {
#[default] #[default]
Eof, Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
pub enum TokenKind {
Eof,
$(
$variant,
)*
}
};
}
define_tokens! {
// Hard-coded values // Hard-coded values
Boolean(&'src str), Boolean(&'src str),
Byte(&'src str), Byte(&'src str),
@ -36,7 +51,11 @@ pub enum Token<'src> {
Return, Return,
Str, Str,
Struct, Struct,
ToString,
While, While,
Write,
WriteLine,
// Symbols // Symbols
ArrowThin, ArrowThin,
@ -100,7 +119,10 @@ impl<'src> Token<'src> {
Token::Mut => 3, Token::Mut => 3,
Token::Str => 3, Token::Str => 3,
Token::Struct => 6, Token::Struct => 6,
Token::ToString => 8,
Token::While => 5, Token::While => 5,
Token::Write => 5,
Token::WriteLine => 10,
Token::BangEqual => 2, Token::BangEqual => 2,
Token::Bang => 1, Token::Bang => 1,
Token::Colon => 1, Token::Colon => 1,
@ -162,6 +184,7 @@ impl<'src> Token<'src> {
Token::Mut => "mut", Token::Mut => "mut",
Token::Str => "str", Token::Str => "str",
Token::Struct => "struct", Token::Struct => "struct",
Token::ToString => "to_string",
Token::While => "while", Token::While => "while",
Token::BangEqual => "!=", Token::BangEqual => "!=",
Token::Bang => "!", Token::Bang => "!",
@ -196,6 +219,8 @@ impl<'src> Token<'src> {
Token::SlashEqual => "/=", Token::SlashEqual => "/=",
Token::Star => "*", Token::Star => "*",
Token::StarEqual => "*=", Token::StarEqual => "*=",
Token::Write => "write",
Token::WriteLine => "write_line",
} }
} }
@ -257,7 +282,10 @@ impl<'src> Token<'src> {
Token::String(text) => TokenOwned::String(text.to_string()), Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str, Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct, Token::Struct => TokenOwned::Struct,
Token::ToString => TokenOwned::ToString,
Token::While => TokenOwned::While, Token::While => TokenOwned::While,
Token::Write => TokenOwned::Write,
Token::WriteLine => TokenOwned::WriteLine,
} }
} }
@ -318,8 +346,11 @@ impl<'src> Token<'src> {
Token::SlashEqual => TokenKind::SlashEqual, Token::SlashEqual => TokenKind::SlashEqual,
Token::Str => TokenKind::Str, Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String, Token::String(_) => TokenKind::String,
Token::ToString => TokenKind::ToString,
Token::Struct => TokenKind::Struct, Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While, Token::While => TokenKind::While,
Token::Write => TokenKind::Write,
Token::WriteLine => TokenKind::WriteLine,
} }
} }
@ -356,13 +387,13 @@ impl<'src> Token<'src> {
| Token::MinusEqual | Token::MinusEqual
| Token::Percent | Token::Percent
| Token::PercentEqual | Token::PercentEqual
| Token::Panic
| Token::Plus | Token::Plus
| Token::PlusEqual | Token::PlusEqual
| Token::Slash | Token::Slash
| Token::SlashEqual | Token::SlashEqual
| Token::Star | Token::Star
| Token::StarEqual | Token::StarEqual
| Token::ToString
) )
} }
} }
@ -425,8 +456,11 @@ impl<'src> Display for Token<'src> {
Token::StarEqual => write!(f, "*="), Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"), Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"), Token::String(value) => write!(f, "{value}"),
Token::ToString => write!(f, "to_string"),
Token::Struct => write!(f, "struct"), Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"), Token::While => write!(f, "while"),
Token::Write => write!(f, "write"),
Token::WriteLine => write!(f, "write_line"),
} }
} }
} }
@ -464,7 +498,10 @@ pub enum TokenOwned {
Panic, Panic,
Return, Return,
Str, Str,
ToString,
While, While,
Write,
WriteLine,
// Symbols // Symbols
ArrowThin, ArrowThin,
@ -561,80 +598,14 @@ impl Display for TokenOwned {
TokenOwned::Str => Token::Str.fmt(f), TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f), TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f), TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::ToString => Token::ToString.fmt(f),
TokenOwned::While => Token::While.fmt(f), TokenOwned::While => Token::While.fmt(f),
TokenOwned::Write => Token::Write.fmt(f),
TokenOwned::WriteLine => Token::WriteLine.fmt(f),
} }
} }
} }
/// Token representation that holds no data.
#[derive(Debug, PartialEq, Clone)]
pub enum TokenKind {
Eof,
Identifier,
// Hard-coded values
Boolean,
Byte,
Character,
Float,
Integer,
String,
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
Fn,
If,
Int,
Let,
Loop,
Map,
Panic,
Return,
Str,
While,
// Symbols
ArrowThin,
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessEqual,
Minus,
MinusEqual,
Mut,
Percent,
PercentEqual,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
StarEqual,
Struct,
Slash,
SlashEqual,
}
impl Display for TokenKind { impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self { match self {
@ -694,7 +665,10 @@ impl Display for TokenKind {
TokenKind::SlashEqual => Token::SlashEqual.fmt(f), TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"), TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f), TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::ToString => Token::ToString.fmt(f),
TokenKind::While => Token::While.fmt(f), TokenKind::While => Token::While.fmt(f),
TokenKind::Write => Token::Write.fmt(f),
TokenKind::WriteLine => Token::WriteLine.fmt(f),
} }
} }
} }

View File

@ -1,4 +1,8 @@
use std::{cmp::Ordering, mem::replace}; use std::{
cmp::Ordering,
io::{self, stdout, Write},
mem::replace,
};
use crate::{ use crate::{
parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, FunctionType, parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
@ -423,7 +427,56 @@ impl Vm {
Some(Value::Primitive(Primitive::String(string))) Some(Value::Primitive(Primitive::String(string)))
} }
NativeFunction::WriteLine => todo!(), NativeFunction::Write => {
let mut stdout = stdout();
for argument_index in 0..argument_count {
if argument_index != 0 {
stdout.write(b" ").map_err(|io_error| VmError::Io {
error: io_error.kind(),
position,
})?;
}
let argument = self.get(argument_index, position)?;
write!(stdout, "{}", argument).map_err(|io_error| VmError::Io {
error: io_error.kind(),
position,
})?;
}
None
}
NativeFunction::WriteLine => {
let mut stdout = stdout();
for argument_index in 0..argument_count {
if argument_index != 0 {
stdout.write(b" ").map_err(|io_error| VmError::Io {
error: io_error.kind(),
position,
})?;
}
let argument_string =
self.get(argument_index, position)?.to_string();
stdout.write_all(argument_string.as_bytes()).map_err(
|io_error| VmError::Io {
error: io_error.kind(),
position,
},
)?;
}
stdout.write(b"\n").map_err(|io_error| VmError::Io {
error: io_error.kind(),
position,
})?;
None
}
}; };
if let Some(value) = return_value { if let Some(value) = return_value {
@ -695,6 +748,10 @@ pub enum VmError {
// Wrappers for foreign errors // Wrappers for foreign errors
Chunk(ChunkError), Chunk(ChunkError),
Io {
error: io::ErrorKind,
position: Span,
},
Value { Value {
error: ValueError, error: ValueError,
position: Span, position: Span,
@ -725,6 +782,7 @@ impl AnnotatedError for VmError {
Self::StackUnderflow { .. } => "Stack underflow", Self::StackUnderflow { .. } => "Stack underflow",
Self::UndefinedVariable { .. } => "Undefined variable", Self::UndefinedVariable { .. } => "Undefined variable",
Self::Chunk(error) => error.description(), Self::Chunk(error) => error.description(),
Self::Io { .. } => "I/O error",
Self::Value { .. } => "Value error", Self::Value { .. } => "Value error",
} }
} }
@ -741,6 +799,7 @@ impl AnnotatedError for VmError {
Some(format!("{identifier} is not in scope")) Some(format!("{identifier} is not in scope"))
} }
Self::Chunk(error) => error.details(), Self::Chunk(error) => error.details(),
Self::Io { error, .. } => Some(error.to_string()),
Self::Value { error, .. } => Some(error.to_string()), Self::Value { error, .. } => Some(error.to_string()),
_ => None, _ => None,
} }
@ -759,6 +818,7 @@ impl AnnotatedError for VmError {
Self::StackOverflow { position } => *position, Self::StackOverflow { position } => *position,
Self::UndefinedVariable { position, .. } => *position, Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(), Self::Chunk(error) => error.position(),
Self::Io { position, .. } => *position,
Self::Value { position, .. } => *position, Self::Value { position, .. } => *position,
} }
} }

View File

@ -15,7 +15,6 @@ fn panic() {
Instruction::call_native(2, NativeFunction::Panic, 2), Instruction::call_native(2, NativeFunction::Panic, 2),
Span(0, 27) Span(0, 27)
), ),
(Instruction::r#return(true), Span(27, 27))
], ],
vec![Value::string("Goodbye world!"), Value::integer(42)], vec![Value::string("Goodbye world!"), Value::integer(42)],
vec![] vec![]
@ -33,3 +32,27 @@ fn panic() {
}) })
) )
} }
#[test]
fn to_string() {
let source = "to_string(42)";
assert_eq!(
parse(source),
Ok(Chunk::with_data(
None,
vec![
(Instruction::load_constant(0, 0, false), Span(10, 12)),
(
Instruction::call_native(1, NativeFunction::ToString, 1),
Span(0, 13)
),
(Instruction::r#return(true), Span(13, 13))
],
vec![Value::integer(42)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(Value::string("42"))))
}