wip
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /target | ||||
							
								
								
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 4 | ||||
|  | ||||
| [[package]] | ||||
| name = "anyhow" | ||||
| version = "1.0.98" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" | ||||
|  | ||||
| [[package]] | ||||
| name = "ts-parser" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
| ] | ||||
							
								
								
									
										7
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| [package] | ||||
| name = "ts-parser" | ||||
| version = "0.1.0" | ||||
| edition = "2024" | ||||
|  | ||||
| [dependencies] | ||||
| anyhow = "1.0.98" | ||||
							
								
								
									
										23
									
								
								src/ast.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/ast.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| use crate::token::Token; | ||||
|  | ||||
| pub struct Module { | ||||
|     pub statements: Vec<Statement>, | ||||
| } | ||||
|  | ||||
| pub enum Statement { | ||||
|     FunctionDeclaration { | ||||
|         name: Token, | ||||
|         parameters: Vec<ParameterDeclaration>, | ||||
|     }, | ||||
|     Expression, | ||||
| } | ||||
|  | ||||
| pub struct ParameterDeclaration { | ||||
|     name: Token, | ||||
|     typename: Token, | ||||
| } | ||||
|  | ||||
| pub enum Expression { | ||||
|     Identifier(Token), | ||||
|     FunctionCall {}, | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/format.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/format.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| use crate::token::{CommentKind, KeywordKind, LiteralKind, Number, Token, TokenKind}; | ||||
|  | ||||
| pub trait Formatter { | ||||
|     fn format(self, options: FormatterOptions) -> anyhow::Result<String>; | ||||
| } | ||||
| pub struct FormatterOptions {} | ||||
|  | ||||
| impl Formatter for &[Token] { | ||||
|     fn format(self, options: FormatterOptions) -> anyhow::Result<String> { | ||||
|         let mut result = String::new(); | ||||
|         for t in self { | ||||
|             let kind = &t.kind; | ||||
|             let s = match kind { | ||||
|                 TokenKind::Identifier(i) => i.clone(), | ||||
|                 TokenKind::Literal(kind) => match kind { | ||||
|                     LiteralKind::String(s) => { | ||||
|                         format!("\"{s}\"") | ||||
|                     } | ||||
|                     LiteralKind::Number(number) => match number { | ||||
|                         Number::Integer(i) => format!("{i}"), | ||||
|                         Number::Float(f) => format!("{f}"), | ||||
|                     }, | ||||
|                 }, | ||||
|                 TokenKind::Keyword(kind) => format!( | ||||
|                     "{}", | ||||
|                     match kind { | ||||
|                         KeywordKind::function => "function ", | ||||
|                         KeywordKind::string => "string", | ||||
|                         KeywordKind::number => "number", | ||||
|                     } | ||||
|                 ), | ||||
|                 TokenKind::Comment(kind, s) => match kind { | ||||
|                     CommentKind::Line => format!("// {s}"), | ||||
|                     CommentKind::Block => format!("/* {s} */"), | ||||
|                 }, | ||||
|                 TokenKind::LeftParen => "(".to_string(), | ||||
|                 TokenKind::RightParen => ")".to_string(), | ||||
|                 TokenKind::LeftCurly => " {".to_string(), | ||||
|                 TokenKind::RightCurly => "}".to_string(), | ||||
|                 TokenKind::Comma => ", ".to_string(), | ||||
|                 TokenKind::Colon => ": ".to_string(), | ||||
|                 TokenKind::Semicolon => ";".to_string(), | ||||
|                 TokenKind::Period => ".".to_string(), | ||||
|                 TokenKind::NewLine => "\n".to_string(), | ||||
|                 TokenKind::EndOfFile => "".to_string(), | ||||
|             }; | ||||
|             result += &s; | ||||
|         } | ||||
|         Ok(result) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										198
									
								
								src/lexer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/lexer.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| use anyhow::bail; | ||||
|  | ||||
| use crate::token::{CommentKind, KeywordKind, LiteralKind, Token, TokenKind, TokenLocation}; | ||||
|  | ||||
| pub struct Lexer { | ||||
|     file: Option<String>, | ||||
|     source: Vec<char>, | ||||
|     tokens: Vec<Token>, | ||||
|     line: usize, | ||||
|     current_line_offset: usize, | ||||
|     start: usize, | ||||
|     current: usize, | ||||
| } | ||||
|  | ||||
| impl Lexer { | ||||
|     pub fn new(source: &str, file: Option<String>) -> Lexer { | ||||
|         Lexer { | ||||
|             source: source.chars().collect::<Vec<_>>(), | ||||
|             tokens: Vec::new(), | ||||
|             line: 1, | ||||
|             start: 0, | ||||
|             current: 0, | ||||
|             file, | ||||
|             current_line_offset: 0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn lex(mut self) -> Vec<Token> { | ||||
|         while self.current < self.source.len() { | ||||
|             self.start = self.current; | ||||
|             self.next_token(); | ||||
|         } | ||||
|         self.clean_newlines(); | ||||
|         self.tokens | ||||
|     } | ||||
|  | ||||
|     fn next_token(&mut self) { | ||||
|         let c = self.consume(); | ||||
|  | ||||
|         let t = match c { | ||||
|             '(' => Some(TokenKind::LeftParen), | ||||
|             ')' => Some(TokenKind::RightParen), | ||||
|             '{' => Some(TokenKind::LeftCurly), | ||||
|             '}' => Some(TokenKind::RightCurly), | ||||
|             ',' => Some(TokenKind::Comma), | ||||
|             ':' => Some(TokenKind::Colon), | ||||
|             ';' => Some(TokenKind::Semicolon), | ||||
|             '.' => Some(TokenKind::Period), | ||||
|             '\n' => { | ||||
|                 self.line += 1; | ||||
|                 self.current_line_offset = self.current; | ||||
|                 Some(TokenKind::NewLine) | ||||
|             } | ||||
|             _ => None, | ||||
|         }; | ||||
|  | ||||
|         if let Some(t) = t { | ||||
|             self.push(t); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if c == '/' { | ||||
|             let p = self.peek(); | ||||
|             let t = match p { | ||||
|                 '/' => { | ||||
|                     while !self.is_eof() { | ||||
|                         let c = self.consume(); | ||||
|                         if c == '\n' { | ||||
|                             self.line += 1; | ||||
|                             self.current_line_offset = self.current; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                     let s = self.current_scan(2, 0); | ||||
|                     TokenKind::Comment(CommentKind::Line, s) | ||||
|                 } | ||||
|                 '*' => { | ||||
|                     while !self.is_eof() { | ||||
|                         if self.peek_match("*/") { | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                     if self.is_eof() { | ||||
|                         todo!("Expected */ before EOF"); | ||||
|                     } | ||||
|                     self.current += 2; | ||||
|                     let s = self.current_scan(2, 2); | ||||
|                     TokenKind::Comment(CommentKind::Block, s) | ||||
|                 } | ||||
|                 _ => todo!("forward slash"), | ||||
|             }; | ||||
|             self.push(t); | ||||
|         } | ||||
|  | ||||
|         if c == '"' { | ||||
|             while !self.is_eof() { | ||||
|                 let c = self.consume(); | ||||
|                 match c { | ||||
|                     '"' => break, | ||||
|                     '\n' => todo!("Expected closing string before new line"), | ||||
|                     _ => (), | ||||
|                 } | ||||
|             } | ||||
|             if self.is_eof() { | ||||
|                 todo!("Expected closing string before EOL") | ||||
|             } | ||||
|             let s = self.current_scan(1, 1); | ||||
|             self.push(TokenKind::Literal(LiteralKind::String(s))); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if c.is_ascii_alphabetic() { | ||||
|             while !self.is_eof() { | ||||
|                 let p = self.peek(); | ||||
|                 if p.is_alphanumeric() || p == '_' { | ||||
|                     self.consume(); | ||||
|                 } else { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if self.is_eof() { | ||||
|                 todo!("Not sure if handling is necessary") | ||||
|             } | ||||
|             let s = self.current_scan(0, 0); | ||||
|             if let Ok(k) = TryInto::<KeywordKind>::try_into(s.as_str()) { | ||||
|                 self.push(TokenKind::Keyword(k)); | ||||
|                 return; | ||||
|             } | ||||
|             self.push(TokenKind::Identifier(s)); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn clean_newlines(&mut self) { | ||||
|         while let Some(TokenKind::NewLine) = self.tokens.first().map(|t| &t.kind) { | ||||
|             self.tokens.remove(0); | ||||
|         } | ||||
|  | ||||
|         let mut i = 0; | ||||
|         loop { | ||||
|             let w = self | ||||
|                 .tokens | ||||
|                 .get(i..(i + 3)) | ||||
|                 .map(|ts| ts.iter().map(|t| &t.kind).collect::<Vec<_>>()); | ||||
|             match w.as_deref() { | ||||
|                 Some([TokenKind::NewLine, TokenKind::NewLine, TokenKind::NewLine]) => { | ||||
|                     self.tokens.remove(i + 2); | ||||
|                 } | ||||
|                 Some(_) => { | ||||
|                     i += 1; | ||||
|                 } | ||||
|                 None => break, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn current_scan(&self, start_offset: usize, end_offset: usize) -> String { | ||||
|         self.source[(self.start + start_offset)..(self.current - end_offset)] | ||||
|             .iter() | ||||
|             .collect::<String>() | ||||
|     } | ||||
|  | ||||
|     fn push(&mut self, kind: TokenKind) { | ||||
|         self.tokens.push(Token { | ||||
|             kind, | ||||
|             location: TokenLocation { | ||||
|                 file: self.file.clone(), | ||||
|                 line: self.line, | ||||
|                 column: self.start.saturating_sub(self.current_line_offset), | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     fn peek(&self) -> char { | ||||
|         self.source[self.current] | ||||
|     } | ||||
|     fn peek_n(&self, n: usize) -> Option<char> { | ||||
|         self.source.get(self.current + n).copied() | ||||
|     } | ||||
|     fn peek_match(&self, m: &str) -> bool { | ||||
|         let c = self.current; | ||||
|         let s = self | ||||
|             .source | ||||
|             .get(c..(c + m.len())) | ||||
|             .map(|s| s.iter().collect::<String>()); | ||||
|         if let Some(s) = s { s == m } else { false } | ||||
|     } | ||||
|  | ||||
|     fn consume(&mut self) -> char { | ||||
|         let c = self.source[self.current]; | ||||
|         self.current += 1; | ||||
|         c | ||||
|     } | ||||
|  | ||||
|     fn is_eof(&self) -> bool { | ||||
|         self.current == self.source.len() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| use token::Token; | ||||
|  | ||||
| mod ast; | ||||
| mod format; | ||||
| mod lexer; | ||||
| mod parse; | ||||
| mod token; | ||||
|  | ||||
| fn main() { | ||||
|     println!("Hello, world!"); | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::{ | ||||
|         format::Formatter, | ||||
|         lexer::{self, Lexer}, | ||||
|     }; | ||||
|  | ||||
|     const BASIC: &str = r#" | ||||
| function hello(name: string){ | ||||
|    console.log("Hey, ", name);  | ||||
| } | ||||
|  | ||||
|  | ||||
| console.log("Starting!"); | ||||
|  | ||||
| hello(); | ||||
| "#; | ||||
|     #[test] | ||||
|     fn lex() { | ||||
|         println!("Running lex"); | ||||
|         let lexer = Lexer::new(BASIC, Some("basic.file".to_string())); | ||||
|         let tokens = lexer.lex(); | ||||
|         println!( | ||||
|             "{}", | ||||
|             tokens.format(crate::format::FormatterOptions {}).unwrap() | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/parse.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/parse.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| use crate::token::Token; | ||||
|  | ||||
| pub struct Parser { | ||||
|     tokens: Vec<Token>, | ||||
| } | ||||
|  | ||||
| impl Parser { | ||||
|     pub fn new(tokens: Vec<Token>) -> Parser { | ||||
|         Self { tokens } | ||||
|     } | ||||
|  | ||||
|     fn parse(&mut self) {} | ||||
| } | ||||
							
								
								
									
										71
									
								
								src/token.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/token.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| use anyhow::anyhow; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Token { | ||||
|     pub kind: TokenKind, | ||||
|     pub location: TokenLocation, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum TokenKind { | ||||
|     Identifier(String), | ||||
|     Literal(LiteralKind), | ||||
|     Keyword(KeywordKind), | ||||
|     Comment(CommentKind, String), | ||||
|  | ||||
|     LeftParen, | ||||
|     RightParen, | ||||
|     LeftCurly, | ||||
|     RightCurly, | ||||
|     Comma, | ||||
|     Colon, | ||||
|     Semicolon, | ||||
|     Period, | ||||
|     NewLine, | ||||
|     EndOfFile, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum CommentKind { | ||||
|     Line, | ||||
|     Block, | ||||
| } | ||||
|  | ||||
| #[allow(non_camel_case_types)] | ||||
| #[derive(Debug)] | ||||
| pub enum KeywordKind { | ||||
|     function, | ||||
|     string, | ||||
|     number, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&str> for KeywordKind { | ||||
|     type Error = anyhow::Error; | ||||
|     fn try_from(value: &str) -> Result<Self, Self::Error> { | ||||
|         match value { | ||||
|             "function" => Ok(Self::function), | ||||
|             "string" => Ok(Self::string), | ||||
|             "number" => Ok(Self::number), | ||||
|             _ => Err(anyhow!("unknown keyword")), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum LiteralKind { | ||||
|     String(String), | ||||
|     Number(Number), | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum Number { | ||||
|     Integer(usize), | ||||
|     Float(f64), | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct TokenLocation { | ||||
|     pub file: Option<String>, | ||||
|     pub line: usize, | ||||
|     pub column: usize, | ||||
| } | ||||
		Reference in New Issue
	
	Block a user