Add very minimal +/- binary op parsing
This commit is contained in:
parent
dec0a0e154
commit
ffc0e812a2
5 changed files with 159 additions and 10 deletions
|
@ -1,6 +1,9 @@
|
||||||
|
mod ast;
|
||||||
|
mod parser;
|
||||||
mod token;
|
mod token;
|
||||||
mod tokenizer;
|
mod tokenizer;
|
||||||
|
|
||||||
pub fn compile(code: &str) {
|
pub fn compile(code: &str) {
|
||||||
tokenizer::tokenize(code);
|
let tokens = tokenizer::tokenize(code);
|
||||||
|
parser::parse(&tokens);
|
||||||
}
|
}
|
||||||
|
|
12
src/compiler/ast.rs
Normal file
12
src/compiler/ast.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#[expect(dead_code)]
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Expression<'source> {
|
||||||
|
IntLiteral(u32),
|
||||||
|
BoolLiteral(bool),
|
||||||
|
Indentifier(String),
|
||||||
|
BinaryOp(
|
||||||
|
Box<Expression<'source>>,
|
||||||
|
&'source str,
|
||||||
|
Box<Expression<'source>>,
|
||||||
|
),
|
||||||
|
}
|
123
src/compiler/parser.rs
Normal file
123
src/compiler/parser.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
use crate::compiler::{
|
||||||
|
ast::Expression::{self, *},
|
||||||
|
token::{Token, TokenType},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse<'source>(tokens: &[Token<'source>]) -> Expression<'source> {
|
||||||
|
let mut pos = 0;
|
||||||
|
|
||||||
|
parse_expression(&mut pos, tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek<'source>(pos: &mut usize, tokens: &[Token<'source>]) -> Token<'source> {
|
||||||
|
if let Some(token) = tokens.get(*pos) {
|
||||||
|
token.clone()
|
||||||
|
} else if let Some(last_token) = tokens.get(*pos - 1) {
|
||||||
|
Token::new("", TokenType::End, last_token.loc)
|
||||||
|
} else {
|
||||||
|
panic!("Input to parser appears to be empty!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next<'source>(pos: &mut usize, tokens: &[Token<'source>]) -> Token<'source> {
|
||||||
|
let token = peek(pos, tokens);
|
||||||
|
*pos += 1;
|
||||||
|
token
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_expect_types<'source>(
|
||||||
|
pos: &mut usize,
|
||||||
|
tokens: &[Token<'source>],
|
||||||
|
types: &Vec<TokenType>,
|
||||||
|
) -> Token<'source> {
|
||||||
|
let token = next(pos, tokens);
|
||||||
|
|
||||||
|
if types.contains(&token.token_type) {
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Parsing error: expected one of {:?} but found {}",
|
||||||
|
types, token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_expect_strings<'source>(
|
||||||
|
pos: &mut usize,
|
||||||
|
tokens: &[Token<'source>],
|
||||||
|
strings: &Vec<&str>,
|
||||||
|
) -> Token<'source> {
|
||||||
|
let token = next(pos, tokens);
|
||||||
|
|
||||||
|
if strings.contains(&token.text) {
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Parsing error: expected one of {:?} but found {}",
|
||||||
|
strings, token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_expect_type<'source>(
|
||||||
|
pos: &mut usize,
|
||||||
|
tokens: &[Token<'source>],
|
||||||
|
expected_type: TokenType,
|
||||||
|
) -> Token<'source> {
|
||||||
|
next_expect_types(pos, tokens, &vec![expected_type])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_int_literal<'source>(pos: &mut usize, tokens: &[Token]) -> Expression<'source> {
|
||||||
|
let token = next_expect_type(pos, tokens, TokenType::Integer);
|
||||||
|
|
||||||
|
IntLiteral(
|
||||||
|
token
|
||||||
|
.text
|
||||||
|
.parse::<u32>()
|
||||||
|
.unwrap_or_else(|_| panic!("Fatal parser error! Invalid value in token {token}")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expression<'source>(pos: &mut usize, tokens: &[Token<'source>]) -> Expression<'source> {
|
||||||
|
let left = parse_int_literal(pos, tokens);
|
||||||
|
let operator_token = next_expect_strings(pos, tokens, &vec!["+", "-"]);
|
||||||
|
let right = parse_int_literal(pos, tokens);
|
||||||
|
Expression::BinaryOp(Box::new(left), operator_token.text, Box::new(right))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::compiler::token::CodeLocation;
|
||||||
|
|
||||||
|
fn new_int(text: &str) -> Token {
|
||||||
|
Token::new(
|
||||||
|
text,
|
||||||
|
TokenType::Integer,
|
||||||
|
CodeLocation::new(usize::MAX, usize::MAX),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_id(text: &str) -> Token {
|
||||||
|
Token::new(
|
||||||
|
text,
|
||||||
|
TokenType::Identifier,
|
||||||
|
CodeLocation::new(usize::MAX, usize::MAX),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binary_op_basic() {
|
||||||
|
let result = parse(&vec![new_int("1"), new_id("+"), new_int("23")]);
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
BinaryOp(Box::new(IntLiteral(1)), "+", Box::new(IntLiteral(23)))
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = parse(&vec![new_int("4"), new_id("-"), new_int("56")]);
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
BinaryOp(Box::new(IntLiteral(4)), "-", Box::new(IntLiteral(56)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct CodeLocation {
|
pub struct CodeLocation {
|
||||||
line: usize,
|
line: usize,
|
||||||
|
@ -10,6 +12,12 @@ impl CodeLocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CodeLocation {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.line, self.char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for CodeLocation {
|
impl PartialEq for CodeLocation {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let true_match = self.line == other.line && self.char == other.char;
|
let true_match = self.line == other.line && self.char == other.char;
|
||||||
|
@ -32,23 +40,21 @@ pub enum TokenType {
|
||||||
Operator,
|
Operator,
|
||||||
Punctuation,
|
Punctuation,
|
||||||
Whitespace,
|
Whitespace,
|
||||||
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenType {
|
impl TokenType {
|
||||||
pub fn ignore(&self) -> bool {
|
pub fn ignore(&self) -> bool {
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
match self {
|
matches!(self, Whitespace | Comment)
|
||||||
Whitespace | Comment => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Token<'source> {
|
pub struct Token<'source> {
|
||||||
text: &'source str,
|
pub text: &'source str,
|
||||||
token_type: TokenType,
|
pub token_type: TokenType,
|
||||||
loc: CodeLocation,
|
pub loc: CodeLocation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'source> Token<'source> {
|
impl<'source> Token<'source> {
|
||||||
|
@ -60,3 +66,9 @@ impl<'source> Token<'source> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'source> fmt::Display for Token<'source> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:?} ``{}`` at {}", self.token_type, self.text, self.loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::compiler;
|
use crate::compiler;
|
||||||
use json;
|
|
||||||
use std::{
|
use std::{
|
||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
net::{IpAddr, TcpListener, TcpStream},
|
net::{IpAddr, TcpListener, TcpStream},
|
||||||
|
|
Reference in a new issue