diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-08-03 09:18:06 -0700 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-08-03 09:18:06 -0700 |
| commit | fc2844ae88c85f082b47630b92a55bca3ff285b1 (patch) | |
| tree | 4a6cca7892459ece38bf72b61352d4fc1bb239e0 | |
| download | gock-fc2844ae88c85f082b47630b92a55bca3ff285b1.tar.gz gock-fc2844ae88c85f082b47630b92a55bca3ff285b1.zip | |
Init
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | gock.ts | 42 | ||||
| -rw-r--r-- | grammar.txt | 29 | ||||
| -rw-r--r-- | src/main.rs | 190 |
6 files changed, 275 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0471cf1 --- /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 = "gock" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..39565c0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gock" +version = "0.1.0" +edition = "2024" + +[dependencies] @@ -0,0 +1,42 @@ +type Int = "Int"; +type Bool = "Bool"; +type Atom = Int | Bool; +type SimpleType = Arrow | Atom; +type Arrow = { + left: SimpleType; + right: SimpleType; +}; + +type Identifier = string; + +type Binding = { + name: Identifier; + type?: SimpleType; +}; + +type Abstraction = { + binding: Binding; + body: ASTNode; +}; + +type ASTNode = Abstraction | Application | Identifier; + +// + +class Environment<T> { + constructor(private readonly _captures: Map<Identifier, T>, private readonly _parent?: Environment) {} + + public add(identifier: Identifier, substitution: T): Environment<T> { + const captures = new HashMap(); captures.put(identifier, substitution); + return new Environment<>(captures, this); + } + + public get(identifier: Identifier): T? { + if (this._captures.has(identifier)) return this._captures.get(identifier); + return this._parent?.get(identifier); + } +} + +//const tokenize +// const parse +// const check diff --git a/grammar.txt b/grammar.txt new file mode 100644 index 0000000..84b8123 --- /dev/null +++ b/grammar.txt @@ -0,0 +1,29 @@ +let x = 2; + y = 3; in + (x y) + +let x: (Int) = 2; + y: (Int) = 3; + f: ((Int -> Int) -> Int) = [a, b] { ((plus a) b) }; in + ((f x) y) +--- + + +LetStatement: "let" [Assignment ";"]+ "in" Body +Binding: Ident (":" TypeDecl)? +Assignment: Binding "=" Computation +Computation: Identifier | Application | Abstraction +Application: "(" Computation Computation ")" +Abstraction: "[" UncurriedArguments "]" "{" Computation "}" +UncurriedArguments: Binding ("," Binding)+ + +LetStatement: "let" Ident (":" TypeDecl)? "=" Computation in + +BaseType: Identifier +Arrow: TypeDecl "->" TypeDecl +TypeDecl: "(" BaseType | Arrow ")" + +Binding: Identifier (":" TypeDecl)? +UncurriedArguments: Identifier +Abstraction: UncurriedArguments "{" Binding "=>" Computation "}" + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7ec318a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,190 @@ +#[derive(Debug, Clone, PartialEq)] +enum TokenType { + Lparen, + Rparen, + Lbrace, + Rbrace, + Lbracket, + Rbracket, + Colon, + Identifier(String), + Eof, +} + +#[derive(Debug, Clone, Copy)] +struct Blame { + line: usize, + char: usize, +} + +#[derive(Debug)] +struct Token { + token: TokenType, + blame: Blame, +} + +struct Lexer; + +impl Lexer { + pub fn analyze(program: &str) -> Vec<Token> { + let mut tokens: Vec<Token> = program + .lines() + .enumerate() + .flat_map(|(line, text)| Self::lex_line(line, text)) + .collect(); + + tokens.push(Token { + token: TokenType::Eof, + blame: Blame { + line: tokens.len(), + char: 0, + }, + }); + tokens + } + + fn lex_line(line: usize, text: &str) -> Vec<Token> { + let mut tokens = Vec::new(); + let mut ident_start: Option<usize> = None; + + let flush = |tokens: &mut Vec<Token>, ident_start: &mut Option<usize>, end: usize| { + if let Some(start) = ident_start.take() { + if end > start { + tokens.push(Token { + token: TokenType::Identifier(text[start..end].to_string()), + blame: Blame { line, char: start }, + }); + } + } + }; + + for (i, c) in text.char_indices() { + let singlechar = match c { + '(' => Some(TokenType::Lparen), + ')' => Some(TokenType::Rparen), + '[' => Some(TokenType::Lbracket), + ']' => Some(TokenType::Rbracket), + '{' => Some(TokenType::Lbrace), + '}' => Some(TokenType::Rbrace), + ':' => Some(TokenType::Colon), + _ => None, + }; + + if c == ' ' || c == '\t' { + flush(&mut tokens, &mut ident_start, i); + continue; + } + + if let Some(tt) = singlechar { + flush(&mut tokens, &mut ident_start, i); + tokens.push(Token { + token: tt, + blame: Blame { line, char: i }, + }); + continue; + } + + if ident_start.is_none() { + ident_start = Some(i); + } + } + + flush(&mut tokens, &mut ident_start, text.len()); + tokens + } +} + +enum Atomic { + Unit, + Bool, + Int, +} + +enum SimpleType { + Arrow((Box<SimpleType>, Box<SimpleType>)), + Atom(Atomic), +} + +type Identifier = String; + +struct Binding { + name: Identifier, + arg_type: Option<SimpleType>, +} + +enum ASTNode { + Abstraction { + binding: Binding, + body: Box<ASTNode>, + }, + Application((Box<ASTNode>, Box<ASTNode>)), + Identifier(Identifier), +} + +pub enum StepResult<T> { + Terminal(T), + Continue, +} + +pub trait Steppable<T> { + fn small_step(&mut self) -> StepResult<T>; +} + +pub struct Environment<'a, T> { + parent_scope: Option<&'a Environment<'a, T>>, + capture: Identifier, + substitution: &'a T, +} + +impl<T> Environment<'_, T> { + fn add<'a>(&'a self, identifier: Identifier, substitution: &'a T) -> Environment<'a, T> { + return Environment { + parent_scope: Some(&self), + capture: identifier, + substitution: substitution, + }; + } + + fn get<'a>(&'a self, identifier: Identifier) -> Option<&'a T> { + if *identifier == *(self.capture) { + return Some(self.substitution); + } + return match self.parent_scope { + Some(scope) => (*scope).get(identifier), + None => None, + }; + } + + fn root<'a>(identifier: Identifier, substitution: &'a T) -> Environment<'a, T> { + return Environment { + parent_scope: None, + capture: identifier, + substitution: substitution, + }; + } +} + +fn main() { + println!("Hello, world!"); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_env() { + let ident = Identifier::from("ident"); + let ident2 = Identifier::from("ident2"); + let val: i32 = 1; + let val2: i32 = 2; + let val3: i32 = 3; + let env = Environment::root(ident.clone(), &val); + assert_eq!(env.get(ident.clone()), Some(&val)); + let env2 = env.add(ident2.clone(), &val2); + assert_eq!(env2.get(ident.clone()), Some(&val2)); + let env3 = env2.add(ident.clone(), &val3); + assert_eq!(env3.get(ident2.clone()), Some(&val2)); + assert_eq!(env3.get(ident.clone()), Some(&val3)); + assert_eq!(env.get(ident.clone()), Some(&val)); + } +} |
