diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index bd3d7b9..176e752 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -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", }; diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 403fd9f..46135a4 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -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)), diff --git a/dust-lang/src/native_function.rs b/dust-lang/src/native_function.rs index ce48b86..3e5d3fe 100644 --- a/dust-lang/src/native_function.rs +++ b/dust-lang/src/native_function.rs @@ -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 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 for u8 { match native_function { NativeFunction::Panic => PANIC, NativeFunction::ToString => TO_STRING, + NativeFunction::Write => WRITE, NativeFunction::WriteLine => WRITE_LINE, } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 60988b6..fabfa78 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -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, + }, } } } diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 8737536..d3abb5d 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -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), } } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index d49fff2..cb8162c 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -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, } } diff --git a/dust-lang/tests/native_functions.rs b/dust-lang/tests/native_functions.rs index e3f2a9e..307477f 100644 --- a/dust-lang/tests/native_functions.rs +++ b/dust-lang/tests/native_functions.rs @@ -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")))) +}