From 382d43ef7700cd4e16c49e3c4623aceb6a677031 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 30 Oct 2024 09:11:28 -0400 Subject: [PATCH] Add NativeFunction macro and implement read_line --- dust-lang/src/instruction.rs | 12 ++- dust-lang/src/lexer.rs | 6 +- dust-lang/src/native_function.rs | 166 ++++++++++++++++++++----------- dust-lang/src/parser.rs | 91 ++++++++--------- dust-lang/src/token.rs | 38 ------- 5 files changed, 158 insertions(+), 155 deletions(-) diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index cf2824b..6c23633 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -581,9 +581,13 @@ impl Instruction { let to_register = self.a(); let native_function = NativeFunction::from(self.b()); let argument_count = self.c(); - let native_function_name = native_function.as_str(); + let mut output = String::new(); - let mut output = format!("R{to_register} = {native_function_name}("); + if native_function.returns_value() { + let native_function_name = native_function.as_str(); + + output.push_str(&format!("R{} = {}(", to_register, native_function_name)); + } if argument_count != 0 { let first_argument = to_register.saturating_sub(argument_count); @@ -597,7 +601,9 @@ impl Instruction { } } - output.push(')'); + if native_function.returns_value() { + output.push(')'); + } output } diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 46135a4..c37a8b9 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -563,15 +563,11 @@ impl<'src> Lexer<'src> { "loop" => Token::Loop, "map" => Token::Map, "mut" => Token::Mut, - "panic" => Token::Panic, "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), }; @@ -1258,7 +1254,7 @@ mod tests { assert_eq!( lex(input), Ok(vec![ - (Token::WriteLine, Span(0, 10)), + (Token::Identifier("write_line"), 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 64c56dd..872e9fe 100644 --- a/dust-lang/src/native_function.rs +++ b/dust-lang/src/native_function.rs @@ -1,6 +1,6 @@ use std::{ fmt::{self, Display, Formatter}, - io::{self, stdout, Write}, + io::{self, stdin, stdout, Write}, }; use serde::{Deserialize, Serialize}; @@ -128,62 +128,101 @@ pub enum NativeFunction { Write = WRITE as isize, } -impl NativeFunction { - pub fn as_str(&self) -> &'static str { - match self { - NativeFunction::Panic => "panic", - NativeFunction::Parse => "parse", - NativeFunction::ToByte => "to_byte", - NativeFunction::ToFloat => "to_float", - NativeFunction::ToInteger => "to_integer", - NativeFunction::ToString => "to_string", - NativeFunction::All => "all", - NativeFunction::Any => "any", - NativeFunction::Append => "append", - NativeFunction::Contains => "contains", - NativeFunction::Find => "find", - NativeFunction::Flatten => "flatten", - NativeFunction::Get => "get", - NativeFunction::IndexOf => "index_of", - NativeFunction::Join => "join", - NativeFunction::Length => "length", - NativeFunction::Map => "map", - NativeFunction::Prepend => "prepend", - NativeFunction::Reduce => "reduce", - NativeFunction::Remove => "remove", - NativeFunction::Reverse => "reverse", - NativeFunction::Set => "set", - NativeFunction::Slice => "slice", - NativeFunction::Sort => "sort", - NativeFunction::Split => "split", - NativeFunction::Unzip => "unzip", - NativeFunction::Zip => "zip", - NativeFunction::CharAt => "char_at", - NativeFunction::CharCodeAt => "char_code_at", - NativeFunction::Chars => "chars", - NativeFunction::EndsWith => "ends_with", - NativeFunction::Format => "format", - NativeFunction::Includes => "includes", - NativeFunction::Match => "match", - NativeFunction::PadEnd => "pad_end", - NativeFunction::PadStart => "pad_start", - NativeFunction::Repeat => "repeat", - NativeFunction::Replace => "replace", - NativeFunction::SplitAt => "split_at", - NativeFunction::SplitLines => "split_lines", - NativeFunction::SplitWhitespace => "split_whitespace", - NativeFunction::StartsWith => "starts_with", - NativeFunction::ToLowerCase => "to_lower_case", - NativeFunction::ToUpperCase => "to_upper_case", - NativeFunction::Trim => "trim", - NativeFunction::TrimEnd => "trim_end", - NativeFunction::TrimStart => "trim_start", - NativeFunction::ReadLine => "read_line", - NativeFunction::WriteLine => "write_line", - NativeFunction::Write => "write", - } - } +macro_rules! impl_from_str_for_native_function { + ($(($name:ident, $str:expr, $returns_value:expr)),*) => { + impl NativeFunction { + pub fn as_str(&self) -> &'static str { + match self { + $( + NativeFunction::$name => $str, + )* + } + } + #[allow(clippy::should_implement_trait)] + pub fn from_str(string: &str) -> Option { + match string { + $( + $str => Some(NativeFunction::$name), + )* + _ => None, + } + } + + pub fn returns_value(&self) -> bool { + match self { + $( + NativeFunction::$name => $returns_value, + )* + } + } + } + }; +} + +// Use the macro to implement From<&str> for NativeFunction +impl_from_str_for_native_function! { + (Panic, "panic", false), + + // Type conversion + (Parse, "parse", true), + (ToByte, "to_byte", true), + (ToFloat, "to_float", true), + (ToInteger, "to_integer", true), + (ToString, "to_string", true), + + // List + (All, "all", true), + (Any, "any", true), + (Append, "append", true), + (Contains, "contains", true), + (Find, "find", true), + (Flatten, "flatten", true), + (Get, "get", true), + (IndexOf, "index_of", true), + (Join, "join", true), + (Length, "length", true), + (Map, "map", true), + (Prepend, "prepend", true), + (Reduce, "reduce", true), + (Remove, "remove", true), + (Reverse, "reverse", true), + (Set, "set", true), + (Slice, "slice", true), + (Sort, "sort", true), + (Split, "split", true), + (Unzip, "unzip", true), + (Zip, "zip", true), + + // String + (CharAt, "char_at", true), + (CharCodeAt, "char_code_at", true), + (Chars, "chars", true), + (EndsWith, "ends_with", true), + (Format, "format", true), + (Includes, "includes", true), + (Match, "match", true), + (PadEnd, "pad_end", true), + (PadStart, "pad_start", true), + (Repeat, "repeat", true), + (Replace, "replace", true), + (SplitAt, "split_at", true), + (SplitLines, "split_lines", true), + (SplitWhitespace, "split_whitespace", true), + (StartsWith, "starts_with", true), + (ToLowerCase, "to_lower_case", true), + (ToUpperCase, "to_upper_case", true), + (Trim, "trim", true), + (TrimEnd, "trim_end", true), + (TrimStart, "trim_start", true), + + // I/O + (ReadLine, "read_line", true), + (WriteLine, "write_line", false), + (Write, "write", false) +} + +impl NativeFunction { pub fn call( &self, instruction: Instruction, @@ -279,7 +318,20 @@ impl NativeFunction { NativeFunction::TrimStart => todo!(), // I/O - NativeFunction::ReadLine => todo!(), + NativeFunction::ReadLine => { + let mut buffer = String::new(); + + stdin() + .read_line(&mut buffer) + .map_err(|io_error| VmError::Io { + error: io_error.kind(), + position, + })?; + + buffer = buffer.trim_end_matches('\n').to_string(); + + Some(Value::Primitive(Primitive::String(buffer))) + } NativeFunction::Write => { let to_register = instruction.a(); let mut stdout = stdout(); diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index fabfa78..21a2bb8 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -749,8 +749,6 @@ impl<'src> Parser<'src> { let token = self.current_token; let start_position = self.current_position; - self.advance()?; - let local_index = if let Token::Identifier(text) = token { if let Ok(local_index) = self.chunk.get_local_index(text, start_position) { local_index @@ -771,17 +769,25 @@ impl<'src> Parser<'src> { self.current_is_expression = true; return Ok(()); + } + + return if NativeFunction::from_str(text).is_some() { + self.parse_native_call(allowed) } else { - return Err(ParseError::UndeclaredVariable { + Err(ParseError::UndeclaredVariable { identifier: Identifier::new(text), position: start_position, - }); - } + }) + }; } else { - return Err(ParseError::UndeclaredVariable { - identifier: Identifier::new(text), - position: start_position, - }); + return if NativeFunction::from_str(text).is_some() { + self.parse_native_call(allowed) + } else { + Err(ParseError::UndeclaredVariable { + identifier: Identifier::new(text), + position: start_position, + }) + }; } } else { return Err(ParseError::ExpectedToken { @@ -790,6 +796,9 @@ impl<'src> Parser<'src> { position: start_position, }); }; + + self.advance()?; + let is_mutable = self .chunk .get_local(local_index, start_position)? @@ -1059,45 +1068,43 @@ impl<'src> Parser<'src> { } 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 native_function = if let Token::Identifier(text) = self.current_token { + NativeFunction::from_str(text).unwrap() + } else { + return Err(ParseError::ExpectedToken { + expected: TokenKind::Identifier, + found: self.current_token.to_owned(), + position: self.current_position, + }); }; - let is_expression = self.current_token.is_expression(); - let start = self.current_position.0; let start_register = self.next_register(); self.advance()?; - if self.allow(Token::LeftParenthesis)? { - while !self.allow(Token::RightParenthesis)? { - let expected_register = self.next_register(); + self.expect(Token::LeftParenthesis)?; - self.parse_expression()?; + while !self.allow(Token::RightParenthesis)? { + let expected_register = self.next_register(); - let actual_register = self.next_register() - 1; + self.parse_expression()?; - if expected_register < actual_register { - self.emit_instruction( - Instruction::close(expected_register, actual_register), - self.current_position, - ); - } + let actual_register = self.next_register() - 1; - self.allow(Token::Comma)?; + if expected_register < actual_register { + self.emit_instruction( + Instruction::close(expected_register, actual_register), + self.current_position, + ); } + + self.allow(Token::Comma)?; } 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.current_is_expression = native_function.returns_value(); self.emit_instruction( Instruction::call_native(to_register, native_function, argument_count), @@ -1738,11 +1745,6 @@ impl From<&Token<'_>> for ParseRule<'_> { infix: None, precedence: Precedence::None, }, - Token::Panic => ParseRule { - prefix: Some(Parser::parse_native_call), - infix: None, - precedence: Precedence::Call, - }, Token::Percent => ParseRule { prefix: None, infix: Some(Parser::parse_math_binary), @@ -1819,26 +1821,11 @@ 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 d3abb5d..cb7bf9d 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -47,15 +47,10 @@ define_tokens! { Loop, Map, Mut, - Panic, Return, Str, Struct, - ToString, While, - Write, - - WriteLine, // Symbols ArrowThin, @@ -119,10 +114,7 @@ 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, @@ -142,7 +134,6 @@ impl<'src> Token<'src> { Token::LessEqual => 2, Token::Minus => 1, Token::MinusEqual => 2, - Token::Panic => 5, Token::Percent => 1, Token::PercentEqual => 2, Token::Plus => 1, @@ -184,7 +175,6 @@ impl<'src> Token<'src> { Token::Mut => "mut", Token::Str => "str", Token::Struct => "struct", - Token::ToString => "to_string", Token::While => "while", Token::BangEqual => "!=", Token::Bang => "!", @@ -205,7 +195,6 @@ impl<'src> Token<'src> { Token::LessEqual => "<=", Token::Minus => "-", Token::MinusEqual => "-=", - Token::Panic => "panic", Token::Percent => "%", Token::PercentEqual => "%=", Token::Plus => "+", @@ -219,8 +208,6 @@ impl<'src> Token<'src> { Token::SlashEqual => "/=", Token::Star => "*", Token::StarEqual => "*=", - Token::Write => "write", - Token::WriteLine => "write_line", } } @@ -265,7 +252,6 @@ impl<'src> Token<'src> { Token::Minus => TokenOwned::Minus, Token::MinusEqual => TokenOwned::MinusEqual, Token::Mut => TokenOwned::Mut, - Token::Panic => TokenOwned::Panic, Token::Percent => TokenOwned::Percent, Token::PercentEqual => TokenOwned::PercentEqual, Token::Plus => TokenOwned::Plus, @@ -282,10 +268,7 @@ 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, } } @@ -330,7 +313,6 @@ impl<'src> Token<'src> { Token::Minus => TokenKind::Minus, Token::MinusEqual => TokenKind::MinusEqual, Token::Mut => TokenKind::Mut, - Token::Panic => TokenKind::Panic, Token::Percent => TokenKind::Percent, Token::PercentEqual => TokenKind::PercentEqual, Token::Plus => TokenKind::Plus, @@ -346,11 +328,8 @@ 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, } } @@ -393,7 +372,6 @@ impl<'src> Token<'src> { | Token::SlashEqual | Token::Star | Token::StarEqual - | Token::ToString ) } } @@ -440,7 +418,6 @@ impl<'src> Display for Token<'src> { Token::Minus => write!(f, "-"), Token::MinusEqual => write!(f, "-="), Token::Mut => write!(f, "mut"), - Token::Panic => write!(f, "panic"), Token::Percent => write!(f, "%"), Token::PercentEqual => write!(f, "%="), Token::Plus => write!(f, "+"), @@ -456,11 +433,8 @@ 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"), } } } @@ -495,13 +469,9 @@ pub enum TokenOwned { Loop, Map, Mut, - Panic, Return, Str, - ToString, While, - Write, - WriteLine, // Symbols ArrowThin, @@ -581,7 +551,6 @@ impl Display for TokenOwned { TokenOwned::Minus => Token::Minus.fmt(f), TokenOwned::MinusEqual => Token::MinusEqual.fmt(f), TokenOwned::Mut => Token::Mut.fmt(f), - TokenOwned::Panic => Token::Panic.fmt(f), TokenOwned::Percent => Token::Percent.fmt(f), TokenOwned::PercentEqual => Token::PercentEqual.fmt(f), TokenOwned::Plus => Token::Plus.fmt(f), @@ -598,10 +567,7 @@ 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), } } } @@ -648,7 +614,6 @@ impl Display for TokenKind { TokenKind::Minus => Token::Minus.fmt(f), TokenKind::MinusEqual => Token::MinusEqual.fmt(f), TokenKind::Mut => Token::Mut.fmt(f), - TokenKind::Panic => Token::Panic.fmt(f), TokenKind::Percent => Token::Percent.fmt(f), TokenKind::PercentEqual => Token::PercentEqual.fmt(f), TokenKind::Plus => Token::Plus.fmt(f), @@ -665,10 +630,7 @@ 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), } } }