commit
36aeb10da9
@ -0,0 +1 @@
|
|||||||
|
/target
|
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stlc_type_inference"
|
||||||
|
version = "0.1.0"
|
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "stlc_type_inference"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
@ -0,0 +1,100 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{Ast, Constant, Ident, Type};
|
||||||
|
|
||||||
|
impl Ast {
|
||||||
|
pub fn beta_reduce(self) -> Ast {
|
||||||
|
match self {
|
||||||
|
Ast::Application(lhs, rhs) => match *lhs {
|
||||||
|
Ast::Abstraction(var, _, ast) => ast.subst(var, *rhs),
|
||||||
|
lhs => Ast::Application(Box::new(lhs), rhs),
|
||||||
|
},
|
||||||
|
t => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subst(self, var: Ident, subst: Ast) -> Ast {
|
||||||
|
match self {
|
||||||
|
Ast::Abstraction(var1, typ, ast) => {
|
||||||
|
if var != var1 {
|
||||||
|
Ast::Abstraction(var1, typ, Box::new(ast.subst(var, subst)))
|
||||||
|
} else {
|
||||||
|
Ast::Abstraction(var1, typ, ast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ast::Application(lhs, rhs) => Ast::Application(
|
||||||
|
Box::new(lhs.subst(var.clone(), subst.clone())),
|
||||||
|
Box::new(rhs.subst(var, subst)),
|
||||||
|
),
|
||||||
|
Ast::Variable(v) if v == var => subst,
|
||||||
|
Ast::Variable(v) => Ast::Variable(v),
|
||||||
|
Ast::Constant(constant) => Ast::Constant(constant),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ast {
|
||||||
|
pub fn to_de_brujin(self) -> DeBrujinAst {
|
||||||
|
self.to_de_brujin_inter(Rc::new(HashMap::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_de_brujin_inter(self, mut gamma: Rc<HashMap<String, usize>>) -> DeBrujinAst {
|
||||||
|
match self {
|
||||||
|
Ast::Abstraction(var, _, ast) => {
|
||||||
|
let gamma_ref = Rc::make_mut(&mut gamma);
|
||||||
|
gamma_ref.values_mut().for_each(|v| *v += 1);
|
||||||
|
gamma_ref.insert(var, 1);
|
||||||
|
|
||||||
|
DeBrujinAst::Abstraction(Box::new(ast.to_de_brujin_inter(gamma)))
|
||||||
|
}
|
||||||
|
Ast::Application(lhs, rhs) => DeBrujinAst::Application(
|
||||||
|
Box::new(lhs.to_de_brujin_inter(gamma.clone())),
|
||||||
|
Box::new(rhs.to_de_brujin_inter(gamma)),
|
||||||
|
),
|
||||||
|
Ast::Variable(v) => {
|
||||||
|
if let Some(c) = gamma.get(&v) {
|
||||||
|
DeBrujinAst::BoundVariable(*c)
|
||||||
|
} else {
|
||||||
|
DeBrujinAst::FreeVariable(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ast::Constant(constant) => DeBrujinAst::Constant(constant),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum DeBrujinAst {
|
||||||
|
Abstraction(Box<DeBrujinAst>), // \:1.2
|
||||||
|
Application(Box<DeBrujinAst>, Box<DeBrujinAst>), // 0 1
|
||||||
|
FreeVariable(String), // x
|
||||||
|
BoundVariable(usize), // 1
|
||||||
|
Constant(Constant), // true | false | n
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeBrujinAst {
|
||||||
|
pub fn beta_reduce(self) -> DeBrujinAst {
|
||||||
|
match self {
|
||||||
|
DeBrujinAst::Application(lhs, rhs) => match *lhs {
|
||||||
|
DeBrujinAst::Abstraction(ast) => ast.subst_bound(1, *rhs),
|
||||||
|
lhs => DeBrujinAst::Application(Box::new(lhs), rhs),
|
||||||
|
},
|
||||||
|
a => a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subst_bound(self, depth: usize, subst: DeBrujinAst) -> DeBrujinAst {
|
||||||
|
match self {
|
||||||
|
DeBrujinAst::Abstraction(ast) => ast.subst_bound(depth + 1, subst),
|
||||||
|
DeBrujinAst::Application(lhs, rhs) => DeBrujinAst::Application(
|
||||||
|
Box::new(lhs.subst_bound(depth, subst.clone())),
|
||||||
|
Box::new(rhs.subst_bound(depth, subst)),
|
||||||
|
),
|
||||||
|
DeBrujinAst::BoundVariable(n) if n == depth => subst,
|
||||||
|
a => a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
use crate::{Ast, Constant, PrimitiveType, Type, exec::DeBrujinAst as DBAst};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn beta_reduce() {
|
||||||
|
let input = Ast::Application(
|
||||||
|
Box::new(Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat),
|
||||||
|
Box::new(Ast::Application(
|
||||||
|
Box::new(Ast::Variable("x".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5))),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
Box::new(Ast::Variable("y".to_string())),
|
||||||
|
);
|
||||||
|
let reduced = input.beta_reduce();
|
||||||
|
assert_eq!(
|
||||||
|
reduced,
|
||||||
|
Ast::Application(
|
||||||
|
Box::new(Ast::Variable("y".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_de_brujin_ast_simple() {
|
||||||
|
let input = Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
PrimitiveType::Nat.into(),
|
||||||
|
Box::new(Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
PrimitiveType::Nat.into(),
|
||||||
|
Box::new(Ast::Variable("x".to_string())),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let de_brujin = input.to_de_brujin();
|
||||||
|
assert_eq!(
|
||||||
|
de_brujin,
|
||||||
|
DBAst::Abstraction(Box::new(DBAst::Abstraction(Box::new(
|
||||||
|
DBAst::BoundVariable(1)
|
||||||
|
))))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn de_brujin_beta_reduce() {
|
||||||
|
let input = Ast::Application(
|
||||||
|
Box::new(Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat),
|
||||||
|
Box::new(Ast::Application(
|
||||||
|
Box::new(Ast::Variable("x".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5))),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
Box::new(Ast::Variable("y".to_string())),
|
||||||
|
);
|
||||||
|
let dbast = input.to_de_brujin();
|
||||||
|
let reduced = dbast.beta_reduce();
|
||||||
|
assert_eq!(
|
||||||
|
reduced,
|
||||||
|
DBAst::Application(
|
||||||
|
Box::new(DBAst::FreeVariable("y".to_string())),
|
||||||
|
Box::new(DBAst::Constant(Constant::Nat(5))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
use std::{collections::HashMap, error::Error, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{Ast, Constant, Ident, PrimitiveType, Type};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InferError {
|
||||||
|
NotAFunction,
|
||||||
|
MismatchedType,
|
||||||
|
NotInContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub fn infer_type(mut gamma: Rc<HashMap<Ident, Type>>, ast: Ast) -> Result<Type, InferError> {
|
||||||
|
match ast {
|
||||||
|
Ast::Abstraction(arg, arg_type, ast) => {
|
||||||
|
Rc::make_mut(&mut gamma).insert(arg, arg_type.clone());
|
||||||
|
let out_type = infer_type(gamma, *ast)?;
|
||||||
|
Ok(Type::Arrow(Box::new(arg_type), Box::new(out_type)))
|
||||||
|
}
|
||||||
|
Ast::Application(left, right) => {
|
||||||
|
let left_type = infer_type(gamma.clone(), *left)?;
|
||||||
|
let Type::Arrow(in_type, out_type) = left_type else {
|
||||||
|
return Err(InferError::NotAFunction);
|
||||||
|
};
|
||||||
|
let right_type = infer_type(gamma, *right)?;
|
||||||
|
if *in_type != right_type {
|
||||||
|
return Err(InferError::MismatchedType);
|
||||||
|
}
|
||||||
|
Ok(*out_type)
|
||||||
|
}
|
||||||
|
Ast::Variable(var) => gamma.get(&var).cloned().ok_or(InferError::NotInContext),
|
||||||
|
Ast::Constant(Constant::Nat(_)) => Ok(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
Ast::Constant(Constant::Bool(_)) => Ok(Type::Primitive(PrimitiveType::Bool)),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{Ast, Constant, PrimitiveType, Type};
|
||||||
|
|
||||||
|
use super::infer_type;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn infer_id_type() {
|
||||||
|
let ast = Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
Type::Primitive(PrimitiveType::Nat),
|
||||||
|
Box::new(Ast::Variable("x".to_string())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let infered = infer_type(Rc::new(HashMap::new()), ast).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
infered,
|
||||||
|
Type::Arrow(
|
||||||
|
Box::new(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
Box::new(Type::Primitive(PrimitiveType::Nat))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn infer_addition_result_type() {
|
||||||
|
let ast = Ast::Application(
|
||||||
|
Box::new(Ast::Application(
|
||||||
|
Box::new(Ast::Variable("add".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5))),
|
||||||
|
)),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(7))),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut gamma = HashMap::new();
|
||||||
|
gamma.insert(
|
||||||
|
"add".to_string(),
|
||||||
|
Type::Arrow(
|
||||||
|
Box::new(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
Box::new(Type::Arrow(
|
||||||
|
Box::new(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
Box::new(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let infered = infer_type(Rc::new(gamma), ast).unwrap();
|
||||||
|
assert_eq!(infered, Type::Primitive(PrimitiveType::Nat));
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
mod exec;
|
||||||
|
mod inference;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
type Ident = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum PrimitiveType {
|
||||||
|
Nat,
|
||||||
|
Bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum Type {
|
||||||
|
Primitive(PrimitiveType), // Bool | Nat
|
||||||
|
Arrow(Box<Type>, Box<Type>), // 0 -> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PrimitiveType> for Type {
|
||||||
|
fn from(value: PrimitiveType) -> Self {
|
||||||
|
Type::Primitive(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
fn arrow<T1: Into<Type>, T2: Into<Type>>(t1: T1, t2: T2) -> Self {
|
||||||
|
Self::Arrow(Box::new(t1.into()), Box::new(t2.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum Constant {
|
||||||
|
Nat(usize),
|
||||||
|
Bool(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
enum Ast {
|
||||||
|
Abstraction(Ident, Type, Box<Ast>), // \0:1.2
|
||||||
|
Application(Box<Ast>, Box<Ast>), // 0 1
|
||||||
|
Variable(Ident), // x
|
||||||
|
Constant(Constant), // true | false | n
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Ast {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Ast::Abstraction(var, typ, ast) => write!(f, "(\\ ({var} {typ}) ({ast}))"),
|
||||||
|
Ast::Application(lhs, rhs) => write!(f, "(lhs rhs)"),
|
||||||
|
Ast::Variable(v) => write!(f, "{v}"),
|
||||||
|
Ast::Constant(constant) => write!(f, "{constant}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Type {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Type::Primitive(primitive_type) => write!(f, "{primitive_type}"),
|
||||||
|
Type::Arrow(t1, t2) => write!(f, "({t1} -> {t2})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PrimitiveType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PrimitiveType::Nat => write!(f, "Nat"),
|
||||||
|
PrimitiveType::Bool => write!(f, "Bool"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Constant {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Constant::Nat(n) => write!(f, "{n}"),
|
||||||
|
Constant::Bool(b) => write!(f, "{b}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
@ -0,0 +1,182 @@
|
|||||||
|
use sexpr::{Sexpr, parse_string};
|
||||||
|
|
||||||
|
use crate::{Ast, Constant, PrimitiveType, Type};
|
||||||
|
|
||||||
|
mod sexpr;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
UnexpectedParenClose,
|
||||||
|
UnexpectedEof,
|
||||||
|
TrailingTokens,
|
||||||
|
TrailingExpr,
|
||||||
|
ToplevelSymbol,
|
||||||
|
InvalidSymbol,
|
||||||
|
UnexpectedEndOfList,
|
||||||
|
UnknownType,
|
||||||
|
ExpectedList,
|
||||||
|
ExpectedSymbol,
|
||||||
|
ExpectedLambda,
|
||||||
|
ExpectedIdent,
|
||||||
|
ExpectedType,
|
||||||
|
NotAType,
|
||||||
|
ExpectedBody,
|
||||||
|
ExpectedArrow,
|
||||||
|
ExpectedOneOf(Vec<String>, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_symbol(ast: Option<Sexpr>) -> Result<String, ParseError> {
|
||||||
|
match ast {
|
||||||
|
Some(Sexpr::Symbol(s)) => Ok(s),
|
||||||
|
Some(l) => Err(ParseError::ExpectedSymbol),
|
||||||
|
None => Err(ParseError::ExpectedSymbol),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_ident(ast: Option<Sexpr>) -> Result<String, ParseError> {
|
||||||
|
let sym = expect_symbol(ast)?;
|
||||||
|
if is_ident(&sym) {
|
||||||
|
Ok(sym)
|
||||||
|
} else {
|
||||||
|
Err(ParseError::ExpectedIdent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_list(ast: Option<Sexpr>) -> Result<Vec<Sexpr>, ParseError> {
|
||||||
|
match ast {
|
||||||
|
Some(Sexpr::List(l)) => Ok(l),
|
||||||
|
Some(l) => Err(ParseError::ExpectedList),
|
||||||
|
None => Err(ParseError::ExpectedList),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_one_of<T>(options: &[T], item: String) -> Result<String, ParseError>
|
||||||
|
where
|
||||||
|
T: PartialEq<String> + Into<String> + Clone,
|
||||||
|
{
|
||||||
|
if options.iter().find(|e| **e == item).is_some() {
|
||||||
|
Ok(item)
|
||||||
|
} else {
|
||||||
|
Err(ParseError::ExpectedOneOf(
|
||||||
|
options
|
||||||
|
.iter()
|
||||||
|
.map(|t| Into::<String>::into(t.clone()))
|
||||||
|
.collect(),
|
||||||
|
item,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_empty<T, I: Iterator<Item = T>>(mut iter: I) -> Result<(), ParseError> {
|
||||||
|
match iter.next() {
|
||||||
|
Some(_) => Err(ParseError::TrailingTokens),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: &str) -> Result<Ast, ParseError> {
|
||||||
|
let ast = parse_string(input)?;
|
||||||
|
match ast {
|
||||||
|
Sexpr::Symbol(s) => parse_symbol(s),
|
||||||
|
list => parse_intern(list),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_intern(ast: Sexpr) -> Result<Ast, ParseError> {
|
||||||
|
match ast {
|
||||||
|
Sexpr::Symbol(s) => parse_symbol(s),
|
||||||
|
Sexpr::List(sexprs) => {
|
||||||
|
let mut iter = sexprs.into_iter();
|
||||||
|
match iter.next() {
|
||||||
|
Some(Sexpr::Symbol(sym)) => {
|
||||||
|
if sym == "\\" {
|
||||||
|
let bind = expect_list(iter.next())?;
|
||||||
|
let mut bind = bind.into_iter();
|
||||||
|
let ident = expect_ident(bind.next())?;
|
||||||
|
let typ = parse_type(&bind.next().ok_or(ParseError::ExpectedType)?)?;
|
||||||
|
expect_empty(bind)?;
|
||||||
|
let ast = Ast::Abstraction(
|
||||||
|
ident,
|
||||||
|
typ,
|
||||||
|
Box::new(parse_intern(iter.next().ok_or(ParseError::ExpectedBody)?)?),
|
||||||
|
);
|
||||||
|
expect_empty(iter)?;
|
||||||
|
Ok(ast)
|
||||||
|
} else {
|
||||||
|
let ast = parse_symbol(sym)?;
|
||||||
|
if let Some(e) = iter.next() {
|
||||||
|
let rhs = parse_intern(e)?;
|
||||||
|
expect_empty(iter)?;
|
||||||
|
Ok(Ast::Application(Box::new(ast), Box::new(rhs)))
|
||||||
|
} else {
|
||||||
|
Ok(ast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(app_left) => {
|
||||||
|
if let Some(app_right) = iter.next() {
|
||||||
|
expect_empty(iter)?;
|
||||||
|
Ok(Ast::Application(
|
||||||
|
Box::new(parse_intern(app_left)?),
|
||||||
|
// Make it back into an Sexpr so we can feed it to parse intern
|
||||||
|
Box::new(parse_intern(app_right)?),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(ParseError::UnexpectedEndOfList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(ParseError::UnexpectedEndOfList),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_symbol(s: String) -> Result<Ast, ParseError> {
|
||||||
|
if let Ok(n) = s.parse::<usize>() {
|
||||||
|
Ok(Ast::Constant(Constant::Nat(n)))
|
||||||
|
} else if let Ok(b) = s.parse::<bool>() {
|
||||||
|
Ok(Ast::Constant(Constant::Bool(b)))
|
||||||
|
} else if is_ident(&s) {
|
||||||
|
Ok(Ast::Variable(s))
|
||||||
|
} else {
|
||||||
|
Err(ParseError::InvalidSymbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ident(s: &str) -> bool {
|
||||||
|
s.starts_with(|c: char| c.is_alphabetic()) && s.chars().all(|c| c.is_alphanumeric())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type(ast: &Sexpr) -> Result<Type, ParseError> {
|
||||||
|
match ast {
|
||||||
|
Sexpr::Symbol(s) => parse_prim_type(s),
|
||||||
|
Sexpr::List(sexprs) => parse_type_list(sexprs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type_list(typ: &[Sexpr]) -> Result<Type, ParseError> {
|
||||||
|
let Some(t) = typ.get(0) else { todo!() };
|
||||||
|
|
||||||
|
if typ.get(1).is_some() {
|
||||||
|
let arr = expect_symbol(typ.get(1).cloned())?;
|
||||||
|
if arr != "->" {
|
||||||
|
return Err(ParseError::ExpectedArrow);
|
||||||
|
}
|
||||||
|
Ok(Type::Arrow(
|
||||||
|
Box::new(parse_type(t)?),
|
||||||
|
Box::new(parse_type_list(&typ[2..])?),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
parse_type(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_prim_type(typ: &str) -> Result<Type, ParseError> {
|
||||||
|
match typ {
|
||||||
|
"Bool" => Ok(Type::Primitive(PrimitiveType::Bool)),
|
||||||
|
"Nat" => Ok(Type::Primitive(PrimitiveType::Nat)),
|
||||||
|
_ => Err(ParseError::UnknownType),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
use std::iter::Peekable;
|
||||||
|
use std::ops::{Deref, RangeInclusive};
|
||||||
|
use std::usize;
|
||||||
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
|
use super::ParseError;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Token {
|
||||||
|
LeftParen,
|
||||||
|
RightParen,
|
||||||
|
Symbol(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Sexpr {
|
||||||
|
Symbol(String),
|
||||||
|
List(Vec<Sexpr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sexpr {
|
||||||
|
pub fn symbol(self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Sexpr::Symbol(item) => Some(item),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(self) -> Option<Vec<Sexpr>> {
|
||||||
|
match self {
|
||||||
|
Sexpr::List(item) => Some(item),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenize(input: &str) -> Vec<Token> {
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
// let mut chars = input.chars().peekable();
|
||||||
|
let mut chars = input.chars().peekable();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
match c {
|
||||||
|
'(' => tokens.push(Token::LeftParen),
|
||||||
|
')' => tokens.push(Token::RightParen),
|
||||||
|
_ if c.is_whitespace() => (),
|
||||||
|
_ => {
|
||||||
|
let mut symbol = c.to_string();
|
||||||
|
while let Some(c) = chars.peek() {
|
||||||
|
if c.is_whitespace() || *c == '(' || *c == ')' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
symbol.push(*c);
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
tokens.push(Token::Symbol(symbol));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expr(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Sexpr, ParseError> {
|
||||||
|
match tokens.next() {
|
||||||
|
Some(Token::LeftParen) => {
|
||||||
|
let mut list = Vec::new();
|
||||||
|
while !matches!(tokens.peek(), Some(Token::RightParen,)) {
|
||||||
|
list.push(parse_expr(tokens)?);
|
||||||
|
}
|
||||||
|
let Some(Token::RightParen) = tokens.next() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Ok(Sexpr::List(list))
|
||||||
|
}
|
||||||
|
Some(Token::RightParen) => Err(ParseError::UnexpectedParenClose),
|
||||||
|
Some(Token::Symbol(s)) => Ok(Sexpr::Symbol(s)),
|
||||||
|
None => Err(ParseError::UnexpectedEof),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(tokens: Vec<Token>) -> Result<Sexpr, ParseError> {
|
||||||
|
let mut tokens = tokens.into_iter().peekable();
|
||||||
|
let ast = parse_expr(&mut tokens)?;
|
||||||
|
if tokens.peek().is_some() {
|
||||||
|
return Err(ParseError::TrailingTokens);
|
||||||
|
};
|
||||||
|
Ok(ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_string(src: &str) -> Result<Sexpr, ParseError> {
|
||||||
|
let tokens = tokenize(src);
|
||||||
|
parse(tokens)
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
use crate::{
|
||||||
|
Ast, Constant, PrimitiveType, Type,
|
||||||
|
parse::sexpr::{Sexpr, parse_string},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{parse, parse_type};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_to_sexpr() {
|
||||||
|
let input = "((\\x:Nat.x) (5))";
|
||||||
|
let parsed = parse_string(input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed,
|
||||||
|
Sexpr::List(vec![
|
||||||
|
Sexpr::List(vec![Sexpr::Symbol("\\x:Nat.x".to_string())]),
|
||||||
|
Sexpr::List(vec![Sexpr::Symbol("5".to_string())])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_prim_type() {
|
||||||
|
let input = Sexpr::Symbol("Nat".to_string());
|
||||||
|
let parsed = parse_type(&input).unwrap();
|
||||||
|
assert_eq!(parsed, Type::Primitive(PrimitiveType::Nat))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_simpl_arr_type() {
|
||||||
|
let input = Sexpr::List(vec![
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
Sexpr::Symbol("->".to_string()),
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
]);
|
||||||
|
let parsed = parse_type(&input).unwrap();
|
||||||
|
assert_eq!(parsed, Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_apply_arr_type() {
|
||||||
|
let input = Sexpr::List(vec![
|
||||||
|
Sexpr::List(vec![
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
Sexpr::Symbol("->".to_string()),
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
]),
|
||||||
|
Sexpr::Symbol("->".to_string()),
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
Sexpr::Symbol("->".to_string()),
|
||||||
|
Sexpr::Symbol("Nat".to_string()),
|
||||||
|
]);
|
||||||
|
let parsed = parse_type(&input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed,
|
||||||
|
Type::arrow(
|
||||||
|
Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat),
|
||||||
|
Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_abstraction() {
|
||||||
|
let input = "(\\ (x (Nat -> Nat)) (x 5))";
|
||||||
|
let parsed = parse(input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed,
|
||||||
|
Ast::Abstraction(
|
||||||
|
"x".to_string(),
|
||||||
|
Type::arrow(PrimitiveType::Nat, PrimitiveType::Nat),
|
||||||
|
Box::new(Ast::Application(
|
||||||
|
Box::new(Ast::Variable("x".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5)))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_application() {
|
||||||
|
let input = "((add 5) 6)";
|
||||||
|
let parsed = parse(input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed,
|
||||||
|
Ast::Application(
|
||||||
|
Box::new(Ast::Application(
|
||||||
|
Box::new(Ast::Variable("add".to_string())),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(5)))
|
||||||
|
)),
|
||||||
|
Box::new(Ast::Constant(Constant::Nat(6)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in new issue