commit e57e319dec84f19926da2c36062d2c0718a6321d Author: Seoxi Ryouko Date: Mon Jul 6 11:34:44 2026 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..59b2d33 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "tea" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..771cf1c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tea" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/lex.rs b/src/lex.rs new file mode 100644 index 0000000..81a6c17 --- /dev/null +++ b/src/lex.rs @@ -0,0 +1,77 @@ + +pub mod literal; +use self::literal::parse_literal; +pub mod identifier; +use self::identifier::parse_identifier; +pub mod operator; +use self::operator::{ parse_operator, Operator }; +pub mod symbol; +use self::symbol::{ parse_symbol, Symbol }; +pub mod keyword; +use self::keyword::{ parse_keyword, Keyword }; + +#[derive(Debug)] +pub enum TeaValue { + Int(i32), + Uint(u32), + Float(f32), + String(String), +} + +#[derive(Debug)] +pub enum Token { + Lit(TeaValue), + Id(String), + Kw(Keyword), + Sym(Symbol), + Op(Operator), +} + +/*#[derive(Debug)] +pub struct LexError (String);*/ + +macro_rules! whitespace_chars { + () => { + ' ' | '\t' | '\r' | '\n' + } +} +pub(crate) use whitespace_chars; + +fn skip_whitespace(input: &str) -> &str { + let whitespace: String = input.chars().take_while(|c| { + matches!(c, whitespace_chars!()) + }).collect(); + &input[whitespace.len()..] +} + +macro_rules! try_parse { + ($e:expr, $arr:ident, $str:ident) => { + if let Some((v, s)) = $e { + $str = s; + $arr.push(v); + continue; + } + } +} + +pub fn parse_tokens(s: &str) -> Result, (Vec, &str)> { + let mut input = s; + let mut tokens: Vec = vec![]; + loop { + if input.is_empty() { + return Ok(tokens); + } + try_parse!(parse_literal(input), tokens, input); + try_parse!(parse_operator(input), tokens, input); + try_parse!(parse_symbol(input), tokens, input); + try_parse!(parse_keyword(input), tokens, input); + try_parse!(parse_identifier(input), tokens, input); + let post_whitespace = skip_whitespace(input); + if post_whitespace != input { + input = post_whitespace; + continue; + } + return Err((tokens, "invalid input")); + } +} + diff --git a/src/lex/identifier.rs b/src/lex/identifier.rs new file mode 100644 index 0000000..8b06dac --- /dev/null +++ b/src/lex/identifier.rs @@ -0,0 +1,25 @@ +use super::{ Token }; + +macro_rules! ident_initial_chars { + () => { + 'a' ..= 'z' | 'A' ..= 'Z' + } +} + +macro_rules! ident_chars { + () => { + ident_initial_chars!() | '0' ..= '9' | '$' | '_' + } +} + +pub fn parse_identifier(input: &str) -> Option<(Token, &str)> { + let mut chars = input.chars(); + let first_char: char = match chars.next() { + a @ Some(ident_initial_chars!()) => a.unwrap(), + _ => return None, + }; + let rest: String = chars.take_while(|&c| matches!(c, ident_chars!())).collect(); + let full = format!("{}{}", first_char, rest); + let len = full.len(); + Some((Token::Id(full), &input[len..])) +} diff --git a/src/lex/keyword.rs b/src/lex/keyword.rs new file mode 100644 index 0000000..bec0445 --- /dev/null +++ b/src/lex/keyword.rs @@ -0,0 +1,19 @@ +use super::{ whitespace_chars, Token }; + +#[derive(Debug)] +pub enum Keyword { + Let, +} + +macro_rules! match_keyword { + ($str:literal, $kw:expr, $val:ident) => { + if $val.starts_with($str) && matches!($val.chars().nth($str.len()), None | Some(whitespace_chars!())) { + return Some(($kw, &$val[$str.len()..])); + } + } +} + +pub fn parse_keyword(input: &str) -> Option<(Token, &str)> { + match_keyword!("let", Token::Kw(Keyword::Let), input); + None +} diff --git a/src/lex/literal.rs b/src/lex/literal.rs new file mode 100644 index 0000000..84b5082 --- /dev/null +++ b/src/lex/literal.rs @@ -0,0 +1,101 @@ + +use super::{ Token, TeaValue }; + +macro_rules! dec_chars { + () => { + '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' + } +} + +macro_rules! hex_chars { + () => { + dec_chars!() | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' + } +} +fn parse_float(input: &str) -> Option<(f32, &str)> { + let (input, sign) = if input.starts_with("-") { + (input.strip_prefix("-").unwrap(), -1.0) + } else { + (input, 1.0) + }; + let mut dec = false; + let mut e = false; + let s: String = input.chars().take_while(|&c| { + if matches!(c, dec_chars!()) { + return true; + } + match (dec, e) { + (true, true) => false, + (false, true) => { + if c == '.' { + dec = true; + return true; + } + false + }, + (true, false) => { + if c == 'e' { + e = true; + return true; + } + false + }, + (false, false) => { + if c == '.' { + dec = true; + return true; + } + if c == 'e' { + e = true; + return true; + } + false + }, + } + }).collect(); + if s.is_empty() || !(dec || e) { + return None; + } + Some((sign * s.parse::().unwrap(), &input[s.len()..])) +} + +fn parse_hex_int(input: &str) -> Option<(i32, &str)> { + let (input, sign) = if input.starts_with("-") { + (input.strip_prefix("-").unwrap(), -1) + } else { + (input, 1) + }; + let input = input.strip_prefix("0x")?; + let s: String = input.chars().take_while(|c| matches!(c, hex_chars!())).collect(); + if s.is_empty() { + return None; + } + Some((sign * i32::from_str_radix(&s, 16).unwrap(), &input[s.len()..])) +} + +fn parse_dec_int(input: &str) -> Option<(i32, &str)> { + let (input, sign) = if input.starts_with("-") { + (input.strip_prefix("-").unwrap(), -1) + } else { + (input, 1) + }; + let s: String = input.chars().take_while(|c| matches!(c, dec_chars!())).collect(); + if s.is_empty() { + return None; + } + Some((sign * s.parse::().unwrap(), &input[s.len()..])) +} + +pub fn parse_literal(input: &str) -> Option<(Token, &str)> { + if let Some((val, s)) = parse_float(input) { + return Some((Token::Lit(TeaValue::Float(val)), s)) + } + if let Some((val, s)) = parse_hex_int(input) { + return Some((Token::Lit(TeaValue::Int(val)), s)) + } + if let Some((val, s)) = parse_dec_int(input) { + return Some((Token::Lit(TeaValue::Int(val)), s)) + } + // TODO: other values + None +} diff --git a/src/lex/operator.rs b/src/lex/operator.rs new file mode 100644 index 0000000..3e4a3f7 --- /dev/null +++ b/src/lex/operator.rs @@ -0,0 +1,21 @@ +use super::Token; + +#[derive(Debug)] +pub enum Operator { + Plus, + Minus, + Star, // multiplication + Slash, // division +} + +pub fn parse_operator(input: &str) -> Option<(Token, &str)> { + let c: char = input.chars().next()?; + let input = &input[1..]; + match c { + '+' => Some((Token::Op(Operator::Plus), input)), + '-' => Some((Token::Op(Operator::Minus), input)), + '*' => Some((Token::Op(Operator::Star), input)), + '/' => Some((Token::Op(Operator::Slash), input)), + _ => None + } +} diff --git a/src/lex/symbol.rs b/src/lex/symbol.rs new file mode 100644 index 0000000..fa0fadf --- /dev/null +++ b/src/lex/symbol.rs @@ -0,0 +1,17 @@ +use super::Token; + +#[derive(Debug)] +pub enum Symbol { + EqualSign, + Semicolon, +} + +pub fn parse_symbol(input: &str) -> Option<(Token, &str)> { + let c: char = input.chars().next()?; + let input = &input[1..]; + match c { + '=' => Some((Token::Sym(Symbol::EqualSign), input)), + ';' => Some((Token::Sym(Symbol::Semicolon), input)), + _ => None + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..237ad08 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,12 @@ + +use std::env::args; + +#[macro_use] +mod lex; +use lex::parse_tokens; + +fn main() { + let args: String = args().skip(1).collect::>().join(" "); + let k = parse_tokens(&args); + println!("{k:?}"); +}