Add the to_string, write and write_line natives
This commit is contained in:
parent
af4e43fc9f
commit
c264aaeb13
@ -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",
|
||||
};
|
||||
|
||||
|
@ -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)),
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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"))))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user