1
0

Add NativeFunction macro and implement read_line

This commit is contained in:
Jeff 2024-10-30 09:11:28 -04:00
parent 4d6412006a
commit 382d43ef77
5 changed files with 158 additions and 155 deletions

View File

@ -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
}

View File

@ -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)),

View File

@ -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<Self> {
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();

View File

@ -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,
},
}
}
}

View File

@ -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),
}
}
}