summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Alexander Hunt <me@liz.coffee>2026-08-03 09:18:06 -0700
committerElizabeth Alexander Hunt <me@liz.coffee>2026-08-03 09:18:06 -0700
commitfc2844ae88c85f082b47630b92a55bca3ff285b1 (patch)
tree4a6cca7892459ece38bf72b61352d4fc1bb239e0
downloadgock-fc2844ae88c85f082b47630b92a55bca3ff285b1.tar.gz
gock-fc2844ae88c85f082b47630b92a55bca3ff285b1.zip
Init
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml6
-rw-r--r--gock.ts42
-rw-r--r--grammar.txt29
-rw-r--r--src/main.rs190
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]
diff --git a/gock.ts b/gock.ts
new file mode 100644
index 0000000..e8d1c53
--- /dev/null
+++ b/gock.ts
@@ -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));
+ }
+}