├── src ├── x.rs ├── evaluator.rs ├── objects.rs ├── old_parser │ ├── mod.rs │ ├── precedences.rs │ ├── parser.rs │ └── parsing.rs ├── old-ast │ ├── mod.rs │ ├── expressions.rs │ ├── base.rs │ ├── statements.rs │ ├── nodes.rs │ └── literals.rs ├── util.rs ├── main.rs ├── parser.rs ├── ast.rs ├── old_lex.rs ├── old_parser.rs └── lexer.rs ├── examples ├── hello-world.sp ├── recursion.sp ├── factorial.sp ├── fib.sp ├── loops.sp ├── types.sp └── arrays.sp ├── Cargo.toml ├── Cargo.lock ├── .gitignore ├── .github └── workflows │ ├── build.yml │ └── lint.yml ├── Dockerfile ├── .vscode └── launch.json ├── .goreleaser.yml ├── README.md ├── assets └── banner.svg └── LICENSE /src/x.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/evaluator.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/objects.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/hello-world.sp: -------------------------------------------------------------------------------- 1 | print("Hello, world!") 2 | -------------------------------------------------------------------------------- /src/old_parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | mod parsing; 3 | mod precedences; 4 | -------------------------------------------------------------------------------- /src/old-ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod base; 2 | pub mod expressions; 3 | pub mod literals; 4 | pub mod nodes; 5 | pub mod statements; 6 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn variant_eq(a: &T, b: &T) -> bool { 2 | std::mem::discriminant(a) == std::mem::discriminant(b) 3 | } 4 | -------------------------------------------------------------------------------- /examples/recursion.sp: -------------------------------------------------------------------------------- 1 | value factorial = f(n) -> 2 | value z = 1 3 | 4 | if (n > 1) -> 5 | update z = (n * factorial(n - 1)) 6 | end 7 | 8 | z 9 | end 10 | 11 | print(string(factorial(5))) -------------------------------------------------------------------------------- /examples/factorial.sp: -------------------------------------------------------------------------------- 1 | value factorial = f(n) -> 2 | value z = 1 3 | 4 | if (n > 1) -> 5 | update z = (n * factorial(n - 1)) 6 | end 7 | 8 | return z; 9 | end 10 | 11 | print(string(factorial(5))) -------------------------------------------------------------------------------- /examples/fib.sp: -------------------------------------------------------------------------------- 1 | value fibonacci = f(n) -> 2 | 3 | if (n == 0) -> 4 | return 0; 5 | end 6 | 7 | if (n == 1 or n == 2) -> 8 | return 1; 9 | end 10 | 11 | fibonacci(n - 1) + fibonacci(n - 2) 12 | end 13 | 14 | print(string(fibonacci(5))) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sepia" 3 | version = "0.1.0" 4 | authors = ["Rishi Kothari "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | lazy_static = "1.4.0" 11 | -------------------------------------------------------------------------------- /examples/loops.sp: -------------------------------------------------------------------------------- 1 | 2 | value while = f(whileTrue, doFunc) -> 3 | if (whileTrue()) -> 4 | doFunc() 5 | while(whileTrue, doFunc) 6 | end 7 | 8 | else -> 9 | true 10 | end 11 | end 12 | 13 | value i = 0 14 | 15 | value isTrue = f() -> 16 | i < 15 17 | end 18 | 19 | while(isTrue, f() -> 20 | update i = i+1 21 | 22 | print(string(i)) 23 | end) 24 | -------------------------------------------------------------------------------- /examples/types.sp: -------------------------------------------------------------------------------- 1 | 2 | # Some basic type examples. 3 | # We'll be going through casting, and everything else. 4 | 5 | # Booleans are exactly how you might expect them to be in other languages. 6 | value x = true 7 | 8 | # Let's try converting it to a string! 9 | value x = string(x) 10 | 11 | # Because we've converted it to a string, we can log it using `print`. 12 | print("The value of x is: " + x) -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "lazy_static" 5 | version = "1.4.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 8 | 9 | [[package]] 10 | name = "sepia" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "lazy_static", 14 | ] 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | sepia 8 | main 9 | dist 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | vendor/ 20 | .env 21 | 22 | 23 | # Added by cargo 24 | 25 | /target 26 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // use parser::Parser; 2 | 3 | mod ast; 4 | mod evaluator; 5 | mod lexer; 6 | mod objects; 7 | mod parser; 8 | mod util; 9 | 10 | fn main() { 11 | let lex = lexer::Lexer::new( 12 | r#" 13 | 1 * 2 + 4"#, 14 | ); 15 | 16 | let z: Vec = lex.collect(); 17 | println!("{:#?}", z); 18 | 19 | // let mut p = Parser::new(z); 20 | // println!("{:#?}", p.parseProgram()); 21 | } 22 | -------------------------------------------------------------------------------- /examples/arrays.sp: -------------------------------------------------------------------------------- 1 | value map = f(arr, fn) -> 2 | value iterateOnArr = f(arr, accumulator) -> 3 | if (len(arr) == 0) -> 4 | accumulator 5 | end 6 | 7 | else -> 8 | iterateOnArr(rest(arr), append(accumulator, fn(first(arr)))) 9 | end 10 | end 11 | 12 | iterateOnArr(arr, []) 13 | end 14 | 15 | value arr = ["hello", "world"] 16 | 17 | value addWorld = f(str) -> 18 | str + "world" 19 | end 20 | 21 | print(len(map(arr, addWorld))) -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | jobs: 11 | build: 12 | runs-on: ubuntu-16.04 13 | strategy: 14 | matrix: 15 | go: [ '1.14', '1.13' ] 16 | name: Sepia Build 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Setup go 20 | uses: actions/setup-go@v1 21 | with: 22 | go-version: ${{ matrix.go }} 23 | - run: go build main.go -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.15-alpine as builder 2 | 3 | # Meta data: 4 | LABEL maintainer="itsrishikothari@gmail.com" 5 | LABEL description="A minimal interpreted programming language." 6 | 7 | # Copying over all files: 8 | COPY . /usr/src/app/ 9 | WORKDIR /usr/src/app 10 | 11 | # Installing deps and building binary: 12 | RUN go get -v -t -d ./... 13 | RUN go build -o sepia . 14 | 15 | # Copying over the binary to a thinner image 16 | # hadolint ignore=DL3006,DL3007 17 | FROM alpine:latest 18 | COPY --from=builder /usr/src/app/sepia/ /usr/bin/ 19 | 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb-vscode", 9 | "request": "launch", 10 | "name": "Debug", 11 | "program": "${workspaceRoot}/target/debug/sepia", 12 | "args": [], 13 | "env": [], 14 | "cwd": "${workspaceRoot}" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | release: 2 | github: 3 | owner: rishiosaur 4 | name: 'sepia' 5 | prerelease: auto 6 | before: 7 | hooks: 8 | - go mod download 9 | builds: 10 | - goos: 11 | - darwin 12 | - linux 13 | - windows 14 | goarch: 15 | - amd64 16 | - arm 17 | - arm64 18 | goarm: 19 | - 6 20 | - 7 21 | brews: 22 | - tap: 23 | owner: rishiosaur 24 | name: homebrew-taps 25 | folder: Formula 26 | homepage: 'https://github.com/rishiosaur/sepia' 27 | description: 'A minimal interpreted language.' 28 | commit_author: 29 | name: Rishi Kothari 30 | email: itsrishikothari@gmail.com -------------------------------------------------------------------------------- /src/old-ast/expressions.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::Token; 2 | 3 | use super::{nodes::Expression, statements::BlockStatement}; 4 | 5 | struct IfExpression<'a> { 6 | token: Token, 7 | condition: Box, 8 | consequence: &'a BlockStatement, 9 | alternative: &'a BlockStatement, 10 | } 11 | 12 | impl<'a> Expression for IfExpression<'a> { 13 | fn literal(&self) -> String { 14 | todo!() 15 | } 16 | 17 | fn string(&self) -> String { 18 | todo!() 19 | } 20 | } 21 | 22 | pub struct CallExpression { 23 | token: Token, 24 | function: Box, 25 | arguments: Vec>, 26 | } 27 | 28 | impl Expression for CallExpression { 29 | fn literal(&self) -> String { 30 | todo!() 31 | } 32 | 33 | fn string(&self) -> String { 34 | todo!() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/old-ast/base.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::{Token, TokenType}; 2 | 3 | use crate::ast::nodes::Expression; 4 | 5 | pub struct PrefixExpression { 6 | token: Token, 7 | right: Box, 8 | } 9 | 10 | impl Expression for PrefixExpression { 11 | fn literal(&self) -> String { 12 | format!("{:?}", self.token) 13 | } 14 | fn string(&self) -> String { 15 | format!("({:?}{:?})", self.token.position, self.token) 16 | } 17 | } 18 | 19 | pub struct InfixExpression { 20 | token: Token, 21 | right: Box, 22 | left: Box, 23 | } 24 | 25 | impl Expression for InfixExpression { 26 | fn literal(&self) -> String { 27 | format!("{:?}", self.token) 28 | } 29 | fn string(&self) -> String { 30 | format!("({:?}{:?})", self.token.position, self.token) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | jobs: 11 | golangci: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v2 18 | with: 19 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 20 | version: v1.29 21 | 22 | # Optional: working directory, useful for monorepos 23 | # working-directory: somedir 24 | 25 | # Optional: golangci-lint command line arguments. 26 | # args: --issues-exit-code=0 27 | 28 | # Optional: show only new issues if it's a pull request. The default value is `false`. 29 | # only-new-issues: true -------------------------------------------------------------------------------- /src/old_parser/precedences.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::TokenType; 2 | 3 | pub enum Precedence { 4 | LOWEST = 0, 5 | AND = 1, 6 | OR = 2, 7 | EQUALS = 3, 8 | LESSGREATER = 4, 9 | 10 | SUM = 5, 11 | PRODUCT = 6, 12 | PREFIX = 7, 13 | CALL = 8, 14 | INDEX = 9, 15 | } 16 | 17 | pub fn get_precedence(kind: TokenType) -> Option { 18 | match kind { 19 | TokenType::Equal => Some(Precedence::EQUALS), 20 | TokenType::NotEqual => Some(Precedence::EQUALS), 21 | TokenType::LT => Some(Precedence::LESSGREATER), 22 | TokenType::GT => Some(Precedence::LESSGREATER), 23 | TokenType::LTEq => Some(Precedence::LESSGREATER), 24 | TokenType::GTEq => Some(Precedence::LESSGREATER), 25 | TokenType::Or => Some(Precedence::OR), 26 | TokenType::And => Some(Precedence::AND), 27 | TokenType::Plus => Some(Precedence::SUM), 28 | TokenType::Minus => Some(Precedence::SUM), 29 | TokenType::Asterisk => Some(Precedence::PRODUCT), 30 | TokenType::Slash => Some(Precedence::PRODUCT), 31 | TokenType::LParen => Some(Precedence::CALL), 32 | TokenType::LBracket => Some(Precedence::INDEX), 33 | _ => None, 34 | } 35 | } 36 | 37 | pub fn int_precedence(precedence: Precedence) -> usize { 38 | precedence as usize 39 | } 40 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::{Token, TokenType}; 2 | 3 | #[derive(Debug, PartialOrd, PartialEq)] 4 | enum Precedence { 5 | LOWEST = 0, 6 | AND = 1, 7 | OR = 2, 8 | EQUALS = 3, 9 | LESSGREATER = 4, 10 | SUM = 5, 11 | PRODUCT = 6, 12 | PREFIX = 7, 13 | CALL = 8, 14 | INDEX = 9, 15 | } 16 | 17 | impl Precedence { 18 | fn from_tok(token: &TokenType) -> Option { 19 | match *token { 20 | TokenType::Equal => Some(Precedence::EQUALS), 21 | TokenType::NotEqual => Some(Precedence::EQUALS), 22 | TokenType::LT => Some(Precedence::LESSGREATER), 23 | TokenType::GT => Some(Precedence::LESSGREATER), 24 | TokenType::LTEq => Some(Precedence::LESSGREATER), 25 | TokenType::GTEq => Some(Precedence::LESSGREATER), 26 | TokenType::Or => Some(Precedence::OR), 27 | TokenType::And => Some(Precedence::AND), 28 | TokenType::Plus => Some(Precedence::SUM), 29 | TokenType::Minus => Some(Precedence::SUM), 30 | TokenType::Asterisk => Some(Precedence::PRODUCT), 31 | TokenType::Slash => Some(Precedence::PRODUCT), 32 | TokenType::LParen => Some(Precedence::CALL), 33 | TokenType::LBracket => Some(Precedence::INDEX), 34 | _ => None 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/old-ast/statements.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::Token; 2 | 3 | use super::{ 4 | literals::IdentifierLiteral, 5 | nodes::{Expression, Statement}, 6 | }; 7 | 8 | pub struct BlockStatement { 9 | token: Token, 10 | statements: Vec>, 11 | } 12 | 13 | impl Statement for BlockStatement { 14 | fn literal(&self) -> String { 15 | todo!() 16 | } 17 | 18 | fn string(&self) -> String { 19 | todo!() 20 | } 21 | } 22 | 23 | pub struct ExpressionStatement { 24 | token: Token, 25 | expression: Box, 26 | } 27 | 28 | impl Statement for ExpressionStatement { 29 | fn literal(&self) -> String { 30 | todo!() 31 | } 32 | 33 | fn string(&self) -> String { 34 | todo!() 35 | } 36 | } 37 | 38 | pub struct ValueStatement { 39 | token: Token, 40 | name: IdentifierLiteral, 41 | value: Box, 42 | } 43 | 44 | impl<'a> Statement for ValueStatement { 45 | fn literal(&self) -> String { 46 | todo!() 47 | } 48 | 49 | fn string(&self) -> String { 50 | todo!() 51 | } 52 | } 53 | 54 | pub struct UpdateStatement { 55 | token: Token, 56 | name: IdentifierLiteral, 57 | value: Box, 58 | } 59 | 60 | impl<'a> Statement for UpdateStatement { 61 | fn literal(&self) -> String { 62 | todo!() 63 | } 64 | 65 | fn string(&self) -> String { 66 | todo!() 67 | } 68 | } 69 | 70 | pub struct ReturnStatement { 71 | token: Token, 72 | value: Box, 73 | } 74 | 75 | impl Statement for ReturnStatement { 76 | fn literal(&self) -> String { 77 | todo!() 78 | } 79 | 80 | fn string(&self) -> String { 81 | todo!() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/old_parser/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::nodes::Program, 3 | lexer::{Lexer, Token, TokenType}, 4 | util::variant_eq, 5 | }; 6 | 7 | use super::precedences::{get_precedence, Precedence}; 8 | 9 | pub struct Parser<'a> { 10 | pub tokens: &'a Vec, 11 | pub position: usize, 12 | } 13 | 14 | impl<'a> Parser<'a> { 15 | pub fn current_token(&self) -> Option<&Token> { 16 | self.tokens.get(self.position) 17 | } 18 | 19 | pub fn peek_token(&self) -> Option<&Token> { 20 | self.tokens.get(self.position + 1) 21 | } 22 | 23 | pub fn current_precedence(&self) -> Precedence { 24 | match get_precedence(self.current_token().unwrap().clone().kind) { 25 | Some(p) => p, 26 | _ => Precedence::LOWEST, 27 | } 28 | } 29 | 30 | pub fn peek_precedence(&self) -> Precedence { 31 | match get_precedence(self.peek_token().unwrap().clone().kind) { 32 | Some(p) => p, 33 | _ => Precedence::LOWEST, 34 | } 35 | } 36 | 37 | pub fn current_token_is(&self, kind: TokenType) -> bool { 38 | self.current_token().unwrap().kind == kind 39 | } 40 | 41 | pub fn peek_token_is(&self, kind: TokenType) -> bool { 42 | self.peek_token().unwrap().kind == kind 43 | } 44 | 45 | pub fn expect_peek(&mut self, kind: TokenType) -> bool { 46 | if variant_eq(&kind, &self.peek_token().unwrap().kind) { 47 | self.consume_token(); 48 | return true; 49 | } 50 | 51 | panic!( 52 | "{:?} Peek token was not {:?}, got {:?} instead", 53 | self.position, 54 | kind, 55 | self.peek_token() 56 | ) 57 | } 58 | 59 | pub fn consume_token(&mut self) { 60 | self.position += 1; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::lexer::Token; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Program { 7 | pub statements: Vec, 8 | } 9 | #[derive(Debug, Clone)] 10 | pub enum Expression { 11 | IdentifierLiteral { 12 | token: Token, 13 | }, 14 | StringLiteral { 15 | token: Token, 16 | }, 17 | ArrayLiteral { 18 | token: Token, 19 | elements: Vec, 20 | }, 21 | BooleanLiteral { 22 | token: Token, 23 | }, 24 | IntegerLiteral { 25 | token: Token, 26 | }, 27 | FloatLiteral { 28 | 29 | token: Token, 30 | }, 31 | IndexExpression { 32 | token: Token, 33 | left: Box, 34 | index: Box, 35 | }, 36 | MapLiteral { 37 | token: Token, 38 | pairs: HashMap,Box>, 39 | }, 40 | FunctionLiteral { 41 | token: Token, 42 | parameters: Vec, 43 | }, 44 | 45 | PrefixExpression { 46 | token: Token, 47 | right: Box, 48 | }, 49 | 50 | InfixExpression { 51 | token: Token, 52 | right: Box, 53 | left: Box, 54 | }, 55 | 56 | IfExpression { 57 | token: Token, 58 | condition: Box, 59 | consequence: Statement, 60 | alternative: Statement, 61 | }, 62 | } 63 | 64 | #[derive(Debug, Clone)] 65 | pub enum Statement { 66 | BlockStatement { 67 | token: Token, 68 | statements: Vec, 69 | }, 70 | ExpressionStatement { 71 | token: Token, 72 | expression: Box, 73 | }, 74 | ValueStatement { 75 | token: Token, 76 | name: Token, 77 | value: Box, 78 | }, 79 | UpdateStatement { 80 | token: Token, 81 | name: Token, 82 | value: Box, 83 | }, 84 | ReturnStatement { 85 | token: Token, 86 | value: Box, 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /src/old-ast/nodes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::lexer::Token; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Program<'a> { 7 | statements: Vec>>, 8 | } 9 | #[derive(Debug, Clone)] 10 | pub enum Expression<'a> { 11 | IdentifierLiteral { 12 | token: &'a Token, 13 | }, 14 | StringLiteral { 15 | token: &'a Token, 16 | }, 17 | ArrayLiteral { 18 | token: &'a Token, 19 | elements: &'a Vec>>, 20 | }, 21 | BooleanLiteral { 22 | token: &'a Token, 23 | }, 24 | 25 | FloatLiteral { 26 | token: &'a Token, 27 | }, 28 | IndexExpression { 29 | token: &'a Token, 30 | left: &'a Box>, 31 | index: &'a Box>, 32 | }, 33 | MapLiteral { 34 | token: &'a Token, 35 | pairs: HashMap<&'a Box>, &'a Box>>, 36 | }, 37 | FunctionLiteral { 38 | token: &'a Token, 39 | parameters: &'a Vec>, 40 | }, 41 | 42 | PrefixExpression { 43 | token: &'a Token, 44 | right: &'a Box>, 45 | }, 46 | 47 | InfixExpression { 48 | token: &'a Token, 49 | right: &'a Box>, 50 | left: &'a Box>, 51 | }, 52 | 53 | IfExpression { 54 | token: Token, 55 | condition: &'a Box, 56 | consequence: &'a Statement<'a>, 57 | alternative: &'a Statement<'a>, 58 | }, 59 | } 60 | 61 | #[derive(Debug, Clone)] 62 | pub enum Statement<'a> { 63 | BlockStatement { 64 | token: &'a Token, 65 | statements: Vec<&'a Box>>, 66 | }, 67 | ExpressionStatement { 68 | token: &'a Token, 69 | statements: Vec<&'a Box>>, 70 | }, 71 | ValueStatement { 72 | token: &'a Token, 73 | name: &'a Expression<'a>, 74 | value: &'a Box>, 75 | }, 76 | UpdateStatement { 77 | token: &'a Token, 78 | name: &'a Expression<'a>, 79 | value: &'a Box>, 80 | }, 81 | ReturnStatement { 82 | token: &'a Token, 83 | value: &'a Box>, 84 | }, 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](assets/banner.svg) 2 | # 🌇 Sepia 3 | ![Github Build status](https://img.shields.io/github/workflow/status/rishiosaur/sepia/build?event=push&style=flat-square) 4 | ![GitHub Workflow Status (event)](https://img.shields.io/github/workflow/status/rishiosaur/sepia/lint?event=push&label=lint&style=flat-square) 5 | ![GitHub](https://img.shields.io/github/license/rishiosaur/sepia?style=flat-square) 6 | ![GitHub issues](https://img.shields.io/github/issues/rishiosaur/sepia?style=flat-square) 7 | ![GitHub contributors](https://img.shields.io/github/contributors/rishiosaur/sepia?style=flat-square) 8 | ![GitHub last commit](https://img.shields.io/github/last-commit/rishiosaur/sepia?style=flat-square) 9 | 10 | A minimal, extremely opinionated, functional programming language. 11 | 12 | ## Installation 13 | 14 | On macOS/Linux systems with `Homebrew` installed, you can add Sepia by running `brew install rishiosaur/taps/sepia`. 15 | 16 | To test `sepia` out, run `sepia` in any folder! 17 | 18 | ### Running examples 19 | 20 | Clone `rishiosaur/sepia`, then change directories into the cloned directory. Run `sepia examples/types.sp` as a first example. 21 | 22 | ### VS Code Extension 23 | 24 | I've also designed a VSC toolset around Sepia, which you can find [on the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=rishiosaur.sepia). 25 | 26 | --- 27 | 28 | ## 🎨 Design Goals 29 | Sepia started off as a project to get to learn interpreters in-depth in as short a timeframe as possible (I'll always remember those 20 hours). 30 | 31 | However, it ended up being an idealized version of what I believe programming languages should be: it's a project that's aimed squarely at replacing some of the more well-known languages in my workflow. 32 | 33 | ### 📚 Readability. 34 | 35 | I'm sick and tired of programming languages not being intuitive to write. Whether that be understanding the way that `this` works in JS or figuring out why type checking syntax is the way it is in Go, nearly every major programming language has some unintuitive piece of it. 36 | 37 | Sepia aims to replace every bit of unclear syntax with something that makes sense (usually borrowed from a *mathematical* convention). Every piece of syntax and semantic should have some reason for its implementation. 38 | 39 | ### 🛠 Functionality (alternatively, the destruction of OOP). 40 | 41 | I've always hated the notion of classes and methods 'on' types: it's obtuse, and doesn't lead to great generalizations. 42 | 43 | That's why structures are exactly (and exclusively) what they sound like: a type that defines the structure of data. All functions that're supposed to work 'on' those types are just functions with an argument that requires that type. This not only means that parsing is easier, strong typing is available WHEN NEEDED, and programs execute faster, but it's also a *lot* easier to understand, and allows for incredible generalizations. 44 | 45 | ### 👁 Minimalism. 46 | 47 | When possible, Sepia tries to reuse existing syntax or keywords, and places a focus on clever algorithmic work and efficiency (wrapping the interpreter in Go gives Sepia easy concurrency for asynchronous applications): it doesn't even have loops! 48 | 49 | --- 50 | -------------------------------------------------------------------------------- /src/old-ast/literals.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap}; 2 | 3 | use crate::lexer::Token; 4 | 5 | // use super::{nodes::Expression, statements::BlockStatement}; 6 | 7 | 8 | 9 | // pub struct IdentifierLiteral { 10 | // token: Token, 11 | // } 12 | 13 | // impl Expression for IdentifierLiteral { 14 | // fn literal(&self) -> String { 15 | // todo!() 16 | // } 17 | 18 | // fn string(&self) -> String { 19 | // todo!() 20 | // } 21 | // } 22 | 23 | // pub struct StringLiteral { 24 | // token: Token, 25 | // } 26 | 27 | // impl Expression for StringLiteral { 28 | // fn literal(&self) -> String { 29 | // todo!() 30 | // } 31 | 32 | // fn string(&self) -> String { 33 | // todo!() 34 | // } 35 | // } 36 | 37 | // pub struct ArrayLiteral { 38 | // token: Token, 39 | // elements: Vec>, 40 | // } 41 | 42 | // impl Expression for ArrayLiteral { 43 | // fn literal(&self) -> String { 44 | // todo!() 45 | // } 46 | 47 | // fn string(&self) -> String { 48 | // todo!() 49 | // } 50 | // } 51 | 52 | // pub struct BooleanLiteral { 53 | // token: Token, 54 | // } 55 | 56 | // impl Expression for BooleanLiteral { 57 | // fn literal(&self) -> String { 58 | // todo!() 59 | // } 60 | 61 | // fn string(&self) -> String { 62 | // todo!() 63 | // } 64 | // } 65 | 66 | // pub struct IndexExpression { 67 | // token: Token, 68 | // left: Box, 69 | // index: Box, 70 | // } 71 | 72 | // impl Expression for IndexExpression { 73 | // fn literal(&self) -> String { 74 | // todo!() 75 | // } 76 | 77 | // fn string(&self) -> String { 78 | // todo!() 79 | // } 80 | // } 81 | 82 | // pub struct MapLiteral { 83 | // token: Token, 84 | // pairs: HashMap, Box>, 85 | // } 86 | 87 | // impl Expression for MapLiteral { 88 | // fn literal(&self) -> String { 89 | // todo!() 90 | // } 91 | 92 | // fn string(&self) -> String { 93 | // todo!() 94 | // } 95 | // } 96 | 97 | // pub struct FunctionLiteral<'a, 'b> { 98 | // token: Token, 99 | // parameters: Vec<&'a IdentifierLiteral>, 100 | // body: &'b BlockStatement, 101 | // } 102 | 103 | // impl<'a, 'b> Expression for FunctionLiteral<'a, 'b> { 104 | // fn literal(&self) -> String { 105 | // todo!() 106 | // } 107 | 108 | // fn string(&self) -> String { 109 | // todo!() 110 | // } 111 | // } 112 | 113 | // pub struct IntegerLiteral { 114 | // token: Token, 115 | // } 116 | 117 | // impl Expression for IntegerLiteral { 118 | // fn literal(&self) -> String { 119 | // todo!() 120 | // } 121 | 122 | // fn string(&self) -> String { 123 | // todo!() 124 | // } 125 | // } 126 | 127 | // pub struct FloatLiteral { 128 | // token: Token, 129 | // } 130 | 131 | // impl Expression for FloatLiteral { 132 | // fn literal(&self) -> String { 133 | // todo!() 134 | // } 135 | 136 | // fn string(&self) -> String { 137 | // todo!() 138 | // } 139 | // } 140 | -------------------------------------------------------------------------------- /assets/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/old_parser/parsing.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | lexer::{TokenType}, 3 | ast::nodes::{ 4 | Program, 5 | Statement, 6 | Expression 7 | } 8 | }; 9 | 10 | use super::{ 11 | parser::Parser, 12 | precedences::{int_precedence, Precedence}, 13 | }; 14 | 15 | impl<'a> Parser<'a> { 16 | pub fn parseProgram(&mut self) -> Program { 17 | let mut statements = Vec::>::new(); 18 | 19 | for tok in self.tokens.iter() { 20 | let stmt = self.parseStatement(); 21 | 22 | statements.push(stmt); 23 | } 24 | 25 | Program { 26 | statements, 27 | } 28 | } 29 | 30 | fn parseStatement(&mut self) -> Box<&Statement> { 31 | match self.current_token().unwrap().kind { 32 | crate::lexer::TokenType::Value => self.parseValueStatement(), 33 | crate::lexer::TokenType::Update => self.parseUpdateStatement(), 34 | crate::lexer::TokenType::Return => self.parseReturnStatement(), 35 | _ => self.parseExpressionStatement(), 36 | } 37 | } 38 | 39 | fn parseReturnStatement(&mut self) -> Box<&Statement> { 40 | let token = self.current_token().unwrap(); 41 | self.consume_token(); 42 | let value = self.parseExpression(Precedence::LOWEST); 43 | 44 | while !self.current_token_is(TokenType::Semicolon) { 45 | self.consume_token(); 46 | } 47 | 48 | Box::new(&Statement::ReturnStatement { 49 | token, 50 | value, 51 | }) 52 | } 53 | 54 | fn parseValueStatement(&mut self) -> Box { 55 | let tok = self.current_token().unwrap(); 56 | 57 | self.expect_peek(TokenType::Identifier(String::new())); 58 | 59 | let name = IdentifierLiteral { 60 | token: self.current_token().unwrap(), 61 | }; 62 | 63 | self.expect_peek(TokenType::Assign); 64 | 65 | self.consume_token(); 66 | 67 | let value = self.parseExpression(Precedence::LOWEST); 68 | 69 | if self.peek_token_is(TokenType::Semicolon) { 70 | self.consume_token(); 71 | } 72 | 73 | Box::new(ValueStatement { 74 | token: tok, 75 | name: name, 76 | value: value, 77 | }) 78 | } 79 | 80 | fn parseUpdateStatement(&mut self) -> Box { 81 | let tok = self.current_token().unwrap(); 82 | 83 | self.expect_peek(TokenType::Identifier(String::new())); 84 | 85 | let name = IdentifierLiteral { 86 | token: self.current_token().unwrap(), 87 | }; 88 | 89 | self.expect_peek(TokenType::Assign); 90 | 91 | self.consume_token(); 92 | 93 | let value = self.parseExpression(Precedence::LOWEST); 94 | 95 | if self.peek_token_is(TokenType::Semicolon) { 96 | self.consume_token(); 97 | } 98 | 99 | Box::new(UpdateStatement { 100 | token: tok, 101 | name: name, 102 | value: value, 103 | }) 104 | } 105 | 106 | fn parseExpressionStatement(&mut self) -> Box { 107 | let expression = self.parseExpression(Precedence::LOWEST); 108 | let tok= self.current_token().unwrap(); 109 | 110 | if self.current_token_is(TokenType::Semicolon) { 111 | self.consume_token(); 112 | } 113 | 114 | Box::new(ExpressionStatement { 115 | expression: expression, 116 | token: tok, 117 | }) 118 | } 119 | 120 | fn parseExpression(&mut self, precedence: Precedence) -> Box<&'a Expression<'a>> { 121 | let leftExp = self.matchPrefixExpression(self.current_token().unwrap().kind); 122 | 123 | while !self.peek_token_is(TokenType::Semicolon) 124 | && int_precedence(precedence) < int_precedence(self.peek_precedence()) 125 | { 126 | let infixed = self.matchInfixExpression(self.current_token().unwrap().kind, leftExp.clone()); 127 | 128 | match infixed { 129 | None => return leftExp, 130 | Some(infix) => { 131 | self.consume_token(); 132 | return infix; 133 | } 134 | } 135 | } 136 | 137 | return leftExp; 138 | } 139 | 140 | fn matchPrefixExpression(&mut self, kind: TokenType) -> Box { 141 | match kind { 142 | TokenType::Integer(_) => self.parseIntegerExpression(), 143 | TokenType::String(_) => self.parseStringExpression(), 144 | TokenType::Boolean(_) => self.parseBooleanExpression(), 145 | TokenType::Identifier(_) => self.parseIdentifierExpression(), 146 | TokenType::Minus => self.parsePrefixExpression(), 147 | _ => panic!( 148 | "[{}] PARSER ERROR: NO PREFIX FUNCTION FOUND FOR {:?}", 149 | self.position, kind 150 | ), 151 | } 152 | } 153 | 154 | fn parseBooleanExpression(&self) -> Box { 155 | Box::new(BooleanLiteral { 156 | token: self.current_token().unwrap(), 157 | }) 158 | } 159 | 160 | fn parseIntegerExpression(&self) -> Box { 161 | Box::new(IntegerLiteral { 162 | token: self.current_token().unwrap(), 163 | }) 164 | } 165 | 166 | fn parseStringExpression(&self) -> Box { 167 | Box::new(StringLiteral { 168 | token: self.current_token().unwrap(), 169 | }) 170 | } 171 | 172 | fn parseIdentifierExpression(&self) -> Box<&'a Expression> { 173 | Box::new(IdentifierLiteral { 174 | token: self.current_token().unwrap(), 175 | }) 176 | } 177 | 178 | fn parsePrefixExpression(&mut self) -> Box { 179 | let tok = self.current_token().unwrap(); 180 | 181 | self.consume_token(); 182 | 183 | let right = self.parseExpression(Precedence::LOWEST); 184 | 185 | Box::new(PrefixExpression { 186 | token: tok, 187 | 188 | right: right, 189 | }) 190 | } 191 | 192 | fn matchInfixExpression( 193 | &mut self, 194 | kind: TokenType, 195 | leftExpression: Box, 196 | ) -> Option> { 197 | match kind { 198 | TokenType::Plus => Some(self.parseInfixExpression(leftExpression)), 199 | TokenType::Minus => Some(self.parseInfixExpression(leftExpression)), 200 | TokenType::Asterisk => Some(self.parseInfixExpression(leftExpression)), 201 | TokenType::Slash => Some(self.parseInfixExpression(leftExpression)), 202 | TokenType::Equal => Some(self.parseInfixExpression(leftExpression)), 203 | TokenType::NotEqual => Some(self.parseInfixExpression(leftExpression)), 204 | TokenType::LT => Some(self.parseInfixExpression(leftExpression)), 205 | TokenType::GT => Some(self.parseInfixExpression(leftExpression)), 206 | TokenType::LTEq => Some(self.parseInfixExpression(leftExpression)), 207 | TokenType::GTEq => Some(self.parseInfixExpression(leftExpression)), 208 | _ => None, 209 | } 210 | } 211 | 212 | fn parseInfixExpression(&mut self, left: Box) -> Box { 213 | let tok = self.current_token().unwrap(); 214 | 215 | 216 | self.consume_token(); 217 | 218 | let right = self.parseExpression(Precedence::LOWEST); 219 | 220 | Box::new(InfixExpression { 221 | token: tok, 222 | 223 | right: right, 224 | left: left, 225 | }) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/old_lex.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | 4 | use fmt::Formatter; 5 | 6 | lazy_static! { 7 | static ref KEYWORDS: HashMap<&'static str, TokenType> = { 8 | let mut keywords = HashMap::new(); 9 | keywords.insert("f", TokenType::FUNCTION); 10 | keywords.insert("value", TokenType::VALUE); 11 | keywords.insert("true", TokenType::TRUE); 12 | keywords.insert("false", TokenType::FALSE); 13 | keywords.insert("if", TokenType::IF); 14 | keywords.insert("else", TokenType::ELSE); 15 | keywords.insert("return", TokenType::RETURN); 16 | keywords.insert("is", TokenType::EQ); 17 | keywords.insert("not", TokenType::NOT_EQ); 18 | keywords.insert("end", TokenType::CLOSEBLOCK); 19 | keywords.insert("update", TokenType::UPDATE); 20 | keywords.insert("constant", TokenType::CONSTANT); 21 | keywords.insert("and", TokenType::AND); 22 | keywords.insert("or", TokenType::OR); 23 | keywords 24 | }; 25 | } 26 | 27 | #[derive(Debug)] 28 | pub enum TokenType { 29 | // Endings 30 | ILLEGAL, 31 | EOF, 32 | // Identifiers + literals 33 | IDENT, 34 | INT(usize), 35 | // Operators 36 | ASSIGN, 37 | PLUS, 38 | EQ, 39 | NOT_EQ, 40 | 41 | // Delimiters 42 | COMMA, 43 | SEMICOLON, 44 | LPAREN, 45 | RPAREN, 46 | LBRACE, 47 | RBRACE, 48 | LBRACKET, 49 | RBRACKET, 50 | COLON, 51 | // Keywords 52 | FUNCTION, 53 | VALUE, 54 | UPDATE, 55 | CONSTANT, 56 | TRUE, 57 | FALSE, 58 | IF, 59 | ELSE, 60 | RETURN, 61 | STRING(String), 62 | MINUS, 63 | BANG, 64 | ASTERISK, 65 | SLASH, 66 | LT, 67 | GT, 68 | LTEQ, 69 | GTEQ, 70 | INCREMENT, 71 | DECREMENT, 72 | MINUSEQ, 73 | PLUSEQ, 74 | MULEQ, 75 | SLASHEQ, 76 | OR, 77 | AND, 78 | OPENBLOCK, 79 | CLOSEBLOCK, 80 | } 81 | 82 | use TokenType::*; 83 | 84 | pub struct Token<'a> { 85 | Type: TokenType, 86 | literal: &'a str, 87 | line: usize, 88 | column: usize, 89 | 90 | } 91 | 92 | impl<'a> fmt::Display for Token<'a> { 93 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 94 | write!(f, "({}, {}) [{:?}] lit: '{}'", self.line, self.column, self.Type, self.literal) 95 | } 96 | } 97 | 98 | pub fn lookup_ident(identifier: &str) -> Option<&'static TokenType> { 99 | KEYWORDS.get(identifier) 100 | } 101 | 102 | pub struct LexerError { 103 | line: usize, 104 | column: usize, 105 | reason: String, 106 | } 107 | pub struct Lexer { 108 | pub input: String, 109 | pub position: usize, 110 | pub reading_position: usize, 111 | pub column: usize, 112 | pub line: usize, 113 | pub current_char: char, 114 | pub errors: Vec 115 | } 116 | 117 | impl Lexer { 118 | fn next_token(&mut self) -> Result { 119 | let t: Token; 120 | 121 | self.skipWhitespace(); 122 | 123 | match self.current_char { 124 | '(' => t = self.newCharToken(LPAREN), 125 | ')' => t = self.newCharToken(RPAREN), 126 | '+' => t = self.newCharToken(PLUS), 127 | '-' => { 128 | if self.peek_character() == '>' { 129 | t = self.newCharToken(OPENBLOCK) 130 | } else { 131 | 132 | } 133 | } 134 | ',' => t = self.newCharToken(COMMA), 135 | '{' => t = self.newCharToken(LBRACKET), 136 | _ => { 137 | if self.current_char.is_numeric() { 138 | let int_str = self.consumeInteger(); 139 | let int = int_str.parse::().unwrap(); 140 | 141 | t.literal = int_str; 142 | t.Type = INT(int); 143 | } else if self.current_char.is_alphabetic() { 144 | let start_col = self.column; 145 | let start_line = self.line; 146 | let str_literal = self.consumeIdentifier().clone(); 147 | let TT = lookup_ident(str_literal); 148 | match TT { 149 | Some(&TT) => { 150 | t = Token { 151 | line: start_line, 152 | column: start_col, 153 | literal: str_literal, 154 | Type: TT 155 | } 156 | }, 157 | None => { 158 | t = Token { 159 | line: start_line, 160 | column: start_col, 161 | literal: str_literal, 162 | Type: IDENT 163 | } 164 | } 165 | }; 166 | } else if self.current_char.is_numeric() { 167 | let start_col = self.column; 168 | let start_line = self.line; 169 | let str_literal = self.consumeInteger().clone(); 170 | let int = str_literal.parse::(); 171 | 172 | match int { 173 | Ok(i) => t = Token { 174 | line: start_line, 175 | column: start_col, 176 | literal: str_literal, 177 | Type: INT(i) 178 | }, 179 | Err(z) => { 180 | self.add_error(format!("({}, {}) - {}", self.line, self.column, z)); 181 | return Result::Err(format!("({}, {}) - {}", self.line, self.column, z)); 182 | } 183 | } 184 | 185 | 186 | } else { 187 | self.add_error(format!("({}, {}) - {}", self.line, self.column, "illegal token detected.")); 188 | return Result::Err(format!("({}, {}) - {}", self.line, self.column, "illegal token detected.")); 189 | } 190 | } 191 | 192 | } 193 | 194 | self.consume_char(); 195 | 196 | return Result::Ok(t) 197 | } 198 | 199 | pub fn consume_char(&mut self) { 200 | if self.reading_position >= self.input.len() { 201 | self.current_char = 0 as char; 202 | } else { 203 | self.current_char = self.input.chars().nth(self.reading_position).unwrap(); 204 | } 205 | 206 | self.position += 1; 207 | self.reading_position += 1; 208 | self.column += 1; 209 | } 210 | 211 | fn skipWhitespace(&mut self) { 212 | while self.current_char.is_ascii_whitespace() { 213 | if self.current_char == '\n' { 214 | self.line += 1; 215 | self.column = 1; 216 | } 217 | self.consume_char(); 218 | } 219 | } 220 | 221 | fn consumeInteger(&mut self) -> &str { 222 | let pos = self.position; 223 | while self.current_char.is_ascii_digit() { 224 | self.consume_char() 225 | } 226 | 227 | return &self.input[pos..self.position]; 228 | } 229 | 230 | fn consumeIdentifier<'a>(&'a mut self) -> &str { 231 | let pos = self.position; 232 | while self.current_char.is_alphabetic() { 233 | self.consume_char() 234 | } 235 | 236 | return &self.input[pos..self.position]; 237 | } 238 | 239 | fn readString(&mut self) -> &str { 240 | let pos = self.position; 241 | loop { 242 | self.consume_char(); 243 | if self.current_char == '"' || self.current_char == 0 as char { 244 | break; 245 | } 246 | } 247 | 248 | return &self.input[pos..self.position]; 249 | } 250 | 251 | fn peek_character(&self) -> char { 252 | if self.reading_position >= self.input.len() { 253 | return 0 as char; 254 | } else { 255 | return self.input.chars().nth(self.reading_position).unwrap(); 256 | } 257 | } 258 | 259 | fn newCharToken<'a>(&'a self, Type: TokenType) -> Token<'a> { 260 | Token { 261 | column: self.column, 262 | line: self.line, 263 | literal: self.current_char.to_string().as_str(), 264 | Type: Type 265 | } 266 | } 267 | 268 | fn add_error(&mut self, reason: String) { 269 | let mut owned = "LEXER ERROR: ".to_owned(); 270 | owned.push_str(&reason); 271 | let err = LexerError { 272 | reason: owned, 273 | line: self.line, 274 | column: self.column 275 | }; 276 | 277 | self.errors.push(err); 278 | } 279 | 280 | fn lex(&mut self) { 281 | let tokens = Vec::::new(); 282 | while self.position != self.input.len()-1 { 283 | let token = self.next_token(); 284 | 285 | 286 | } 287 | } 288 | } 289 | 290 | fn New(input: String) -> Lexer { 291 | Lexer { 292 | input: input, 293 | position: 0, 294 | reading_position: 1, 295 | column: 1, 296 | line: 1, 297 | current_char: input.chars().nth(0).unwrap(), 298 | errors: vec![] 299 | } 300 | } -------------------------------------------------------------------------------- /src/old_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::{Expression, Program, Statement}, 3 | lexer::Token, 4 | util::variant_eq, 5 | }; 6 | 7 | use crate::lexer::TokenType; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub enum Precedence { 11 | LOWEST = 0, 12 | AND = 1, 13 | OR = 2, 14 | EQUALS = 3, 15 | LESSGREATER = 4, 16 | SUM = 5, 17 | PRODUCT = 6, 18 | PREFIX = 7, 19 | CALL = 8, 20 | INDEX = 9, 21 | } 22 | 23 | pub fn get_precedence(kind: TokenType) -> Option { 24 | match kind { 25 | TokenType::Equal => Some(Precedence::EQUALS), 26 | TokenType::NotEqual => Some(Precedence::EQUALS), 27 | TokenType::LT => Some(Precedence::LESSGREATER), 28 | TokenType::GT => Some(Precedence::LESSGREATER), 29 | TokenType::LTEq => Some(Precedence::LESSGREATER), 30 | TokenType::GTEq => Some(Precedence::LESSGREATER), 31 | TokenType::Or => Some(Precedence::OR), 32 | TokenType::And => Some(Precedence::AND), 33 | TokenType::Plus => Some(Precedence::SUM), 34 | TokenType::Minus => Some(Precedence::SUM), 35 | TokenType::Asterisk => Some(Precedence::PRODUCT), 36 | TokenType::Slash => Some(Precedence::PRODUCT), 37 | TokenType::LParen => Some(Precedence::CALL), 38 | TokenType::LBracket => Some(Precedence::INDEX), 39 | _ => None, 40 | } 41 | } 42 | 43 | pub fn int_precedence(precedence: Precedence) -> usize { 44 | precedence as usize 45 | } 46 | 47 | pub struct Parser { 48 | pub tokens: Vec, 49 | pub position: usize, 50 | } 51 | 52 | impl Parser { 53 | pub fn current_token(&self) -> Option { 54 | self.tokens.get(self.position).cloned() 55 | } 56 | 57 | pub fn peek_token(&self) -> Option { 58 | self.tokens.get(self.position + 1).cloned() 59 | } 60 | 61 | pub fn current_precedence(&self) -> Precedence { 62 | match get_precedence(self.current_token().unwrap().clone().kind) { 63 | Some(p) => p, 64 | _ => Precedence::LOWEST, 65 | } 66 | } 67 | 68 | pub fn peek_precedence(&self) -> Precedence { 69 | if let Some(peek) = self.peek_token() { 70 | return match get_precedence(peek.kind) { 71 | Some(p) => p, 72 | _ => Precedence::LOWEST, 73 | }; 74 | } else { 75 | return Precedence::LOWEST; 76 | } 77 | } 78 | 79 | pub fn current_token_is(&self, kind: &TokenType) -> bool { 80 | if let Some(peek) = self.current_token() { 81 | return peek.kind == *kind; 82 | } else { 83 | return false; 84 | } 85 | } 86 | 87 | pub fn peek_token_is(&self, kind: &TokenType) -> bool { 88 | if let Some(peek) = self.peek_token() { 89 | return peek.kind == *kind; 90 | } else { 91 | return false; 92 | } 93 | } 94 | 95 | pub fn expect_peek(&mut self, kind: &TokenType) -> bool { 96 | if variant_eq(kind, &self.peek_token().unwrap().kind) { 97 | self.consume_token(); 98 | return true; 99 | } 100 | 101 | panic!( 102 | "{:?} Peek token was not {:?}, got {:?} instead", 103 | self.position, 104 | kind, 105 | self.peek_token() 106 | ) 107 | } 108 | 109 | pub fn consume_token(&mut self) { 110 | self.position += 1; 111 | } 112 | 113 | pub fn new(tokens: Vec) -> Parser { 114 | Parser { 115 | tokens, 116 | position: 0, 117 | } 118 | } 119 | 120 | pub fn parseProgram(&mut self) -> Program { 121 | let mut statements = Vec::::new(); 122 | 123 | while self.position < self.tokens.len() { 124 | statements.push(self.parseStatement()); 125 | } 126 | 127 | Program { statements } 128 | } 129 | 130 | fn parseStatement(&mut self) -> Statement { 131 | match self.current_token().unwrap().kind { 132 | // TokenType::Value => self.parseValueStatement(), 133 | // TokenType::Update => self.parseUpdateStatement(), 134 | TokenType::Return => self.parseReturnStatement(), 135 | _ => self.parseExpressionStatement(), 136 | } 137 | } 138 | 139 | fn parseReturnStatement(&mut self) -> Statement { 140 | let token = self.current_token().unwrap(); 141 | 142 | self.consume_token(); 143 | 144 | let value = self.parseExpression(Precedence::LOWEST); 145 | 146 | Statement::ReturnStatement { value, token } 147 | } 148 | 149 | fn parseExpressionStatement(&mut self) -> Statement { 150 | let token = self.current_token().unwrap(); 151 | let expression = self.parseExpression(Precedence::LOWEST); 152 | 153 | if self.current_token_is(&TokenType::Semicolon) { 154 | self.consume_token(); 155 | } 156 | 157 | Statement::ExpressionStatement { expression, token } 158 | } 159 | 160 | fn parseExpression(&mut self, precedence: Precedence) -> Box { 161 | let mut left = self.matchPrefixExpression(self.current_token().unwrap().kind); 162 | 163 | while !self.peek_token_is(&TokenType::Semicolon) 164 | && int_precedence(precedence) < int_precedence(self.peek_precedence()) 165 | { 166 | if let Some(peek_kind) = self.peek_token() { 167 | 168 | if self.checkInfixExpression(&peek_kind.kind) { 169 | self.consume_token(); 170 | return self 171 | .matchInfixExpression(peek_kind.kind, left.clone()) 172 | .unwrap(); 173 | 174 | } else { 175 | break; 176 | } 177 | } else { 178 | println!("No infix found for {:?}", self.peek_token()); 179 | return left; 180 | } 181 | } 182 | 183 | println!("Left (will be returned): {:?}", left); 184 | 185 | return left; 186 | } 187 | 188 | // Prefix/Infixes 189 | 190 | fn matchPrefixExpression(&mut self, kind: TokenType) -> Box { 191 | match kind { 192 | TokenType::Integer(_) => self.parseIntegerExpression(), 193 | TokenType::Float(_) => self.parseFloatExpression(), 194 | TokenType::String(_) => self.parseStringExpression(), 195 | TokenType::Boolean(_) => self.parseBooleanExpression(), 196 | // TokenType::Identifier(_) => self.parseIdentifierExpression(), 197 | // TokenType::Minus => self.parsePrefixExpression(), 198 | _ => panic!( 199 | "[{}] PARSER ERROR: NO PREFIX FUNCTION FOUND FOR {:?}", 200 | self.position, kind 201 | ), 202 | } 203 | } 204 | 205 | fn parseBooleanExpression(&self) -> Box { 206 | Box::new(Expression::BooleanLiteral { 207 | token: self.current_token().unwrap(), 208 | }) 209 | } 210 | 211 | fn parseIntegerExpression(&self) -> Box { 212 | // println!("Int"); 213 | Box::new(Expression::IntegerLiteral { 214 | token: self.current_token().unwrap(), 215 | }) 216 | } 217 | 218 | fn parseFloatExpression(&self) -> Box { 219 | // println!("Int"); 220 | Box::new(Expression::FloatLiteral { 221 | token: self.current_token().unwrap(), 222 | }) 223 | } 224 | 225 | fn parseStringExpression(&self) -> Box { 226 | Box::new(Expression::StringLiteral { 227 | token: self.current_token().unwrap(), 228 | }) 229 | } 230 | 231 | fn checkInfixExpression(&self, kind: &TokenType) -> bool { 232 | match *kind { 233 | TokenType::Plus => true, 234 | TokenType::Minus => true, 235 | TokenType::Asterisk => true, 236 | TokenType::Slash => true, 237 | TokenType::Equal => true, 238 | TokenType::NotEqual => true, 239 | TokenType::LT => true, 240 | TokenType::GT => true, 241 | TokenType::LTEq => true, 242 | TokenType::GTEq => true, 243 | _ => false, 244 | } 245 | } 246 | 247 | fn matchInfixExpression( 248 | &mut self, 249 | kind: TokenType, 250 | leftExpression: Box, 251 | ) -> Option> { 252 | println!("PARSING INFIX WITH LEFT: {:?}", leftExpression); 253 | match kind { 254 | TokenType::Plus => Some(self.parseInfixExpression(leftExpression)), 255 | TokenType::Minus => Some(self.parseInfixExpression(leftExpression)), 256 | TokenType::Asterisk => Some(self.parseInfixExpression(leftExpression)), 257 | TokenType::Slash => Some(self.parseInfixExpression(leftExpression)), 258 | TokenType::Equal => Some(self.parseInfixExpression(leftExpression)), 259 | TokenType::NotEqual => Some(self.parseInfixExpression(leftExpression)), 260 | TokenType::LT => Some(self.parseInfixExpression(leftExpression)), 261 | TokenType::GT => Some(self.parseInfixExpression(leftExpression)), 262 | TokenType::LTEq => Some(self.parseInfixExpression(leftExpression)), 263 | TokenType::GTEq => Some(self.parseInfixExpression(leftExpression)), 264 | _ => None, 265 | } 266 | } 267 | 268 | fn parseInfixExpression(&mut self, left: Box) -> Box { 269 | let tok = self.current_token().unwrap(); 270 | 271 | let precedence = self.current_precedence(); 272 | self.consume_token(); 273 | 274 | println!("----PARSING RIGHT, current tok: {:?}", self.current_token()); 275 | 276 | let right = self.parseExpression(precedence); 277 | self.consume_token(); 278 | 279 | Box::new(Expression::InfixExpression { 280 | token: tok, 281 | 282 | right, 283 | left, 284 | }) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Rishi Kothari 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct Position { 3 | column: usize, 4 | line: usize, 5 | } 6 | 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub enum TokenType { 9 | //Associated 10 | String(String), 11 | Integer(usize), 12 | Float(f64), 13 | Identifier(String), 14 | Boolean(bool), 15 | 16 | LBracket, 17 | RBracket, 18 | LParen, 19 | RParen, 20 | LBrace, 21 | RBrace, 22 | 23 | Ampersand, 24 | And, 25 | Equal, 26 | NotEqual, 27 | Bar, 28 | Or, 29 | Assign, 30 | Function, 31 | Bang, 32 | Update, 33 | 34 | OpenBlock, 35 | EndBlock, 36 | 37 | Plus, 38 | Minus, 39 | DoublePlus, 40 | DoubleMinus, 41 | Semicolon, 42 | Colon, 43 | Period, 44 | Asterisk, 45 | Slash, 46 | GT, 47 | LT, 48 | GTEq, 49 | LTEq, 50 | 51 | Value, 52 | If, 53 | Else, 54 | Return, 55 | } 56 | 57 | #[derive(Debug, Clone)] 58 | pub struct Token { 59 | pub position: Position, 60 | pub kind: TokenType, 61 | } 62 | 63 | #[derive(Debug, Clone)] 64 | pub enum LexerError { 65 | UndefinedError(Position), 66 | } 67 | 68 | #[derive(Debug, Clone)] 69 | pub(crate) struct Lexer<'a> { 70 | input: &'a str, 71 | literal_position: Position, 72 | position: usize, 73 | } 74 | 75 | impl<'a> Lexer<'a> { 76 | fn consume_char(&mut self) { 77 | self.literal_position.column += 1; 78 | self.position += 1; 79 | } 80 | 81 | fn get_nth_char(&self, position: Option) -> Option { 82 | if let Some(pos) = position { 83 | //Check if pos exists 84 | return self.input.chars().nth(pos); 85 | } else { 86 | return self.input.chars().nth(self.position); 87 | } 88 | } 89 | 90 | fn peek_char(&self) -> Option { 91 | self.get_nth_char(Some(self.position + 1)) 92 | } 93 | 94 | pub fn single_char_tok(&mut self, kind: TokenType) -> Option { 95 | let current_position = self.literal_position; 96 | self.consume_char(); 97 | Some(Token { 98 | kind: kind, 99 | position: current_position, 100 | }) 101 | } 102 | 103 | pub fn double_char_tok(&mut self, kind: TokenType) -> Option { 104 | let current_position = self.literal_position; 105 | self.consume_char(); 106 | self.consume_char(); 107 | Some(Token { 108 | kind: kind, 109 | position: current_position, 110 | }) 111 | } 112 | 113 | pub fn string(&mut self) -> Option { 114 | let current_position = self.literal_position; 115 | self.consume_char(); 116 | let mut owned = String::new(); 117 | while let Some(ch) = self.get_nth_char(None) { 118 | match ch { 119 | '"' => { 120 | self.consume_char(); 121 | break; 122 | } 123 | '\n' => { 124 | self.literal_position.column = 0; 125 | self.literal_position.line += 1; 126 | owned.push(ch); 127 | self.consume_char(); 128 | } 129 | _ => { 130 | owned.push(ch); 131 | self.consume_char(); 132 | } 133 | } 134 | } 135 | 136 | Some(Token { 137 | kind: TokenType::String(owned), 138 | position: current_position, 139 | }) 140 | } 141 | 142 | pub fn ident(&mut self) -> Option { 143 | let current_position = self.literal_position; 144 | let mut owned = String::new(); 145 | 146 | while let Some(ch) = self.get_nth_char(None) { 147 | match ch { 148 | 'A'..='Z' | 'a'..='z' | '_' => { 149 | owned.push(ch); 150 | self.consume_char(); 151 | } 152 | _ => break, 153 | } 154 | } 155 | 156 | let kind = match owned.as_str() { 157 | "true" => TokenType::Boolean(true), 158 | "false" => TokenType::Boolean(false), 159 | "value" => TokenType::Value, 160 | "return" => TokenType::Return, 161 | "if" => TokenType::If, 162 | "else" => TokenType::Else, 163 | "f" => TokenType::Function, 164 | "end" => TokenType::EndBlock, 165 | "update" => TokenType::Update, 166 | _ => TokenType::Identifier(owned), 167 | }; 168 | 169 | Some(Token { 170 | kind: kind, 171 | position: current_position, 172 | }) 173 | } 174 | 175 | pub fn integer(&mut self) -> Option { 176 | enum NumberTypes { 177 | Integer, 178 | Float, 179 | } 180 | let current_position = self.literal_position; 181 | let mut num = String::new(); 182 | let mut num_type = NumberTypes::Integer; 183 | 184 | while let Some(ch) = self.get_nth_char(None) { 185 | match ch { 186 | '0'..='9' => { 187 | num.push(ch); 188 | self.consume_char(); 189 | } 190 | '.' if matches!(self.peek_char(), Some('0'..='9')) => { 191 | num_type = NumberTypes::Float; 192 | num.push(ch); 193 | self.consume_char(); 194 | } 195 | _ => break, 196 | } 197 | } 198 | 199 | match num_type { 200 | NumberTypes::Integer => match num.parse::() { 201 | Ok(n) => Some(Token { 202 | kind: TokenType::Integer(n), 203 | position: current_position, 204 | }), 205 | _ => { 206 | panic!( 207 | "[{:?}] LEXER ERROR: Could not parse integer.", 208 | current_position 209 | ); 210 | } 211 | }, 212 | NumberTypes::Float => match num.parse::() { 213 | Ok(n) => Some(Token { 214 | kind: TokenType::Float(n), 215 | position: current_position, 216 | }), 217 | _ => { 218 | panic!( 219 | "[{:?}] LEXER ERROR: Could not parse float.", 220 | current_position 221 | ); 222 | } 223 | }, 224 | } 225 | } 226 | 227 | // pub fn comment(&mut self, block: bool) -> Option { 228 | // while let Some(ch) = self.get_nth_char(None) { 229 | // match ch { 230 | // "*" => { 231 | // if block { 232 | // if let Some(c) = self.peek_char() { 233 | // if c == '' 234 | // } 235 | // } 236 | // } 237 | // } 238 | // } 239 | // } 240 | 241 | pub fn new(input: &'a str) -> Lexer<'a> { 242 | Self { 243 | literal_position: Position { 244 | column: 0, 245 | line: 1, // Remember: lines aren't zero-indexed. 246 | }, 247 | position: 0, 248 | input: input, 249 | } 250 | } 251 | } 252 | 253 | impl<'a> Iterator for Lexer<'a> { 254 | type Item = Token; 255 | 256 | fn next(&mut self) -> Option { 257 | let current_character = self.get_nth_char(None); 258 | let peek_character = self.peek_char(); 259 | 260 | if current_character == None { 261 | return None; 262 | } 263 | 264 | match current_character.unwrap() { 265 | '(' => self.single_char_tok(TokenType::LParen), 266 | ')' => self.single_char_tok(TokenType::RParen), 267 | '[' => self.single_char_tok(TokenType::LBracket), 268 | ']' => self.single_char_tok(TokenType::RBracket), 269 | '{' => self.single_char_tok(TokenType::LBrace), 270 | '}' => self.single_char_tok(TokenType::RBrace), 271 | ';' => self.single_char_tok(TokenType::Semicolon), 272 | '|' => match peek_character { 273 | Some('|') => self.double_char_tok(TokenType::Or), 274 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::Bar), 275 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 276 | }, 277 | 278 | '&' => match peek_character { 279 | Some('&') => self.double_char_tok(TokenType::And), 280 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::Bar), 281 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 282 | }, 283 | 284 | '+' => match peek_character { 285 | Some('+') => self.double_char_tok(TokenType::DoublePlus), 286 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::Plus), 287 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 288 | }, 289 | 290 | '-' => match peek_character { 291 | Some('-') => self.double_char_tok(TokenType::DoubleMinus), 292 | Some('>') => self.double_char_tok(TokenType::OpenBlock), 293 | None | Some(' ') | Some('\t') | Some('\r') => { 294 | self.single_char_tok(TokenType::Minus) 295 | } 296 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 297 | }, 298 | 299 | '*' => match peek_character { 300 | Some('-') => self.double_char_tok(TokenType::DoubleMinus), 301 | Some('>') => self.double_char_tok(TokenType::OpenBlock), 302 | None | Some(' ') | Some('\t') | Some('\r') => { 303 | self.single_char_tok(TokenType::Asterisk) 304 | } 305 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 306 | }, 307 | 308 | '/' => { 309 | match peek_character { 310 | // Some('*') => self.double_char_tok(TokenType::OpenBlock), 311 | None | Some(' ') | Some('\t') | Some('\r') => { 312 | self.single_char_tok(TokenType::Slash) 313 | } 314 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 315 | } 316 | } 317 | 318 | '=' => match peek_character { 319 | Some('=') => self.double_char_tok(TokenType::Equal), 320 | None | Some(' ') | Some('\t') | Some('\r') => { 321 | self.single_char_tok(TokenType::Assign) 322 | } 323 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 324 | }, 325 | '!' => match peek_character { 326 | Some('=') => self.double_char_tok(TokenType::NotEqual), 327 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::Bang), 328 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 329 | }, 330 | '>' => match peek_character { 331 | Some('=') => self.double_char_tok(TokenType::GTEq), 332 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::GT), 333 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 334 | }, 335 | '<' => match peek_character { 336 | Some('=') => self.double_char_tok(TokenType::LTEq), 337 | None | Some(' ') | Some('\t') | Some('\r') => self.single_char_tok(TokenType::LT), 338 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 339 | }, 340 | '\n' => { 341 | self.literal_position.column = 0; 342 | self.literal_position.line += 1; 343 | self.consume_char(); 344 | self.next() 345 | } 346 | ' ' | '\t' | '\r' => { 347 | self.consume_char(); 348 | self.next() 349 | } 350 | '0'..='9' => self.integer(), 351 | 'A'..='Z' | 'a'..='z' | '_' => self.ident(), 352 | '"' => self.string(), 353 | _ => panic!("[{:?}] LEXER ERROR: Undefined token", self.literal_position), 354 | } 355 | } 356 | } 357 | --------------------------------------------------------------------------------