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 {
NativeFunction::Panic => "PANIC",
NativeFunction::ToString => "TO_STRING",
NativeFunction::Write => "WRITE",
NativeFunction::WriteLine => "WRITE_LINE",
};

View File

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

View File

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

View File

@ -1058,7 +1058,18 @@ impl<'src> Parser<'src> {
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_register = self.next_register();
@ -1086,9 +1097,10 @@ impl<'src> Parser<'src> {
let end = self.current_position.1;
let to_register = self.next_register();
let argument_count = to_register - start_register;
self.current_is_expression = is_expression;
self.emit_instruction(
Instruction::call_native(to_register, NativeFunction::Panic, argument_count),
Instruction::call_native(to_register, native_function, argument_count),
Span(start, end),
);
Ok(())
@ -1727,7 +1739,7 @@ impl From<&Token<'_>> for ParseRule<'_> {
precedence: Precedence::None,
},
Token::Panic => ParseRule {
prefix: Some(Parser::parse_panic),
prefix: Some(Parser::parse_native_call),
infix: None,
precedence: Precedence::Call,
},
@ -1807,11 +1819,26 @@ impl From<&Token<'_>> for ParseRule<'_> {
precedence: Precedence::None,
},
Token::Struct => todo!(),
Token::ToString => ParseRule {
prefix: Some(Parser::parse_native_call),
infix: None,
precedence: Precedence::Call,
},
Token::While => ParseRule {
prefix: Some(Parser::parse_while),
infix: 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};
/// Source code token.
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum Token<'src> {
// End of file
#[default]
Eof,
macro_rules! define_tokens {
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum Token<'src> {
#[default]
Eof,
$(
$variant $(($data_type))?,
)*
}
#[derive(Debug, PartialEq, Clone)]
pub enum TokenKind {
Eof,
$(
$variant,
)*
}
};
}
define_tokens! {
// Hard-coded values
Boolean(&'src str),
Byte(&'src str),
@ -36,7 +51,11 @@ pub enum Token<'src> {
Return,
Str,
Struct,
ToString,
While,
Write,
WriteLine,
// Symbols
ArrowThin,
@ -100,7 +119,10 @@ impl<'src> Token<'src> {
Token::Mut => 3,
Token::Str => 3,
Token::Struct => 6,
Token::ToString => 8,
Token::While => 5,
Token::Write => 5,
Token::WriteLine => 10,
Token::BangEqual => 2,
Token::Bang => 1,
Token::Colon => 1,
@ -162,6 +184,7 @@ impl<'src> Token<'src> {
Token::Mut => "mut",
Token::Str => "str",
Token::Struct => "struct",
Token::ToString => "to_string",
Token::While => "while",
Token::BangEqual => "!=",
Token::Bang => "!",
@ -196,6 +219,8 @@ impl<'src> Token<'src> {
Token::SlashEqual => "/=",
Token::Star => "*",
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::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::ToString => TokenOwned::ToString,
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::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::ToString => TokenKind::ToString,
Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While,
Token::Write => TokenKind::Write,
Token::WriteLine => TokenKind::WriteLine,
}
}
@ -356,13 +387,13 @@ impl<'src> Token<'src> {
| Token::MinusEqual
| Token::Percent
| Token::PercentEqual
| Token::Panic
| Token::Plus
| Token::PlusEqual
| Token::Slash
| Token::SlashEqual
| Token::Star
| Token::StarEqual
| Token::ToString
)
}
}
@ -425,8 +456,11 @@ impl<'src> Display for Token<'src> {
Token::StarEqual => write!(f, "*="),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "{value}"),
Token::ToString => write!(f, "to_string"),
Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"),
Token::Write => write!(f, "write"),
Token::WriteLine => write!(f, "write_line"),
}
}
}
@ -464,7 +498,10 @@ pub enum TokenOwned {
Panic,
Return,
Str,
ToString,
While,
Write,
WriteLine,
// Symbols
ArrowThin,
@ -561,80 +598,14 @@ impl Display for TokenOwned {
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::ToString => Token::ToString.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 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
@ -694,7 +665,10 @@ impl Display for TokenKind {
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::ToString => Token::ToString.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::{
parse, value::Primitive, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
@ -423,7 +427,56 @@ impl Vm {
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 {
@ -695,6 +748,10 @@ pub enum VmError {
// Wrappers for foreign errors
Chunk(ChunkError),
Io {
error: io::ErrorKind,
position: Span,
},
Value {
error: ValueError,
position: Span,
@ -725,6 +782,7 @@ impl AnnotatedError for VmError {
Self::StackUnderflow { .. } => "Stack underflow",
Self::UndefinedVariable { .. } => "Undefined variable",
Self::Chunk(error) => error.description(),
Self::Io { .. } => "I/O error",
Self::Value { .. } => "Value error",
}
}
@ -741,6 +799,7 @@ impl AnnotatedError for VmError {
Some(format!("{identifier} is not in scope"))
}
Self::Chunk(error) => error.details(),
Self::Io { error, .. } => Some(error.to_string()),
Self::Value { error, .. } => Some(error.to_string()),
_ => None,
}
@ -759,6 +818,7 @@ impl AnnotatedError for VmError {
Self::StackOverflow { position } => *position,
Self::UndefinedVariable { position, .. } => *position,
Self::Chunk(error) => error.position(),
Self::Io { position, .. } => *position,
Self::Value { position, .. } => *position,
}
}

View File

@ -15,7 +15,6 @@ fn panic() {
Instruction::call_native(2, NativeFunction::Panic, 2),
Span(0, 27)
),
(Instruction::r#return(true), Span(27, 27))
],
vec![Value::string("Goodbye world!"), Value::integer(42)],
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"))))
}