Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9b834e012 | |||
| 57dada7aba | |||
| ba6285e006 | |||
| c7fdeaf37a | |||
| 09737aa40b | |||
| 1eec1b06ce | |||
| 276f0b1031 | |||
| d7604ba039 | |||
| c71f68eb55 | |||
| c665e52782 | |||
| 50b473cd55 | |||
| abf00f383c | |||
| ab17ebbadc | |||
| cc281fc6ab | |||
| 1afde9ce35 | |||
| 6e1d5af134 | |||
| ee27095fb3 | |||
| 69f5035a8b | |||
| 362817e512 | |||
| 421aab3aa2 | |||
| 5eb6411d53 |
@@ -7,7 +7,7 @@ labels:
|
|||||||
- enhancement
|
- enhancement
|
||||||
---
|
---
|
||||||
# Feature Progress
|
# Feature Progress
|
||||||
<!-- Describe the steps for implementing this feature in libconlang -->
|
<!-- Describe the steps for implementing this feature -->
|
||||||
- [ ] <!-- Step 1 of implementing a feature -->
|
- [ ] <!-- Step 1 of implementing a feature -->
|
||||||
|
|
||||||
# Feature description
|
# Feature description
|
||||||
@@ -21,4 +21,4 @@ since it most closely matches what I'm currently aiming for
|
|||||||
-->
|
-->
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
13
Cargo.toml
13
Cargo.toml
@@ -1,10 +1,19 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["libconlang", "cl-repl"]
|
members = [
|
||||||
|
"cl-repl",
|
||||||
|
"cl-typeck",
|
||||||
|
"cl-interpret",
|
||||||
|
"cl-structures",
|
||||||
|
"cl-token",
|
||||||
|
"cl-ast",
|
||||||
|
"cl-parser",
|
||||||
|
"cl-lexer",
|
||||||
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
repository = "https://git.soft.fish/j/Conlang"
|
repository = "https://git.soft.fish/j/Conlang"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
authors = ["John Breaux <j@soft.fish>"]
|
authors = ["John Breaux <j@soft.fish>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
11
cl-ast/Cargo.toml
Normal file
11
cl-ast/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-ast"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
@@ -563,106 +563,6 @@ mod display {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod format {
|
|
||||||
//! Code formatting for the [AST](super::super)
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
io::{Result, Write as IoWrite},
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Trait which adds a function to [Writers](IoWrite) to turn them into [Prettifier]
|
|
||||||
pub trait Pretty {
|
|
||||||
/// Indents code according to the number of matched curly braces
|
|
||||||
fn pretty(self) -> Prettifier<'static, Self>
|
|
||||||
where Self: IoWrite + Sized;
|
|
||||||
}
|
|
||||||
impl<W: IoWrite> Pretty for W {
|
|
||||||
fn pretty(self) -> Prettifier<'static, Self>
|
|
||||||
where Self: IoWrite + Sized {
|
|
||||||
Prettifier::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Prettifier<'i, T: IoWrite> {
|
|
||||||
level: isize,
|
|
||||||
indent: &'i str,
|
|
||||||
writer: T,
|
|
||||||
}
|
|
||||||
impl<'i, W: IoWrite> Prettifier<'i, W> {
|
|
||||||
pub fn new(writer: W) -> Self {
|
|
||||||
Self { level: 0, indent: " ", writer }
|
|
||||||
}
|
|
||||||
pub fn with_indent(indent: &'i str, writer: W) -> Self {
|
|
||||||
Self { level: 0, indent, writer }
|
|
||||||
}
|
|
||||||
pub fn indent<'scope>(&'scope mut self) -> Indent<'scope, 'i, W> {
|
|
||||||
Indent::new(self)
|
|
||||||
}
|
|
||||||
fn write_indentation(&mut self) -> Result<usize> {
|
|
||||||
let Self { level, indent, writer } = self;
|
|
||||||
let mut count = 0;
|
|
||||||
for _ in 0..*level {
|
|
||||||
count += writer.write(indent.as_bytes())?;
|
|
||||||
}
|
|
||||||
Ok(count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<W: IoWrite> From<W> for Prettifier<'static, W> {
|
|
||||||
fn from(value: W) -> Self {
|
|
||||||
Self::new(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'i, W: IoWrite> IoWrite for Prettifier<'i, W> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
let mut size = 0;
|
|
||||||
for buf in buf.split_inclusive(|b| b"{}".contains(b)) {
|
|
||||||
match buf.last() {
|
|
||||||
Some(b'{') => self.level += 1,
|
|
||||||
Some(b'}') => self.level -= 1,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
for buf in buf.split_inclusive(|b| b'\n' == *b) {
|
|
||||||
size += self.writer.write(buf)?;
|
|
||||||
if let Some(b'\n') = buf.last() {
|
|
||||||
self.write_indentation()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.writer.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Indent<'scope, 'i, T: IoWrite> {
|
|
||||||
formatter: &'scope mut Prettifier<'i, T>,
|
|
||||||
}
|
|
||||||
impl<'s, 'i, T: IoWrite> Indent<'s, 'i, T> {
|
|
||||||
pub fn new(formatter: &'s mut Prettifier<'i, T>) -> Self {
|
|
||||||
formatter.level += 1;
|
|
||||||
Self { formatter }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'s, 'i, T: IoWrite> Deref for Indent<'s, 'i, T> {
|
|
||||||
type Target = Prettifier<'i, T>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'s, 'i, T: IoWrite> DerefMut for Indent<'s, 'i, T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'s, 'i, T: IoWrite> Drop for Indent<'s, 'i, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.formatter.level -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod convert {
|
mod convert {
|
||||||
//! Converts between major enums and enum variants
|
//! Converts between major enums and enum variants
|
||||||
106
cl-ast/src/format.rs
Normal file
106
cl-ast/src/format.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{Result as FmtResult, Write as FmtWrite},
|
||||||
|
io::{Result as IoResult, Write as IoWrite},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Trait which adds a function to [fmt Writers](FmtWrite) to turn them into [Prettifier]
|
||||||
|
pub trait FmtPretty: FmtWrite {
|
||||||
|
/// Indents code according to the number of matched curly braces
|
||||||
|
fn pretty(self) -> Prettifier<'static, Self>
|
||||||
|
where Self: Sized {
|
||||||
|
Prettifier::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Trait which adds a function to [io Writers](IoWrite) to turn them into [Prettifier]
|
||||||
|
pub trait IoPretty: IoWrite {
|
||||||
|
/// Indents code according to the number of matched curly braces
|
||||||
|
fn pretty(self) -> Prettifier<'static, Self>
|
||||||
|
where Self: Sized;
|
||||||
|
}
|
||||||
|
impl<W: FmtWrite> FmtPretty for W {}
|
||||||
|
impl<W: IoWrite> IoPretty for W {
|
||||||
|
fn pretty(self) -> Prettifier<'static, Self> {
|
||||||
|
Prettifier::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intercepts calls to either [std::io::Write] or [std::fmt::Write],
|
||||||
|
/// and inserts indentation between matched parentheses
|
||||||
|
pub struct Prettifier<'i, T: ?Sized> {
|
||||||
|
level: isize,
|
||||||
|
indent: &'i str,
|
||||||
|
writer: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i, W> Prettifier<'i, W> {
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
Self { level: 0, indent: " ", writer }
|
||||||
|
}
|
||||||
|
pub fn with_indent(indent: &'i str, writer: W) -> Self {
|
||||||
|
Self { level: 0, indent, writer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i, W: FmtWrite> Prettifier<'i, W> {
|
||||||
|
#[inline]
|
||||||
|
fn fmt_write_indentation(&mut self) -> FmtResult {
|
||||||
|
let Self { level, indent, writer } = self;
|
||||||
|
for _ in 0..*level {
|
||||||
|
writer.write_str(indent)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'i, W: IoWrite> Prettifier<'i, W> {
|
||||||
|
pub fn io_write_indentation(&mut self) -> IoResult<usize> {
|
||||||
|
let Self { level, indent, writer } = self;
|
||||||
|
let mut count = 0;
|
||||||
|
for _ in 0..*level {
|
||||||
|
count += writer.write(indent.as_bytes())?;
|
||||||
|
}
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i, W: FmtWrite> FmtWrite for Prettifier<'i, W> {
|
||||||
|
fn write_str(&mut self, s: &str) -> FmtResult {
|
||||||
|
for s in s.split_inclusive(['{', '}']) {
|
||||||
|
match s.as_bytes().last() {
|
||||||
|
Some(b'{') => self.level += 1,
|
||||||
|
Some(b'}') => self.level -= 1,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
for s in s.split_inclusive('\n') {
|
||||||
|
self.writer.write_str(s)?;
|
||||||
|
if let Some(b'\n') = s.as_bytes().last() {
|
||||||
|
self.fmt_write_indentation()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i, W: IoWrite> IoWrite for Prettifier<'i, W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let mut size = 0;
|
||||||
|
for buf in buf.split_inclusive(|b| b"{}".contains(b)) {
|
||||||
|
match buf.last() {
|
||||||
|
Some(b'{') => self.level += 1,
|
||||||
|
Some(b'}') => self.level -= 1,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
for buf in buf.split_inclusive(|b| b'\n' == *b) {
|
||||||
|
size += self.writer.write(buf)?;
|
||||||
|
if let Some(b'\n') = buf.last() {
|
||||||
|
self.io_write_indentation()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.writer.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//! # The Abstract Syntax Tree
|
//! # The Abstract Syntax Tree
|
||||||
//! Contains definitions of AST Nodes, to be derived by a [parser](super::parser).
|
//! Contains definitions of Conlang AST Nodes.
|
||||||
//!
|
//!
|
||||||
//! # Notable nodes
|
//! # Notable nodes
|
||||||
//! - [Item] and [ItemKind]: Top-level constructs
|
//! - [Item] and [ItemKind]: Top-level constructs
|
||||||
@@ -9,9 +9,13 @@
|
|||||||
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
|
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
|
||||||
//! - [Ty] and [TyKind]: Type qualifiers
|
//! - [Ty] and [TyKind]: Type qualifiers
|
||||||
//! - [Path]: Path expressions
|
//! - [Path]: Path expressions
|
||||||
use crate::common::*;
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
use cl_structures::span::*;
|
||||||
|
|
||||||
pub mod ast_impl;
|
pub mod ast_impl;
|
||||||
|
pub mod format;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum Mutability {
|
pub enum Mutability {
|
||||||
17
cl-interpret/Cargo.toml
Normal file
17
cl-interpret/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-interpret"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-parser = { path = "../cl-parser" }
|
||||||
16
cl-interpret/examples/fib.rs
Normal file
16
cl-interpret/examples/fib.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Calculate Fibonacci numbers
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
for num in 0..=30 {
|
||||||
|
println!("fib({num}) = {}", fib(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the classic recursive definition of fib()
|
||||||
|
fn fib(a: i64) -> i64 {
|
||||||
|
if a > 1 {
|
||||||
|
fib(a - 1) + fib(a - 2)
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
239
cl-interpret/src/builtin.rs
Normal file
239
cl-interpret/src/builtin.rs
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
//! Implementations of built-in functions
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
env::Environment,
|
||||||
|
error::{Error, IResult},
|
||||||
|
temp_type_impl::ConValue,
|
||||||
|
BuiltIn, Callable,
|
||||||
|
};
|
||||||
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
|
builtins! {
|
||||||
|
const MISC;
|
||||||
|
/// Unstable variadic print function
|
||||||
|
pub fn print<_, args> () -> IResult<ConValue> {
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
for arg in args {
|
||||||
|
write!(out, "{arg}").ok();
|
||||||
|
}
|
||||||
|
writeln!(out).ok();
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
/// Prints the [Debug](std::fmt::Debug) version of the input values
|
||||||
|
pub fn dbg<_, args> () -> IResult<ConValue> {
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
for arg in args {
|
||||||
|
writeln!(out, "{arg:?}").ok();
|
||||||
|
}
|
||||||
|
Ok(args.into())
|
||||||
|
}
|
||||||
|
/// Dumps info from the environment
|
||||||
|
pub fn dump<env, _>() -> IResult<ConValue> {
|
||||||
|
println!("{}", *env);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const BINARY;
|
||||||
|
/// Multiplication `a * b`
|
||||||
|
pub fn mul(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Division `a / b`
|
||||||
|
pub fn div(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs){
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Remainder `a % b`
|
||||||
|
pub fn rem(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Addition `a + b`
|
||||||
|
pub fn add(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a.to_string() + b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Subtraction `a - b`
|
||||||
|
pub fn sub(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift Left `a << b`
|
||||||
|
pub fn shl(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Shift Right `a >> b`
|
||||||
|
pub fn shr(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bitwise And `a & b`
|
||||||
|
pub fn and(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Bitwise Or `a | b`
|
||||||
|
pub fn or(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Bitwise Exclusive Or `a ^ b`
|
||||||
|
pub fn xor(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
Ok(match (lhs, rhs) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether `a < b`
|
||||||
|
pub fn lt(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, <)
|
||||||
|
}
|
||||||
|
/// Tests whether `a <= b`
|
||||||
|
pub fn lt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, <=)
|
||||||
|
}
|
||||||
|
/// Tests whether `a == b`
|
||||||
|
pub fn eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, ==)
|
||||||
|
}
|
||||||
|
/// Tests whether `a != b`
|
||||||
|
pub fn neq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, !=)
|
||||||
|
}
|
||||||
|
/// Tests whether `a <= b`
|
||||||
|
pub fn gt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, true, >=)
|
||||||
|
}
|
||||||
|
/// Tests whether `a < b`
|
||||||
|
pub fn gt(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
cmp!(lhs, rhs, false, >)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const RANGE;
|
||||||
|
/// Exclusive Range `a..b`
|
||||||
|
pub fn range_exc(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1)))
|
||||||
|
}
|
||||||
|
/// Inclusive Range `a..=b`
|
||||||
|
pub fn range_inc(lhs, rhs) -> IResult<ConValue> {
|
||||||
|
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(ConValue::RangeInc(lhs, rhs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builtins! {
|
||||||
|
const UNARY;
|
||||||
|
/// Negates the ConValue
|
||||||
|
pub fn neg(tail) -> IResult<ConValue> {
|
||||||
|
Ok(match tail {
|
||||||
|
ConValue::Empty => ConValue::Empty,
|
||||||
|
ConValue::Int(v) => ConValue::Int(-v),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Inverts the ConValue
|
||||||
|
pub fn not(tail) -> IResult<ConValue> {
|
||||||
|
Ok(match tail {
|
||||||
|
ConValue::Empty => ConValue::Empty,
|
||||||
|
ConValue::Int(v) => ConValue::Int(!v),
|
||||||
|
ConValue::Bool(v) => ConValue::Bool(!v),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns an argument slice into an array with the (inferred) correct number of elements
|
||||||
|
pub fn to_args<const N: usize>(args: &[ConValue]) -> IResult<&[ConValue; N]> {
|
||||||
|
args.try_into()
|
||||||
|
.map_err(|_| Error::ArgNumber { want: N, got: args.len() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns function definitions into ZSTs which implement [Callable] and [BuiltIn]
|
||||||
|
macro builtins (
|
||||||
|
$(prefix = $prefix:literal)?
|
||||||
|
const $defaults:ident $( = [$($additional_builtins:expr),*$(,)?])?;
|
||||||
|
$(
|
||||||
|
$(#[$meta:meta])*$vis:vis fn $name:ident$(<$env:tt, $args:tt>)? ( $($($arg:tt),+$(,)?)? ) $(-> $rety:ty)?
|
||||||
|
$body:block
|
||||||
|
)*
|
||||||
|
) {
|
||||||
|
/// Builtins to load when a new interpreter is created
|
||||||
|
pub const $defaults: &[&dyn BuiltIn] = &[$(&$name,)* $($additional_builtins)*];
|
||||||
|
$(
|
||||||
|
$(#[$meta])* #[allow(non_camel_case_types)] #[derive(Clone, Debug)]
|
||||||
|
/// ```rust,ignore
|
||||||
|
#[doc = stringify!(builtin! fn $name($($($arg),*)?) $(-> $rety)? $body)]
|
||||||
|
/// ```
|
||||||
|
$vis struct $name;
|
||||||
|
impl BuiltIn for $name {
|
||||||
|
fn description(&self) -> &str { concat!("builtin ", stringify!($name), stringify!(($($($arg),*)?) )) }
|
||||||
|
}
|
||||||
|
impl Callable for $name {
|
||||||
|
#[allow(unused)]
|
||||||
|
fn call(&self, env: &mut Environment, args: &[ConValue]) $(-> $rety)? {
|
||||||
|
// println!("{}", stringify!($name), );
|
||||||
|
$(let $env = env;
|
||||||
|
let $args = args;)?
|
||||||
|
$(let [$($arg),*] = to_args(args)?;)?
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
fn name(&self) -> &str { stringify!($name) }
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Templates comparison functions for [ConValue]
|
||||||
|
macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) {
|
||||||
|
match ($a, $b) {
|
||||||
|
(ConValue::Empty, ConValue::Empty) => Ok(ConValue::Bool($empty)),
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::Char(a), ConValue::Char(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(a $op b)),
|
||||||
|
_ => Err(Error::TypeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
461
cl-interpret/src/interpret.rs
Normal file
461
cl-interpret/src/interpret.rs
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
//! A work-in-progress tree walk interpreter for Conlang
|
||||||
|
//!
|
||||||
|
//! Currently, major parts of the interpreter are not yet implemented, and major parts will never be
|
||||||
|
//! implemented in its current form. Namely, since no [ConValue] has a stable location, it's
|
||||||
|
//! meaningless to get a pointer to one, and would be undefined behavior to dereference a pointer to
|
||||||
|
//! one in any situation.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use cl_ast::*;
|
||||||
|
/// A work-in-progress tree walk interpreter for Conlang
|
||||||
|
pub trait Interpret {
|
||||||
|
/// Interprets this thing in the given [`Environment`].
|
||||||
|
///
|
||||||
|
/// Everything returns a value!™
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for File {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
for item in &self.items {
|
||||||
|
item.interpret(env)?;
|
||||||
|
}
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Item {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
match &self.kind {
|
||||||
|
ItemKind::Alias(item) => item.interpret(env),
|
||||||
|
ItemKind::Const(item) => item.interpret(env),
|
||||||
|
ItemKind::Static(item) => item.interpret(env),
|
||||||
|
ItemKind::Module(item) => item.interpret(env),
|
||||||
|
ItemKind::Function(item) => item.interpret(env),
|
||||||
|
ItemKind::Struct(item) => item.interpret(env),
|
||||||
|
ItemKind::Enum(item) => item.interpret(env),
|
||||||
|
ItemKind::Impl(item) => item.interpret(env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Alias {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("Interpret type alias in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Const {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("interpret const in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Static {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("interpret static in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Module {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
// TODO: Enter this module's namespace
|
||||||
|
match &self.kind {
|
||||||
|
ModuleKind::Inline(file) => file.interpret(env),
|
||||||
|
ModuleKind::Outline => todo!("Load and parse external files"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Function {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
// register the function in the current environment
|
||||||
|
env.insert_fn(self);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Struct {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("Interpret structs in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Enum {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("Interpret enums in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Impl {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("Enter a struct's namespace and insert function definitions into it in {env}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Stmt {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { extents: _, kind, semi } = self;
|
||||||
|
let out = match kind {
|
||||||
|
StmtKind::Empty => ConValue::Empty,
|
||||||
|
StmtKind::Local(stmt) => stmt.interpret(env)?,
|
||||||
|
StmtKind::Item(stmt) => stmt.interpret(env)?,
|
||||||
|
StmtKind::Expr(stmt) => stmt.interpret(env)?,
|
||||||
|
};
|
||||||
|
Ok(match semi {
|
||||||
|
Semi::Terminated => ConValue::Empty,
|
||||||
|
Semi::Unterminated => out,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Let {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Let { mutable: _, name: Identifier(name), ty: _, init } = self;
|
||||||
|
let init = init.as_ref().map(|i| i.interpret(env)).transpose()?;
|
||||||
|
env.insert(name, init);
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Expr {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { extents: _, kind } = self;
|
||||||
|
match kind {
|
||||||
|
ExprKind::Assign(v) => v.interpret(env),
|
||||||
|
ExprKind::Binary(v) => v.interpret(env),
|
||||||
|
ExprKind::Unary(v) => v.interpret(env),
|
||||||
|
ExprKind::Member(v) => v.interpret(env),
|
||||||
|
ExprKind::Call(v) => v.interpret(env),
|
||||||
|
ExprKind::Index(v) => v.interpret(env),
|
||||||
|
ExprKind::Path(v) => v.interpret(env),
|
||||||
|
ExprKind::Literal(v) => v.interpret(env),
|
||||||
|
ExprKind::Array(v) => v.interpret(env),
|
||||||
|
ExprKind::ArrayRep(v) => v.interpret(env),
|
||||||
|
ExprKind::AddrOf(v) => v.interpret(env),
|
||||||
|
ExprKind::Block(v) => v.interpret(env),
|
||||||
|
ExprKind::Empty => Ok(ConValue::Empty),
|
||||||
|
ExprKind::Group(v) => v.interpret(env),
|
||||||
|
ExprKind::Tuple(v) => v.interpret(env),
|
||||||
|
ExprKind::While(v) => v.interpret(env),
|
||||||
|
ExprKind::If(v) => v.interpret(env),
|
||||||
|
ExprKind::For(v) => v.interpret(env),
|
||||||
|
ExprKind::Break(v) => v.interpret(env),
|
||||||
|
ExprKind::Return(v) => v.interpret(env),
|
||||||
|
ExprKind::Continue(v) => v.interpret(env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Assign {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Assign { head, op, tail } = self;
|
||||||
|
// Resolve the head pattern
|
||||||
|
let head = match &head.kind {
|
||||||
|
ExprKind::Path(Path { parts, .. }) if parts.len() == 1 => {
|
||||||
|
match parts.last().expect("parts should not be empty") {
|
||||||
|
PathPart::SuperKw => Err(Error::NotAssignable(head.extents.head))?,
|
||||||
|
PathPart::SelfKw => todo!("Assignment to `self`"),
|
||||||
|
PathPart::Ident(Identifier(s)) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExprKind::Member(_) => todo!("Member access assignment"),
|
||||||
|
ExprKind::Call(_) => todo!("Assignment to the result of a function call?"),
|
||||||
|
ExprKind::Index(_) => todo!("Assignment to an index operation"),
|
||||||
|
ExprKind::Path(_) => todo!("Path expression resolution (IMPORTANT)"),
|
||||||
|
ExprKind::Empty | ExprKind::Group(_) | ExprKind::Tuple(_) => {
|
||||||
|
todo!("Pattern Destructuring?")
|
||||||
|
}
|
||||||
|
_ => Err(Error::NotAssignable(head.extents.head))?,
|
||||||
|
};
|
||||||
|
// Get the initializer and the tail
|
||||||
|
let init = tail.interpret(env)?;
|
||||||
|
let target = env.get_mut(head)?;
|
||||||
|
|
||||||
|
if let AssignKind::Plain = op {
|
||||||
|
use std::mem::discriminant as variant;
|
||||||
|
// runtime typecheck
|
||||||
|
match target {
|
||||||
|
Some(value) if variant(value) == variant(&init) => {
|
||||||
|
*value = init;
|
||||||
|
}
|
||||||
|
value @ None => *value = Some(init),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}
|
||||||
|
return Ok(ConValue::Empty);
|
||||||
|
}
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Err(Error::NotInitialized(head.into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
match op {
|
||||||
|
AssignKind::Add => target.add_assign(init)?,
|
||||||
|
AssignKind::Sub => target.sub_assign(init)?,
|
||||||
|
AssignKind::Mul => target.mul_assign(init)?,
|
||||||
|
AssignKind::Div => target.div_assign(init)?,
|
||||||
|
AssignKind::Rem => target.rem_assign(init)?,
|
||||||
|
AssignKind::And => target.bitand_assign(init)?,
|
||||||
|
AssignKind::Or => target.bitor_assign(init)?,
|
||||||
|
AssignKind::Xor => target.bitxor_assign(init)?,
|
||||||
|
AssignKind::Shl => target.shl_assign(init)?,
|
||||||
|
AssignKind::Shr => target.shr_assign(init)?,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Ok(ConValue::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Binary {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Binary { head, tail } = self;
|
||||||
|
let mut head = head.interpret(env)?;
|
||||||
|
// Short-circuiting ops
|
||||||
|
for (op, tail) in tail {
|
||||||
|
match op {
|
||||||
|
BinaryKind::LogAnd => {
|
||||||
|
if head.truthy()? {
|
||||||
|
head = tail.interpret(env)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Ok(head); // Short circuiting
|
||||||
|
}
|
||||||
|
BinaryKind::LogOr => {
|
||||||
|
if !head.truthy()? {
|
||||||
|
head = tail.interpret(env)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Ok(head); // Short circuiting
|
||||||
|
}
|
||||||
|
BinaryKind::LogXor => {
|
||||||
|
head = ConValue::Bool(head.truthy()? ^ tail.interpret(env)?.truthy()?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let tail = tail.interpret(env)?;
|
||||||
|
head = match op {
|
||||||
|
BinaryKind::Mul => env.call("mul", &[head, tail]),
|
||||||
|
BinaryKind::Div => env.call("div", &[head, tail]),
|
||||||
|
BinaryKind::Rem => env.call("rem", &[head, tail]),
|
||||||
|
BinaryKind::Add => env.call("add", &[head, tail]),
|
||||||
|
BinaryKind::Sub => env.call("sub", &[head, tail]),
|
||||||
|
BinaryKind::Shl => env.call("shl", &[head, tail]),
|
||||||
|
BinaryKind::Shr => env.call("shr", &[head, tail]),
|
||||||
|
BinaryKind::BitAnd => env.call("and", &[head, tail]),
|
||||||
|
BinaryKind::BitOr => env.call("or", &[head, tail]),
|
||||||
|
BinaryKind::BitXor => env.call("xor", &[head, tail]),
|
||||||
|
BinaryKind::RangeExc => env.call("range_exc", &[head, tail]),
|
||||||
|
BinaryKind::RangeInc => env.call("range_inc", &[head, tail]),
|
||||||
|
BinaryKind::Lt => env.call("lt", &[head, tail]),
|
||||||
|
BinaryKind::LtEq => env.call("lt_eq", &[head, tail]),
|
||||||
|
BinaryKind::Equal => env.call("eq", &[head, tail]),
|
||||||
|
BinaryKind::NotEq => env.call("neq", &[head, tail]),
|
||||||
|
BinaryKind::GtEq => env.call("gt_eq", &[head, tail]),
|
||||||
|
BinaryKind::Gt => env.call("gt", &[head, tail]),
|
||||||
|
BinaryKind::Dot => todo!("search within a type's namespace!"),
|
||||||
|
_ => Ok(head),
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Unary {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Unary { tail, ops } = self;
|
||||||
|
let mut operand = tail.interpret(env)?;
|
||||||
|
|
||||||
|
for op in ops.iter().rev() {
|
||||||
|
operand = match op {
|
||||||
|
UnaryKind::Deref => env.call("deref", &[operand])?,
|
||||||
|
UnaryKind::Neg => env.call("neg", &[operand])?,
|
||||||
|
UnaryKind::Not => env.call("not", &[operand])?,
|
||||||
|
UnaryKind::At => {
|
||||||
|
println!("{operand}");
|
||||||
|
operand
|
||||||
|
}
|
||||||
|
UnaryKind::Tilde => unimplemented!("Tilde operator"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(operand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Member {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
todo!("Interpret member accesses in {env}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Call {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { callee, args } = self;
|
||||||
|
// evaluate the callee
|
||||||
|
let mut callee = callee.interpret(env)?;
|
||||||
|
for args in args {
|
||||||
|
let ConValue::Tuple(args) = args.interpret(env)? else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
callee = callee.call(env, &args)?;
|
||||||
|
}
|
||||||
|
Ok(callee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Index {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { head, indices } = self;
|
||||||
|
let mut head = head.interpret(env)?;
|
||||||
|
for indices in indices {
|
||||||
|
let Indices { exprs } = indices;
|
||||||
|
for index in exprs {
|
||||||
|
head = head.index(&index.interpret(env)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Path {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { absolute: _, parts } = self;
|
||||||
|
|
||||||
|
if parts.len() == 1 {
|
||||||
|
match parts.last().expect("parts should not be empty") {
|
||||||
|
PathPart::SuperKw | PathPart::SelfKw => todo!("Path navigation"),
|
||||||
|
PathPart::Ident(Identifier(s)) => env.get(s).cloned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("Path navigation!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Literal {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
Ok(match self {
|
||||||
|
Literal::String(value) => ConValue::from(value.as_str()),
|
||||||
|
Literal::Char(value) => ConValue::Char(*value),
|
||||||
|
Literal::Bool(value) => ConValue::Bool(*value),
|
||||||
|
// Literal::Float(value) => todo!("Float values in interpreter: {value:?}"),
|
||||||
|
Literal::Int(value) => ConValue::Int(*value as _),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Array {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { values } = self;
|
||||||
|
let mut out = vec![];
|
||||||
|
for expr in values {
|
||||||
|
out.push(expr.interpret(env)?)
|
||||||
|
}
|
||||||
|
Ok(ConValue::Array(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for ArrayRep {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { value, repeat } = self;
|
||||||
|
let repeat = match repeat.interpret(env)? {
|
||||||
|
ConValue::Int(v) => v,
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
};
|
||||||
|
let value = value.interpret(env)?;
|
||||||
|
Ok(ConValue::Array(vec![value; repeat as usize]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for AddrOf {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { count: _, mutable: _, expr } = self;
|
||||||
|
// this is stupid
|
||||||
|
todo!("Create reference\nfrom expr: {expr}\nin env:\n{env}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Block {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { stmts } = self;
|
||||||
|
let mut env = env.frame("block");
|
||||||
|
let mut out = ConValue::Empty;
|
||||||
|
for stmt in stmts {
|
||||||
|
out = stmt.interpret(&mut env)?;
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Group {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { expr } = self;
|
||||||
|
expr.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Tuple {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { exprs } = self;
|
||||||
|
Ok(ConValue::Tuple(exprs.iter().try_fold(
|
||||||
|
vec![],
|
||||||
|
|mut out, element| {
|
||||||
|
out.push(element.interpret(env)?);
|
||||||
|
Ok(out)
|
||||||
|
},
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for While {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
while cond.interpret(env)?.truthy()? {
|
||||||
|
match pass.interpret(env) {
|
||||||
|
Err(Error::Break(value)) => return Ok(value),
|
||||||
|
Err(Error::Continue) => continue,
|
||||||
|
e => e?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fail.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for If {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { cond, pass, fail } = self;
|
||||||
|
if cond.interpret(env)?.truthy()? {
|
||||||
|
pass.interpret(env)
|
||||||
|
} else {
|
||||||
|
fail.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for For {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { bind: Identifier(name), cond, pass, fail } = self;
|
||||||
|
// TODO: A better iterator model
|
||||||
|
let bounds = match cond.interpret(env)? {
|
||||||
|
ConValue::RangeExc(a, b) => a..=b,
|
||||||
|
ConValue::RangeInc(a, b) => a..=b,
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let mut env = env.frame("loop variable");
|
||||||
|
for loop_var in bounds {
|
||||||
|
env.insert(name, Some(loop_var.into()));
|
||||||
|
match pass.interpret(&mut env) {
|
||||||
|
Err(Error::Break(value)) => return Ok(value),
|
||||||
|
Err(Error::Continue) => continue,
|
||||||
|
result => result?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail.interpret(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Else {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
match body {
|
||||||
|
Some(body) => body.interpret(env),
|
||||||
|
None => Ok(ConValue::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Continue {
|
||||||
|
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
Err(Error::Continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Return {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
Err(Error::Return(
|
||||||
|
body.as_ref()
|
||||||
|
.map(|body| body.interpret(env))
|
||||||
|
.unwrap_or(Ok(ConValue::Empty))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Interpret for Break {
|
||||||
|
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
|
let Self { body } = self;
|
||||||
|
Err(Error::Break(
|
||||||
|
body.as_ref()
|
||||||
|
.map(|body| body.interpret(env))
|
||||||
|
.unwrap_or(Ok(ConValue::Empty))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
615
cl-interpret/src/lib.rs
Normal file
615
cl-interpret/src/lib.rs
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
//! Walks a Conlang AST, interpreting it as a program.
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
use env::Environment;
|
||||||
|
use error::{Error, IResult};
|
||||||
|
use interpret::Interpret;
|
||||||
|
use temp_type_impl::ConValue;
|
||||||
|
|
||||||
|
/// Callable types can be called from within a Conlang program
|
||||||
|
pub trait Callable: std::fmt::Debug {
|
||||||
|
/// Calls this [Callable] in the provided [Environment], with [ConValue] args \
|
||||||
|
/// The Callable is responsible for checking the argument count and validating types
|
||||||
|
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
|
||||||
|
/// Returns the common name of this identifier.
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [BuiltIn]s are [Callable]s with bespoke definitions
|
||||||
|
pub trait BuiltIn: std::fmt::Debug + Callable {
|
||||||
|
fn description(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod temp_type_impl {
|
||||||
|
//! Temporary implementations of Conlang values
|
||||||
|
//!
|
||||||
|
//! The most permanent fix is a temporary one.
|
||||||
|
use super::{
|
||||||
|
error::{Error, IResult},
|
||||||
|
function::Function,
|
||||||
|
BuiltIn, Callable, Environment,
|
||||||
|
};
|
||||||
|
use std::ops::*;
|
||||||
|
|
||||||
|
type Integer = isize;
|
||||||
|
|
||||||
|
/// A Conlang value
|
||||||
|
///
|
||||||
|
/// This is a hack to work around the fact that Conlang doesn't
|
||||||
|
/// have a functioning type system yet :(
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub enum ConValue {
|
||||||
|
/// The empty/unit `()` type
|
||||||
|
#[default]
|
||||||
|
Empty,
|
||||||
|
/// An integer
|
||||||
|
Int(Integer),
|
||||||
|
/// A boolean
|
||||||
|
Bool(bool),
|
||||||
|
/// A unicode character
|
||||||
|
Char(char),
|
||||||
|
/// A string
|
||||||
|
String(String),
|
||||||
|
/// An Array
|
||||||
|
Array(Vec<ConValue>),
|
||||||
|
/// A tuple
|
||||||
|
Tuple(Vec<ConValue>),
|
||||||
|
/// An exclusive range
|
||||||
|
RangeExc(Integer, Integer),
|
||||||
|
/// An inclusive range
|
||||||
|
RangeInc(Integer, Integer),
|
||||||
|
/// A callable thing
|
||||||
|
Function(Function),
|
||||||
|
/// A built-in function
|
||||||
|
BuiltIn(&'static dyn BuiltIn),
|
||||||
|
}
|
||||||
|
impl ConValue {
|
||||||
|
/// Gets whether the current value is true or false
|
||||||
|
pub fn truthy(&self) -> IResult<bool> {
|
||||||
|
match self {
|
||||||
|
ConValue::Bool(v) => Ok(*v),
|
||||||
|
_ => Err(Error::TypeError)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn range_exc(self, other: Self) -> IResult<Self> {
|
||||||
|
let (Self::Int(a), Self::Int(b)) = (self, other) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(Self::RangeExc(a, b.saturating_sub(1)))
|
||||||
|
}
|
||||||
|
pub fn range_inc(self, other: Self) -> IResult<Self> {
|
||||||
|
let (Self::Int(a), Self::Int(b)) = (self, other) else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
Ok(Self::RangeInc(a, b))
|
||||||
|
}
|
||||||
|
pub fn index(&self, index: &Self) -> IResult<ConValue> {
|
||||||
|
let Self::Int(index) = index else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
let Self::Array(arr) = self else {
|
||||||
|
Err(Error::TypeError)?
|
||||||
|
};
|
||||||
|
arr.get(*index as usize)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(Error::OobIndex(*index as usize, arr.len()))
|
||||||
|
}
|
||||||
|
cmp! {
|
||||||
|
lt: false, <;
|
||||||
|
lt_eq: true, <=;
|
||||||
|
eq: true, ==;
|
||||||
|
neq: false, !=;
|
||||||
|
gt_eq: true, >=;
|
||||||
|
gt: false, >;
|
||||||
|
}
|
||||||
|
assign! {
|
||||||
|
add_assign: +;
|
||||||
|
bitand_assign: &;
|
||||||
|
bitor_assign: |;
|
||||||
|
bitxor_assign: ^;
|
||||||
|
div_assign: /;
|
||||||
|
mul_assign: *;
|
||||||
|
rem_assign: %;
|
||||||
|
shl_assign: <<;
|
||||||
|
shr_assign: >>;
|
||||||
|
sub_assign: -;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for ConValue {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ConValue::Function(func) => func.name(),
|
||||||
|
ConValue::BuiltIn(func) => func.name(),
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
match self {
|
||||||
|
Self::Function(func) => func.call(interpreter, args),
|
||||||
|
Self::BuiltIn(func) => func.call(interpreter, args),
|
||||||
|
_ => Err(Error::NotCallable(self.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Templates comparison functions for [ConValue]
|
||||||
|
macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$(
|
||||||
|
/// TODO: Remove when functions are implemented:
|
||||||
|
/// Desugar into function calls
|
||||||
|
pub fn $fn(&self, other: &Self) -> IResult<Self> {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Empty, Self::Empty) => Ok(Self::Bool($empty)),
|
||||||
|
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
(Self::String(a), Self::String(b)) => Ok(Self::Bool(a $op b)),
|
||||||
|
_ => Err(Error::TypeError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*}
|
||||||
|
macro assign($( $fn: ident: $op: tt );*$(;)?) {$(
|
||||||
|
pub fn $fn(&mut self, other: Self) -> IResult<()> {
|
||||||
|
*self = (std::mem::take(self) $op other)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)*}
|
||||||
|
/// Implements [From] for an enum with 1-tuple variants
|
||||||
|
macro from ($($T:ty => $v:expr),*$(,)?) {
|
||||||
|
$(impl From<$T> for ConValue {
|
||||||
|
fn from(value: $T) -> Self { $v(value.into()) }
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
from! {
|
||||||
|
Integer => ConValue::Int,
|
||||||
|
bool => ConValue::Bool,
|
||||||
|
char => ConValue::Char,
|
||||||
|
&str => ConValue::String,
|
||||||
|
String => ConValue::String,
|
||||||
|
Function => ConValue::Function,
|
||||||
|
Vec<ConValue> => ConValue::Tuple,
|
||||||
|
&'static dyn BuiltIn => ConValue::BuiltIn,
|
||||||
|
}
|
||||||
|
impl From<()> for ConValue {
|
||||||
|
fn from(_: ()) -> Self {
|
||||||
|
Self::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&[ConValue]> for ConValue {
|
||||||
|
fn from(value: &[ConValue]) -> Self {
|
||||||
|
match value.len() {
|
||||||
|
0 => Self::Empty,
|
||||||
|
1 => value[0].clone(),
|
||||||
|
_ => Self::Tuple(value.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements binary [std::ops] traits for [ConValue]
|
||||||
|
///
|
||||||
|
/// TODO: Desugar operators into function calls
|
||||||
|
macro ops($($trait:ty: $fn:ident = [$($match:tt)*])*) {
|
||||||
|
$(impl $trait for ConValue {
|
||||||
|
type Output = IResult<Self>;
|
||||||
|
/// TODO: Desugar operators into function calls
|
||||||
|
fn $fn(self, rhs: Self) -> Self::Output {Ok(match (self, rhs) {$($match)*})}
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
ops! {
|
||||||
|
Add: add = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||||
|
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a + &b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitAnd: bitand = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitOr: bitor = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
BitXor: bitxor = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
|
||||||
|
(ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Div: div = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Mul: mul = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Rem: rem = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Shl: shl = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Shr: shr = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
Sub: sub = [
|
||||||
|
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||||
|
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b),
|
||||||
|
_ => Err(Error::TypeError)?
|
||||||
|
]
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ConValue {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConValue::Empty => "Empty".fmt(f),
|
||||||
|
ConValue::Int(v) => v.fmt(f),
|
||||||
|
ConValue::Bool(v) => v.fmt(f),
|
||||||
|
ConValue::Char(v) => v.fmt(f),
|
||||||
|
ConValue::String(v) => v.fmt(f),
|
||||||
|
ConValue::Array(array) => {
|
||||||
|
'['.fmt(f)?;
|
||||||
|
for (idx, element) in array.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
", ".fmt(f)?
|
||||||
|
}
|
||||||
|
element.fmt(f)?
|
||||||
|
}
|
||||||
|
']'.fmt(f)
|
||||||
|
}
|
||||||
|
ConValue::RangeExc(a, b) => write!(f, "{a}..{}", b + 1),
|
||||||
|
ConValue::RangeInc(a, b) => write!(f, "{a}..={b}"),
|
||||||
|
ConValue::Tuple(tuple) => {
|
||||||
|
'('.fmt(f)?;
|
||||||
|
for (idx, element) in tuple.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
", ".fmt(f)?
|
||||||
|
}
|
||||||
|
element.fmt(f)?
|
||||||
|
}
|
||||||
|
')'.fmt(f)
|
||||||
|
}
|
||||||
|
ConValue::Function(func) => {
|
||||||
|
use cl_ast::format::*;
|
||||||
|
use std::fmt::Write;
|
||||||
|
write!(f.pretty(), "{}", func.decl())
|
||||||
|
}
|
||||||
|
ConValue::BuiltIn(func) => {
|
||||||
|
write!(f, "{}", func.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod interpret;
|
||||||
|
|
||||||
|
pub mod function {
|
||||||
|
//! Represents a block of code which lives inside the Interpreter
|
||||||
|
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
|
||||||
|
use cl_ast::{Function as FnDecl, Identifier, Param};
|
||||||
|
/// Represents a block of code which persists inside the Interpreter
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Function {
|
||||||
|
/// Stores the contents of the function declaration
|
||||||
|
decl: Box<FnDecl>,
|
||||||
|
// /// Stores the enclosing scope of the function
|
||||||
|
// env: Box<Environment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
pub fn new(decl: &FnDecl) -> Self {
|
||||||
|
Self { decl: decl.clone().into() }
|
||||||
|
}
|
||||||
|
pub fn decl(&self) -> &FnDecl {
|
||||||
|
&self.decl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Callable for Function {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
let FnDecl { name: Identifier(ref name), .. } = *self.decl;
|
||||||
|
name
|
||||||
|
}
|
||||||
|
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
let FnDecl { name: Identifier(name), args: declargs, body, rety: _ } = &*self.decl;
|
||||||
|
// Check arg mapping
|
||||||
|
if args.len() != declargs.len() {
|
||||||
|
return Err(Error::ArgNumber { want: declargs.len(), got: args.len() });
|
||||||
|
}
|
||||||
|
let Some(body) = body else {
|
||||||
|
return Err(Error::NotDefined(name.into()));
|
||||||
|
};
|
||||||
|
// TODO: completely refactor data storage
|
||||||
|
let mut frame = env.frame("fn args");
|
||||||
|
for (Param { mutability: _, name: Identifier(name), ty: _ }, value) in
|
||||||
|
declargs.iter().zip(args)
|
||||||
|
{
|
||||||
|
frame.insert(name, Some(value.clone()));
|
||||||
|
}
|
||||||
|
match body.interpret(&mut frame) {
|
||||||
|
Err(Error::Return(value)) => Ok(value),
|
||||||
|
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
|
||||||
|
result => result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod builtin;
|
||||||
|
|
||||||
|
pub mod env {
|
||||||
|
//! Lexical and non-lexical scoping for variables
|
||||||
|
use super::{
|
||||||
|
builtin::{BINARY, MISC, RANGE, UNARY},
|
||||||
|
error::{Error, IResult},
|
||||||
|
function::Function,
|
||||||
|
temp_type_impl::ConValue,
|
||||||
|
BuiltIn, Callable, Interpret,
|
||||||
|
};
|
||||||
|
use cl_ast::{Function as FnDecl, Identifier};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::Display,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Implements a nested lexical scope
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Environment {
|
||||||
|
frames: Vec<(HashMap<String, Option<ConValue>>, &'static str)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Environment {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for (frame, name) in self.frames.iter().rev() {
|
||||||
|
writeln!(f, "--- {name} ---")?;
|
||||||
|
for (var, val) in frame {
|
||||||
|
write!(f, "{var}: ")?;
|
||||||
|
match val {
|
||||||
|
Some(value) => writeln!(f, "\t{value}"),
|
||||||
|
None => writeln!(f, "<undefined>"),
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for Environment {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
frames: vec![
|
||||||
|
(to_hashmap(RANGE), "range ops"),
|
||||||
|
(to_hashmap(UNARY), "unary ops"),
|
||||||
|
(to_hashmap(BINARY), "binary ops"),
|
||||||
|
(to_hashmap(MISC), "builtins"),
|
||||||
|
(HashMap::new(), "globals"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<String, Option<ConValue>> {
|
||||||
|
from.iter()
|
||||||
|
.map(|&v| (v.name().into(), Some(v.into())))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
/// Creates an [Environment] with no [builtins](super::builtin)
|
||||||
|
pub fn no_builtins(name: &'static str) -> Self {
|
||||||
|
Self { frames: vec![(Default::default(), name)] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
|
||||||
|
node.interpret(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a function inside the interpreter's scope,
|
||||||
|
/// and returns the result
|
||||||
|
pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult<ConValue> {
|
||||||
|
// FIXME: Clone to satisfy the borrow checker
|
||||||
|
let function = self.get(name)?.clone();
|
||||||
|
function.call(self, args)
|
||||||
|
}
|
||||||
|
/// Enters a nested scope, returning a [`Frame`] stack-guard.
|
||||||
|
///
|
||||||
|
/// [`Frame`] implements Deref/DerefMut for [`Environment`].
|
||||||
|
pub fn frame(&mut self, name: &'static str) -> Frame {
|
||||||
|
Frame::new(self, name)
|
||||||
|
}
|
||||||
|
/// Resolves a variable mutably.
|
||||||
|
///
|
||||||
|
/// Returns a mutable reference to the variable's record, if it exists.
|
||||||
|
pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option<ConValue>> {
|
||||||
|
for (frame, _) in self.frames.iter_mut().rev() {
|
||||||
|
if let Some(var) = frame.get_mut(id) {
|
||||||
|
return Ok(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::NotDefined(id.into()))
|
||||||
|
}
|
||||||
|
/// Resolves a variable immutably.
|
||||||
|
///
|
||||||
|
/// Returns a reference to the variable's contents, if it is defined and initialized.
|
||||||
|
pub fn get(&self, id: &str) -> IResult<&ConValue> {
|
||||||
|
for (frame, _) in self.frames.iter().rev() {
|
||||||
|
match frame.get(id) {
|
||||||
|
Some(Some(var)) => return Ok(var),
|
||||||
|
Some(None) => return Err(Error::NotInitialized(id.into())),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::NotDefined(id.into()))
|
||||||
|
}
|
||||||
|
/// Inserts a new [ConValue] into this [Environment]
|
||||||
|
pub fn insert(&mut self, id: &str, value: Option<ConValue>) {
|
||||||
|
if let Some((frame, _)) = self.frames.last_mut() {
|
||||||
|
frame.insert(id.into(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A convenience function for registering a [FnDecl] as a [Function]
|
||||||
|
pub fn insert_fn(&mut self, decl: &FnDecl) {
|
||||||
|
let FnDecl { name: Identifier(name), .. } = decl;
|
||||||
|
let (name, function) = (name.clone(), Some(Function::new(decl).into()));
|
||||||
|
if let Some((frame, _)) = self.frames.last_mut() {
|
||||||
|
frame.insert(name, function);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions which aid in the implementation of [`Frame`]
|
||||||
|
impl Environment {
|
||||||
|
/// Enters a scope, creating a new namespace for variables
|
||||||
|
fn enter(&mut self, name: &'static str) -> &mut Self {
|
||||||
|
self.frames.push((Default::default(), name));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exits the scope, destroying all local variables and
|
||||||
|
/// returning the outer scope, if there is one
|
||||||
|
fn exit(&mut self) -> &mut Self {
|
||||||
|
if self.frames.len() > 2 {
|
||||||
|
self.frames.pop();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a stack frame
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Frame<'scope> {
|
||||||
|
scope: &'scope mut Environment,
|
||||||
|
}
|
||||||
|
impl<'scope> Frame<'scope> {
|
||||||
|
fn new(scope: &'scope mut Environment, name: &'static str) -> Self {
|
||||||
|
Self { scope: scope.enter(name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> Deref for Frame<'scope> {
|
||||||
|
type Target = Environment;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> DerefMut for Frame<'scope> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'scope> Drop for Frame<'scope> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.scope.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod error {
|
||||||
|
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
|
||||||
|
|
||||||
|
use super::temp_type_impl::ConValue;
|
||||||
|
use cl_structures::span::Loc;
|
||||||
|
|
||||||
|
pub type IResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
/// Represents any error thrown by the [Environment](super::Environment)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Propagate a Return value
|
||||||
|
Return(ConValue),
|
||||||
|
/// Propagate a Break value
|
||||||
|
Break(ConValue),
|
||||||
|
/// Break propagated across function bounds
|
||||||
|
BadBreak(ConValue),
|
||||||
|
/// Continue to the next iteration of a loop
|
||||||
|
Continue,
|
||||||
|
/// Underflowed the stack
|
||||||
|
StackUnderflow,
|
||||||
|
/// Exited the last scope
|
||||||
|
ScopeExit,
|
||||||
|
/// Type incompatibility
|
||||||
|
// TODO: store the type information in this error
|
||||||
|
TypeError,
|
||||||
|
/// In clause of For loop didn't yield a Range
|
||||||
|
NotIterable,
|
||||||
|
/// A value at this [location](struct@Loc) can't be indexed
|
||||||
|
NotIndexable(Loc),
|
||||||
|
/// An array index went out of bounds
|
||||||
|
OobIndex(usize, usize),
|
||||||
|
/// An expression at this [location](struct@Loc)ation is not assignable
|
||||||
|
NotAssignable(Loc),
|
||||||
|
/// A name was not defined in scope before being used
|
||||||
|
NotDefined(String),
|
||||||
|
/// A name was defined but not initialized
|
||||||
|
NotInitialized(String),
|
||||||
|
/// A value was called, but is not callable
|
||||||
|
NotCallable(ConValue),
|
||||||
|
/// A function was called with the wrong number of arguments
|
||||||
|
ArgNumber {
|
||||||
|
want: usize,
|
||||||
|
got: usize,
|
||||||
|
},
|
||||||
|
NullPointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Return(value) => write!(f, "return {value}"),
|
||||||
|
Error::Break(value) => write!(f, "break {value}"),
|
||||||
|
Error::BadBreak(value) => write!(f, "rogue break: {value}"),
|
||||||
|
Error::Continue => "continue".fmt(f),
|
||||||
|
Error::StackUnderflow => "Stack underflow".fmt(f),
|
||||||
|
Error::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f),
|
||||||
|
Error::TypeError => "Incompatible types".fmt(f),
|
||||||
|
Error::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f),
|
||||||
|
Error::NotIndexable(location) => {
|
||||||
|
write!(f, "{location} expression cannot be indexed")
|
||||||
|
}
|
||||||
|
Error::OobIndex(idx, len) => {
|
||||||
|
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
|
||||||
|
}
|
||||||
|
Error::NotAssignable(location) => {
|
||||||
|
write!(f, "{location} expression is not assignable")
|
||||||
|
}
|
||||||
|
Error::NotDefined(value) => {
|
||||||
|
write!(f, "{value} not bound. Did you mean `let {value};`?")
|
||||||
|
}
|
||||||
|
Error::NotInitialized(value) => {
|
||||||
|
write!(f, "{value} bound, but not initialized")
|
||||||
|
}
|
||||||
|
Error::NotCallable(value) => {
|
||||||
|
write!(f, "{value} is not callable.")
|
||||||
|
}
|
||||||
|
Error::ArgNumber { want, got } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Expected {want} argument{}, got {got}",
|
||||||
|
if *want == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Error::NullPointer => {
|
||||||
|
write!(f, "Attempted to dereference a null pointer?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use crate::{
|
use crate::{env::Environment, temp_type_impl::ConValue, Interpret};
|
||||||
ast::*,
|
use cl_ast::*;
|
||||||
interpreter::{env::Environment, temp_type_impl::ConValue, Interpret},
|
use cl_parser::Parser;
|
||||||
lexer::Lexer,
|
use cl_lexer::Lexer;
|
||||||
parser::Parser,
|
|
||||||
};
|
|
||||||
pub use macros::*;
|
pub use macros::*;
|
||||||
|
|
||||||
mod macros {
|
mod macros {
|
||||||
@@ -49,7 +47,7 @@ mod macros {
|
|||||||
//! env_eq!(env.x, 10); // like assert_eq! for Environments
|
//! env_eq!(env.x, 10); // like assert_eq! for Environments
|
||||||
//! ```
|
//! ```
|
||||||
#![allow(unused_macros)]
|
#![allow(unused_macros)]
|
||||||
use crate::interpreter::IResult;
|
use crate::IResult;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -212,7 +210,7 @@ mod fn_declarations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod operators {
|
mod operators {
|
||||||
use crate::ast::Tuple;
|
use cl_ast::Tuple;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
13
cl-lexer/Cargo.toml
Normal file
13
cl-lexer/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-lexer"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
unicode-xid = "0.2.4"
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
//! Converts a text file into tokens
|
//! Converts a text file into tokens
|
||||||
use crate::token::preamble::*;
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
use cl_structures::span::Loc;
|
||||||
|
use cl_token::*;
|
||||||
use std::{
|
use std::{
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
};
|
};
|
||||||
use unicode_xid::UnicodeXID;
|
use unicode_xid::UnicodeXID;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub mod lexer_iter {
|
pub mod lexer_iter {
|
||||||
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
|
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
|
||||||
use super::{
|
use super::{
|
||||||
@@ -445,6 +451,12 @@ impl<'t> Lexer<'t> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'t> From<&Lexer<'t>> for Loc {
|
||||||
|
fn from(value: &Lexer<'t>) -> Self {
|
||||||
|
Loc(value.line(), value.col())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use error::{Error, LResult, Reason};
|
use error::{Error, LResult, Reason};
|
||||||
pub mod error {
|
pub mod error {
|
||||||
//! [Error] type for the [Lexer](super::Lexer)
|
//! [Error] type for the [Lexer](super::Lexer)
|
||||||
167
cl-lexer/src/tests.rs
Normal file
167
cl-lexer/src/tests.rs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
use crate::Lexer;
|
||||||
|
use cl_token::*;
|
||||||
|
|
||||||
|
macro test_lexer_output_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
||||||
|
#[test]
|
||||||
|
fn $f() {$(
|
||||||
|
assert_eq!(
|
||||||
|
Lexer::new($test)
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.unwrap().ty())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
dbg!($expect)
|
||||||
|
);
|
||||||
|
)*}
|
||||||
|
)*}
|
||||||
|
|
||||||
|
macro test_lexer_data_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
||||||
|
#[test]
|
||||||
|
fn $f() {$(
|
||||||
|
assert_eq!(
|
||||||
|
Lexer::new($test)
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.unwrap().into_data())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
dbg!($expect)
|
||||||
|
);
|
||||||
|
)*}
|
||||||
|
)*}
|
||||||
|
|
||||||
|
/// Convert an `[ expr, ... ]` into a `[ *, ... ]`
|
||||||
|
macro td ($($id:expr),*) {
|
||||||
|
[$($id.into()),*]
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ident {
|
||||||
|
use super::*;
|
||||||
|
macro ident ($($id:literal),*) {
|
||||||
|
[$(Data::Identifier($id.into())),*]
|
||||||
|
}
|
||||||
|
test_lexer_data_type! {
|
||||||
|
underscore { "_ _" => ident!["_", "_"] }
|
||||||
|
unicode { "_ε ε_" => ident!["_ε", "ε_"] }
|
||||||
|
many_underscore { "____________________________________" =>
|
||||||
|
ident!["____________________________________"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod keyword {
|
||||||
|
use super::*;
|
||||||
|
macro kw($($k:ident),*) {
|
||||||
|
[ $(Type::Keyword(Keyword::$k),)* ]
|
||||||
|
}
|
||||||
|
test_lexer_output_type! {
|
||||||
|
kw_break { "break break" => kw![Break, Break] }
|
||||||
|
kw_continue { "continue continue" => kw![Continue, Continue] }
|
||||||
|
kw_else { "else else" => kw![Else, Else] }
|
||||||
|
kw_false { "false false" => kw![False, False] }
|
||||||
|
kw_for { "for for" => kw![For, For] }
|
||||||
|
kw_fn { "fn fn" => kw![Fn, Fn] }
|
||||||
|
kw_if { "if if" => kw![If, If] }
|
||||||
|
kw_in { "in in" => kw![In, In] }
|
||||||
|
kw_let { "let let" => kw![Let, Let] }
|
||||||
|
kw_return { "return return" => kw![Return, Return] }
|
||||||
|
kw_true { "true true" => kw![True, True] }
|
||||||
|
kw_while { "while while" => kw![While, While] }
|
||||||
|
keywords { "break continue else false for fn if in let return true while" =>
|
||||||
|
kw![Break, Continue, Else, False, For, Fn, If, In, Let, Return, True, While] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod integer {
|
||||||
|
use super::*;
|
||||||
|
test_lexer_data_type! {
|
||||||
|
hex {
|
||||||
|
"0x0 0x1 0x15 0x2100 0x8000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
dec {
|
||||||
|
"0d0 0d1 0d21 0d8448 0d32768" =>
|
||||||
|
td![0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
oct {
|
||||||
|
"0o0 0o1 0o25 0o20400 0o100000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
bin {
|
||||||
|
"0b0 0b1 0b10101 0b10000100000000 0b1000000000000000" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
baseless {
|
||||||
|
"0 1 21 8448 32768" =>
|
||||||
|
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod string {
|
||||||
|
use super::*;
|
||||||
|
test_lexer_data_type! {
|
||||||
|
empty_string {
|
||||||
|
"\"\"" =>
|
||||||
|
td![String::from("")]
|
||||||
|
}
|
||||||
|
unicode_string {
|
||||||
|
"\"I 💙 🦈!\"" =>
|
||||||
|
td![String::from("I 💙 🦈!")]
|
||||||
|
}
|
||||||
|
escape_string {
|
||||||
|
" \"This is a shark: \\u{1f988}\" " =>
|
||||||
|
td![String::from("This is a shark: 🦈")]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod punct {
|
||||||
|
use super::*;
|
||||||
|
test_lexer_output_type! {
|
||||||
|
l_curly { "{ {" => [ Type::LCurly, Type::LCurly ] }
|
||||||
|
r_curly { "} }" => [ Type::RCurly, Type::RCurly ] }
|
||||||
|
l_brack { "[ [" => [ Type::LBrack, Type::LBrack ] }
|
||||||
|
r_brack { "] ]" => [ Type::RBrack, Type::RBrack ] }
|
||||||
|
l_paren { "( (" => [ Type::LParen, Type::LParen ] }
|
||||||
|
r_paren { ") )" => [ Type::RParen, Type::RParen ] }
|
||||||
|
amp { "& &" => [ Type::Amp, Type::Amp ] }
|
||||||
|
amp_amp { "&& &&" => [ Type::AmpAmp, Type::AmpAmp ] }
|
||||||
|
amp_eq { "&= &=" => [ Type::AmpEq, Type::AmpEq ] }
|
||||||
|
arrow { "-> ->" => [ Type::Arrow, Type::Arrow] }
|
||||||
|
at { "@ @" => [ Type::At, Type::At] }
|
||||||
|
backslash { "\\ \\" => [ Type::Backslash, Type::Backslash] }
|
||||||
|
bang { "! !" => [ Type::Bang, Type::Bang] }
|
||||||
|
bangbang { "!! !!" => [ Type::BangBang, Type::BangBang] }
|
||||||
|
bangeq { "!= !=" => [ Type::BangEq, Type::BangEq] }
|
||||||
|
bar { "| |" => [ Type::Bar, Type::Bar] }
|
||||||
|
barbar { "|| ||" => [ Type::BarBar, Type::BarBar] }
|
||||||
|
bareq { "|= |=" => [ Type::BarEq, Type::BarEq] }
|
||||||
|
colon { ": :" => [ Type::Colon, Type::Colon] }
|
||||||
|
comma { ", ," => [ Type::Comma, Type::Comma] }
|
||||||
|
dot { ". ." => [ Type::Dot, Type::Dot] }
|
||||||
|
dotdot { ".. .." => [ Type::DotDot, Type::DotDot] }
|
||||||
|
dotdoteq { "..= ..=" => [ Type::DotDotEq, Type::DotDotEq] }
|
||||||
|
eq { "= =" => [ Type::Eq, Type::Eq] }
|
||||||
|
eqeq { "== ==" => [ Type::EqEq, Type::EqEq] }
|
||||||
|
fatarrow { "=> =>" => [ Type::FatArrow, Type::FatArrow] }
|
||||||
|
grave { "` `" => [ Type::Grave, Type::Grave] }
|
||||||
|
gt { "> >" => [ Type::Gt, Type::Gt] }
|
||||||
|
gteq { ">= >=" => [ Type::GtEq, Type::GtEq] }
|
||||||
|
gtgt { ">> >>" => [ Type::GtGt, Type::GtGt] }
|
||||||
|
gtgteq { ">>= >>=" => [ Type::GtGtEq, Type::GtGtEq] }
|
||||||
|
hash { "# #" => [ Type::Hash, Type::Hash] }
|
||||||
|
lt { "< <" => [ Type::Lt, Type::Lt] }
|
||||||
|
lteq { "<= <=" => [ Type::LtEq, Type::LtEq] }
|
||||||
|
ltlt { "<< <<" => [ Type::LtLt, Type::LtLt] }
|
||||||
|
ltlteq { "<<= <<=" => [ Type::LtLtEq, Type::LtLtEq] }
|
||||||
|
minus { "- -" => [ Type::Minus, Type::Minus] }
|
||||||
|
minuseq { "-= -=" => [ Type::MinusEq, Type::MinusEq] }
|
||||||
|
plus { "+ +" => [ Type::Plus, Type::Plus] }
|
||||||
|
pluseq { "+= +=" => [ Type::PlusEq, Type::PlusEq] }
|
||||||
|
question { "? ?" => [ Type::Question, Type::Question] }
|
||||||
|
rem { "% %" => [ Type::Rem, Type::Rem] }
|
||||||
|
remeq { "%= %=" => [ Type::RemEq, Type::RemEq] }
|
||||||
|
semi { "; ;" => [ Type::Semi, Type::Semi] }
|
||||||
|
slash { "/ /" => [ Type::Slash, Type::Slash] }
|
||||||
|
slasheq { "/= /=" => [ Type::SlashEq, Type::SlashEq] }
|
||||||
|
star { "* *" => [ Type::Star, Type::Star] }
|
||||||
|
stareq { "*= *=" => [ Type::StarEq, Type::StarEq] }
|
||||||
|
tilde { "~ ~" => [ Type::Tilde, Type::Tilde] }
|
||||||
|
xor { "^ ^" => [ Type::Xor, Type::Xor] }
|
||||||
|
xoreq { "^= ^=" => [ Type::XorEq, Type::XorEq] }
|
||||||
|
xorxor { "^^ ^^" => [ Type::XorXor, Type::XorXor] }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
cl-parser/Cargo.toml
Normal file
14
cl-parser/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-parser"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
209
cl-parser/src/error.rs
Normal file
209
cl-parser/src/error.rs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use cl_lexer::error::{Error as LexError, Reason};
|
||||||
|
use std::fmt::Display;
|
||||||
|
pub type PResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
/// Contains information about [Parser] errors
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Error {
|
||||||
|
pub reason: ErrorKind,
|
||||||
|
pub while_parsing: Parsing,
|
||||||
|
pub loc: Loc,
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
/// Represents the reason for parse failure
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
Lexical(LexError),
|
||||||
|
EndOfInput,
|
||||||
|
UnmatchedParentheses,
|
||||||
|
UnmatchedCurlyBraces,
|
||||||
|
UnmatchedSquareBrackets,
|
||||||
|
Unexpected(Type),
|
||||||
|
Expected {
|
||||||
|
want: Type,
|
||||||
|
got: Type,
|
||||||
|
},
|
||||||
|
/// No rules matched
|
||||||
|
Nothing,
|
||||||
|
/// Indicates unfinished code
|
||||||
|
Todo,
|
||||||
|
}
|
||||||
|
impl From<LexError> for ErrorKind {
|
||||||
|
fn from(value: LexError) -> Self {
|
||||||
|
match value.reason() {
|
||||||
|
Reason::EndOfFile => Self::EndOfInput,
|
||||||
|
_ => Self::Lexical(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compactly represents the stage of parsing an [Error] originated in
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Parsing {
|
||||||
|
File,
|
||||||
|
|
||||||
|
Attrs,
|
||||||
|
Meta,
|
||||||
|
|
||||||
|
Item,
|
||||||
|
Visibility,
|
||||||
|
Mutability,
|
||||||
|
ItemKind,
|
||||||
|
Alias,
|
||||||
|
Const,
|
||||||
|
Static,
|
||||||
|
Module,
|
||||||
|
ModuleKind,
|
||||||
|
Function,
|
||||||
|
Param,
|
||||||
|
Struct,
|
||||||
|
StructKind,
|
||||||
|
StructMember,
|
||||||
|
Enum,
|
||||||
|
EnumKind,
|
||||||
|
Variant,
|
||||||
|
VariantKind,
|
||||||
|
Impl,
|
||||||
|
|
||||||
|
Ty,
|
||||||
|
TyKind,
|
||||||
|
TyTuple,
|
||||||
|
TyRef,
|
||||||
|
TyFn,
|
||||||
|
|
||||||
|
Stmt,
|
||||||
|
StmtKind,
|
||||||
|
Let,
|
||||||
|
|
||||||
|
Expr,
|
||||||
|
ExprKind,
|
||||||
|
Assign,
|
||||||
|
AssignKind,
|
||||||
|
Binary,
|
||||||
|
BinaryKind,
|
||||||
|
Unary,
|
||||||
|
UnaryKind,
|
||||||
|
Index,
|
||||||
|
Call,
|
||||||
|
Member,
|
||||||
|
PathExpr,
|
||||||
|
PathPart,
|
||||||
|
Identifier,
|
||||||
|
Literal,
|
||||||
|
Array,
|
||||||
|
ArrayRep,
|
||||||
|
AddrOf,
|
||||||
|
Block,
|
||||||
|
Group,
|
||||||
|
Tuple,
|
||||||
|
While,
|
||||||
|
If,
|
||||||
|
For,
|
||||||
|
Else,
|
||||||
|
Break,
|
||||||
|
Return,
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self { reason, while_parsing, loc } = self;
|
||||||
|
match reason {
|
||||||
|
// TODO entries are debug-printed
|
||||||
|
ErrorKind::Todo => write!(f, "{loc} {reason} {while_parsing:?}"),
|
||||||
|
// lexical errors print their own higher-resolution loc info
|
||||||
|
ErrorKind::Lexical(e) => write!(f, "{e} (while parsing {while_parsing})"),
|
||||||
|
_ => write!(f, "{loc} {reason} while parsing {while_parsing}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for ErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Lexical(e) => e.fmt(f),
|
||||||
|
ErrorKind::EndOfInput => write!(f, "End of input"),
|
||||||
|
ErrorKind::UnmatchedParentheses => write!(f, "Unmatched parentheses"),
|
||||||
|
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
|
||||||
|
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
|
||||||
|
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
|
||||||
|
ErrorKind::Expected { want: e, got: g } => {
|
||||||
|
write!(f, "Expected {e}, but got {g}")
|
||||||
|
}
|
||||||
|
ErrorKind::Nothing => write!(f, "Nothing found"),
|
||||||
|
ErrorKind::Todo => write!(f, "TODO:"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Display for Parsing {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Parsing::File => "a file",
|
||||||
|
|
||||||
|
Parsing::Attrs => "an attribute-set",
|
||||||
|
Parsing::Meta => "an attribute",
|
||||||
|
|
||||||
|
Parsing::Item => "an item",
|
||||||
|
Parsing::Visibility => "a visibility qualifier",
|
||||||
|
Parsing::Mutability => "a mutability qualifier",
|
||||||
|
Parsing::ItemKind => "an item",
|
||||||
|
Parsing::Alias => "a type alias",
|
||||||
|
Parsing::Const => "a const item",
|
||||||
|
Parsing::Static => "a static variable",
|
||||||
|
Parsing::Module => "a module",
|
||||||
|
Parsing::ModuleKind => "a module",
|
||||||
|
Parsing::Function => "a function",
|
||||||
|
Parsing::Param => "a function parameter",
|
||||||
|
Parsing::Struct => "a struct",
|
||||||
|
Parsing::StructKind => "a struct",
|
||||||
|
Parsing::StructMember => "a struct member",
|
||||||
|
Parsing::Enum => "an enum",
|
||||||
|
Parsing::EnumKind => "an enum",
|
||||||
|
Parsing::Variant => "an enum variant",
|
||||||
|
Parsing::VariantKind => "an enum variant",
|
||||||
|
Parsing::Impl => "an impl block",
|
||||||
|
|
||||||
|
Parsing::Ty => "a type",
|
||||||
|
Parsing::TyKind => "a type",
|
||||||
|
Parsing::TyTuple => "a tuple of types",
|
||||||
|
Parsing::TyRef => "a reference type",
|
||||||
|
Parsing::TyFn => "a function pointer type",
|
||||||
|
|
||||||
|
Parsing::Stmt => "a statement",
|
||||||
|
Parsing::StmtKind => "a statement",
|
||||||
|
Parsing::Let => "a local variable declaration",
|
||||||
|
|
||||||
|
Parsing::Expr => "an expression",
|
||||||
|
Parsing::ExprKind => "an expression",
|
||||||
|
Parsing::Assign => "an assignment",
|
||||||
|
Parsing::AssignKind => "an assignment operator",
|
||||||
|
Parsing::Binary => "a binary expression",
|
||||||
|
Parsing::BinaryKind => "a binary operator",
|
||||||
|
Parsing::Unary => "a unary expression",
|
||||||
|
Parsing::UnaryKind => "a unary operator",
|
||||||
|
Parsing::Index => "an indexing expression",
|
||||||
|
Parsing::Call => "a call expression",
|
||||||
|
Parsing::Member => "a member access expression",
|
||||||
|
Parsing::PathExpr => "a path",
|
||||||
|
Parsing::PathPart => "a path component",
|
||||||
|
Parsing::Identifier => "an identifier",
|
||||||
|
Parsing::Literal => "a literal",
|
||||||
|
Parsing::Array => "an array",
|
||||||
|
Parsing::ArrayRep => "an array of form [k;N]",
|
||||||
|
Parsing::AddrOf => "a borrow op",
|
||||||
|
Parsing::Block => "a block",
|
||||||
|
Parsing::Group => "a grouped expression",
|
||||||
|
Parsing::Tuple => "a tuple",
|
||||||
|
Parsing::While => "a while expression",
|
||||||
|
Parsing::If => "an if expression",
|
||||||
|
Parsing::For => "a for expression",
|
||||||
|
Parsing::Else => "an else block",
|
||||||
|
Parsing::Break => "a break expression",
|
||||||
|
Parsing::Return => "a return expression",
|
||||||
|
Parsing::Continue => "a continue expression",
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
cl-parser/src/lib.rs
Normal file
16
cl-parser/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//! Parses [tokens](cl_token::token) into an [AST](cl_ast)
|
||||||
|
//!
|
||||||
|
//! For the full grammar, see [grammar.ebnf][1]
|
||||||
|
//!
|
||||||
|
//! [1]: https://git.soft.fish/j/Conlang/src/branch/main/grammar.ebnf
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
pub use parser::Parser;
|
||||||
|
|
||||||
|
use cl_structures::span::*;
|
||||||
|
use cl_token::*;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub mod parser;
|
||||||
@@ -1,237 +1,13 @@
|
|||||||
//! Parses [tokens](super::token) into an [AST](super::ast)
|
use super::*;
|
||||||
//!
|
use crate::error::{
|
||||||
//! For the full grammar, see [grammar.ebnf][1]
|
|
||||||
//!
|
|
||||||
//! [1]: https://git.soft.fish/j/Conlang/src/branch/main/grammar.ebnf
|
|
||||||
|
|
||||||
use self::error::{
|
|
||||||
Error,
|
Error,
|
||||||
ErrorKind::{self, *},
|
ErrorKind::{self, *},
|
||||||
PResult, Parsing,
|
PResult, Parsing,
|
||||||
};
|
};
|
||||||
use crate::{
|
use cl_ast::*;
|
||||||
ast::*,
|
use cl_lexer::Lexer;
|
||||||
common::*,
|
|
||||||
lexer::{error::Error as LexError, Lexer},
|
|
||||||
token::{
|
|
||||||
token_data::Data,
|
|
||||||
token_type::{Keyword, Type},
|
|
||||||
Token,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod error {
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
pub type PResult<T> = Result<T, Error>;
|
|
||||||
|
|
||||||
/// Contains information about [Parser] errors
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Error {
|
|
||||||
pub reason: ErrorKind,
|
|
||||||
pub while_parsing: Parsing,
|
|
||||||
pub loc: Loc,
|
|
||||||
}
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
/// Represents the reason for parse failure
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ErrorKind {
|
|
||||||
Lexical(LexError),
|
|
||||||
EndOfInput,
|
|
||||||
UnmatchedParentheses,
|
|
||||||
UnmatchedCurlyBraces,
|
|
||||||
UnmatchedSquareBrackets,
|
|
||||||
Unexpected(Type),
|
|
||||||
Expected {
|
|
||||||
want: Type,
|
|
||||||
got: Type,
|
|
||||||
},
|
|
||||||
/// No rules matched
|
|
||||||
Nothing,
|
|
||||||
/// Indicates unfinished code
|
|
||||||
Todo,
|
|
||||||
}
|
|
||||||
impl From<LexError> for ErrorKind {
|
|
||||||
fn from(value: LexError) -> Self {
|
|
||||||
use crate::lexer::error::Reason;
|
|
||||||
match value.reason() {
|
|
||||||
Reason::EndOfFile => Self::EndOfInput,
|
|
||||||
_ => Self::Lexical(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compactly represents the stage of parsing an [Error] originated in
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Parsing {
|
|
||||||
File,
|
|
||||||
|
|
||||||
Attrs,
|
|
||||||
Meta,
|
|
||||||
|
|
||||||
Item,
|
|
||||||
Visibility,
|
|
||||||
Mutability,
|
|
||||||
ItemKind,
|
|
||||||
Alias,
|
|
||||||
Const,
|
|
||||||
Static,
|
|
||||||
Module,
|
|
||||||
ModuleKind,
|
|
||||||
Function,
|
|
||||||
Param,
|
|
||||||
Struct,
|
|
||||||
StructKind,
|
|
||||||
StructMember,
|
|
||||||
Enum,
|
|
||||||
EnumKind,
|
|
||||||
Variant,
|
|
||||||
VariantKind,
|
|
||||||
Impl,
|
|
||||||
|
|
||||||
Ty,
|
|
||||||
TyKind,
|
|
||||||
TyTuple,
|
|
||||||
TyRef,
|
|
||||||
TyFn,
|
|
||||||
|
|
||||||
Stmt,
|
|
||||||
StmtKind,
|
|
||||||
Let,
|
|
||||||
|
|
||||||
Expr,
|
|
||||||
ExprKind,
|
|
||||||
Assign,
|
|
||||||
AssignKind,
|
|
||||||
Binary,
|
|
||||||
BinaryKind,
|
|
||||||
Unary,
|
|
||||||
UnaryKind,
|
|
||||||
Index,
|
|
||||||
Call,
|
|
||||||
Member,
|
|
||||||
PathExpr,
|
|
||||||
PathPart,
|
|
||||||
Identifier,
|
|
||||||
Literal,
|
|
||||||
Array,
|
|
||||||
ArrayRep,
|
|
||||||
AddrOf,
|
|
||||||
Block,
|
|
||||||
Group,
|
|
||||||
Tuple,
|
|
||||||
While,
|
|
||||||
If,
|
|
||||||
For,
|
|
||||||
Else,
|
|
||||||
Break,
|
|
||||||
Return,
|
|
||||||
Continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let Self { reason, while_parsing, loc } = self;
|
|
||||||
match reason {
|
|
||||||
// TODO entries are debug-printed
|
|
||||||
Todo => write!(f, "{loc} {reason} {while_parsing:?}"),
|
|
||||||
// lexical errors print their own higher-resolution loc info
|
|
||||||
Lexical(e) => write!(f, "{e} (while parsing {while_parsing})"),
|
|
||||||
_ => write!(f, "{loc} {reason} while parsing {while_parsing}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for ErrorKind {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
ErrorKind::Lexical(e) => e.fmt(f),
|
|
||||||
ErrorKind::EndOfInput => write!(f, "End of input"),
|
|
||||||
ErrorKind::UnmatchedParentheses => write!(f, "Unmatched parentheses"),
|
|
||||||
ErrorKind::UnmatchedCurlyBraces => write!(f, "Unmatched curly braces"),
|
|
||||||
ErrorKind::UnmatchedSquareBrackets => write!(f, "Unmatched square brackets"),
|
|
||||||
ErrorKind::Unexpected(t) => write!(f, "Encountered unexpected token `{t}`"),
|
|
||||||
ErrorKind::Expected { want: e, got: g } => {
|
|
||||||
write!(f, "Expected {e}, but got {g}")
|
|
||||||
}
|
|
||||||
ErrorKind::Nothing => write!(f, "Nothing found"),
|
|
||||||
ErrorKind::Todo => write!(f, "TODO:"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for Parsing {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Parsing::File => "a file",
|
|
||||||
|
|
||||||
Parsing::Attrs => "an attribute-set",
|
|
||||||
Parsing::Meta => "an attribute",
|
|
||||||
|
|
||||||
Parsing::Item => "an item",
|
|
||||||
Parsing::Visibility => "a visibility qualifier",
|
|
||||||
Parsing::Mutability => "a mutability qualifier",
|
|
||||||
Parsing::ItemKind => "an item",
|
|
||||||
Parsing::Alias => "a type alias",
|
|
||||||
Parsing::Const => "a const item",
|
|
||||||
Parsing::Static => "a static variable",
|
|
||||||
Parsing::Module => "a module",
|
|
||||||
Parsing::ModuleKind => "a module",
|
|
||||||
Parsing::Function => "a function",
|
|
||||||
Parsing::Param => "a function parameter",
|
|
||||||
Parsing::Struct => "a struct",
|
|
||||||
Parsing::StructKind => "a struct",
|
|
||||||
Parsing::StructMember => "a struct member",
|
|
||||||
Parsing::Enum => "an enum",
|
|
||||||
Parsing::EnumKind => "an enum",
|
|
||||||
Parsing::Variant => "an enum variant",
|
|
||||||
Parsing::VariantKind => "an enum variant",
|
|
||||||
Parsing::Impl => "an impl block",
|
|
||||||
|
|
||||||
Parsing::Ty => "a type",
|
|
||||||
Parsing::TyKind => "a type",
|
|
||||||
Parsing::TyTuple => "a tuple of types",
|
|
||||||
Parsing::TyRef => "a reference type",
|
|
||||||
Parsing::TyFn => "a function pointer type",
|
|
||||||
|
|
||||||
Parsing::Stmt => "a statement",
|
|
||||||
Parsing::StmtKind => "a statement",
|
|
||||||
Parsing::Let => "a local variable declaration",
|
|
||||||
|
|
||||||
Parsing::Expr => "an expression",
|
|
||||||
Parsing::ExprKind => "an expression",
|
|
||||||
Parsing::Assign => "an assignment",
|
|
||||||
Parsing::AssignKind => "an assignment operator",
|
|
||||||
Parsing::Binary => "a binary expression",
|
|
||||||
Parsing::BinaryKind => "a binary operator",
|
|
||||||
Parsing::Unary => "a unary expression",
|
|
||||||
Parsing::UnaryKind => "a unary operator",
|
|
||||||
Parsing::Index => "an indexing expression",
|
|
||||||
Parsing::Call => "a call expression",
|
|
||||||
Parsing::Member => "a member access expression",
|
|
||||||
Parsing::PathExpr => "a path",
|
|
||||||
Parsing::PathPart => "a path component",
|
|
||||||
Parsing::Identifier => "an identifier",
|
|
||||||
Parsing::Literal => "a literal",
|
|
||||||
Parsing::Array => "an array",
|
|
||||||
Parsing::ArrayRep => "an array of form [k;N]",
|
|
||||||
Parsing::AddrOf => "a borrow op",
|
|
||||||
Parsing::Block => "a block",
|
|
||||||
Parsing::Group => "a grouped expression",
|
|
||||||
Parsing::Tuple => "a tuple",
|
|
||||||
Parsing::While => "a while expression",
|
|
||||||
Parsing::If => "an if expression",
|
|
||||||
Parsing::For => "a for expression",
|
|
||||||
Parsing::Else => "an else block",
|
|
||||||
Parsing::Break => "a break expression",
|
|
||||||
Parsing::Return => "a return expression",
|
|
||||||
Parsing::Continue => "a continue expression",
|
|
||||||
}
|
|
||||||
.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Parses a sequence of [Tokens](Token) into an [AST](cl_ast)
|
||||||
pub struct Parser<'t> {
|
pub struct Parser<'t> {
|
||||||
/// Lazy tokenizer
|
/// Lazy tokenizer
|
||||||
lexer: Lexer<'t>,
|
lexer: Lexer<'t>,
|
||||||
@@ -849,21 +625,21 @@ impl<'t> Parser<'t> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro binary($($name:ident {$lower:ident, $op:ident})*) {
|
macro binary($($name:ident {$lower:ident, $op:ident})*) {
|
||||||
$(pub fn $name(&mut self) -> PResult<ExprKind> {
|
$(pub fn $name(&mut self) -> PResult<ExprKind> {
|
||||||
let head = self.expr_from(Self::$lower)?;
|
let head = self.expr_from(Self::$lower)?;
|
||||||
let mut tail = vec![];
|
let mut tail = vec![];
|
||||||
loop {
|
loop {
|
||||||
match self.$op() {
|
match self.$op() {
|
||||||
Ok(op) => tail.push((op, self.expr_from(Self::$lower)?)),
|
Ok(op) => tail.push((op, self.expr_from(Self::$lower)?)),
|
||||||
Err(Error { reason: Unexpected(_) | EndOfInput, ..}) => break,
|
Err(Error { reason: Unexpected(_) | EndOfInput, ..}) => break,
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if tail.is_empty() {
|
}
|
||||||
return Ok(head.kind);
|
if tail.is_empty() {
|
||||||
}
|
return Ok(head.kind);
|
||||||
Ok(Binary { head: head.into(), tail }.into())
|
}
|
||||||
})*
|
Ok(Binary { head: head.into(), tail }.into())
|
||||||
|
})*
|
||||||
}
|
}
|
||||||
/// # Expression parsing
|
/// # Expression parsing
|
||||||
impl<'t> Parser<'t> {
|
impl<'t> Parser<'t> {
|
||||||
@@ -889,7 +665,11 @@ impl<'t> Parser<'t> {
|
|||||||
/// [Assign] = [Path] ([AssignKind] [Assign]) | [Compare](Binary)
|
/// [Assign] = [Path] ([AssignKind] [Assign]) | [Compare](Binary)
|
||||||
pub fn exprkind_assign(&mut self) -> PResult<ExprKind> {
|
pub fn exprkind_assign(&mut self) -> PResult<ExprKind> {
|
||||||
let head = self.expr_from(Self::exprkind_compare)?;
|
let head = self.expr_from(Self::exprkind_compare)?;
|
||||||
if !matches!(head.kind, ExprKind::Path(_)) {
|
// TODO: Formalize the concept of a "place expression"
|
||||||
|
if !matches!(
|
||||||
|
head.kind,
|
||||||
|
ExprKind::Path(_) | ExprKind::Call(_) | ExprKind::Member(_) | ExprKind::Index(_)
|
||||||
|
) {
|
||||||
return Ok(head.kind);
|
return Ok(head.kind);
|
||||||
}
|
}
|
||||||
let Ok(op) = self.assign_op() else {
|
let Ok(op) = self.assign_op() else {
|
||||||
@@ -1157,13 +937,13 @@ impl<'t> Parser<'t> {
|
|||||||
/// [If] = <code>`if` [Expr] [Block] [Else]?</code>
|
/// [If] = <code>`if` [Expr] [Block] [Else]?</code>
|
||||||
#[rustfmt::skip] // second line is barely not long enough
|
#[rustfmt::skip] // second line is barely not long enough
|
||||||
pub fn parse_if(&mut self) -> PResult<If> {
|
pub fn parse_if(&mut self) -> PResult<If> {
|
||||||
self.match_kw(Keyword::If, Parsing::If)?;
|
self.match_kw(Keyword::If, Parsing::If)?;
|
||||||
Ok(If {
|
Ok(If {
|
||||||
cond: self.expr()?.into(),
|
cond: self.expr()?.into(),
|
||||||
pass: self.block()?.into(),
|
pass: self.block()?.into(),
|
||||||
fail: self.parse_else()?,
|
fail: self.parse_else()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// [For]: `for` Pattern (TODO) `in` [Expr] [Block] [Else]?
|
/// [For]: `for` Pattern (TODO) `in` [Expr] [Block] [Else]?
|
||||||
pub fn parse_for(&mut self) -> PResult<For> {
|
pub fn parse_for(&mut self) -> PResult<For> {
|
||||||
self.match_kw(Keyword::For, Parsing::For)?;
|
self.match_kw(Keyword::For, Parsing::For)?;
|
||||||
@@ -1190,16 +970,16 @@ impl<'t> Parser<'t> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro operator($($name:ident ($returns:ident) {$($t:ident => $p:ident),*$(,)?};)*) {$(
|
macro operator($($name:ident ($returns:ident) {$($t:ident => $p:ident),*$(,)?};)*) {$(
|
||||||
pub fn $name (&mut self) -> PResult<$returns> {
|
pub fn $name (&mut self) -> PResult<$returns> {
|
||||||
const PARSING: Parsing = Parsing::$returns;
|
const PARSING: Parsing = Parsing::$returns;
|
||||||
let out = Ok(match self.peek_type(PARSING) {
|
let out = Ok(match self.peek_type(PARSING) {
|
||||||
$(Ok(Type::$t) => $returns::$p,)*
|
$(Ok(Type::$t) => $returns::$p,)*
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
Ok(t) => Err(self.error(Unexpected(t), PARSING))?,
|
Ok(t) => Err(self.error(Unexpected(t), PARSING))?,
|
||||||
});
|
});
|
||||||
self.consume_peeked();
|
self.consume_peeked();
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
)*}
|
)*}
|
||||||
|
|
||||||
/// ## Operator Kinds
|
/// ## Operator Kinds
|
||||||
@@ -10,5 +10,13 @@ publish.workspace = true
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
conlang = { path = "../libconlang" }
|
cl-ast = { path = "../cl-ast" }
|
||||||
|
cl-lexer = { path = "../cl-lexer" }
|
||||||
|
cl-token = { path = "../cl-token" }
|
||||||
|
cl-parser = { path = "../cl-parser" }
|
||||||
|
cl-interpret = { path = "../cl-interpret" }
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
argh = "0.1.12"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
cl-structures = { path = "../cl-structures" }
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
//! Collects identifiers into a list
|
//! Collects identifiers into a list
|
||||||
|
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::Parser;
|
||||||
use cl_repl::repline::Repline;
|
use cl_repl::repline::Repline;
|
||||||
use conlang::{common::Loc, lexer::Lexer, parser::Parser};
|
use cl_structures::span::Loc;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
error::Error,
|
error::Error,
|
||||||
@@ -124,7 +126,7 @@ use collectible::Collectible;
|
|||||||
pub mod collectible {
|
pub mod collectible {
|
||||||
|
|
||||||
use super::Collector;
|
use super::Collector;
|
||||||
use conlang::ast::*;
|
use cl_ast::*;
|
||||||
pub trait Collectible<'code> {
|
pub trait Collectible<'code> {
|
||||||
fn collect(&'code self, c: &mut Collector<'code>);
|
fn collect(&'code self, c: &mut Collector<'code>);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use conlang::lexer::Lexer;
|
use cl_lexer::Lexer;
|
||||||
|
use cl_token::Token;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{stdin, IsTerminal, Read},
|
io::{stdin, IsTerminal, Read},
|
||||||
@@ -57,7 +58,7 @@ fn lex_tokens(file: &str, path: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_token(t: conlang::token::Token) {
|
fn print_token(t: Token) {
|
||||||
println!(
|
println!(
|
||||||
"{:02}:{:02}: {:#19} │{}│",
|
"{:02}:{:02}: {:#19} │{}│",
|
||||||
t.line(),
|
t.line(),
|
||||||
|
|||||||
6
cl-repl/src/bin/conlang.rs
Normal file
6
cl-repl/src/bin/conlang.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use cl_repl::cli::run;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
run(argh::from_env())
|
||||||
|
}
|
||||||
@@ -3,85 +3,93 @@
|
|||||||
//! # TODO
|
//! # TODO
|
||||||
//! - [ ] Readline-like line editing
|
//! - [ ] Readline-like line editing
|
||||||
//! - [ ] Raw mode?
|
//! - [ ] Raw mode?
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
|
pub mod ansi {
|
||||||
|
// ANSI color escape sequences
|
||||||
|
pub const ANSI_RED: &str = "\x1b[31m";
|
||||||
|
// const ANSI_GREEN: &str = "\x1b[32m"; // the color of type checker mode
|
||||||
|
pub const ANSI_CYAN: &str = "\x1b[36m";
|
||||||
|
pub const ANSI_BRIGHT_GREEN: &str = "\x1b[92m";
|
||||||
|
pub const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
|
||||||
|
pub const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
|
||||||
|
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
|
||||||
|
pub const ANSI_RESET: &str = "\x1b[0m";
|
||||||
|
pub const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
|
||||||
|
|
||||||
|
pub const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
||||||
|
}
|
||||||
|
|
||||||
pub mod args {
|
pub mod args {
|
||||||
use crate::cli::Mode;
|
use argh::FromArgs;
|
||||||
use std::{
|
use std::{path::PathBuf, str::FromStr};
|
||||||
io::{stdin, IsTerminal},
|
|
||||||
ops::Deref,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
/// The Conlang prototype debug interface
|
||||||
|
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub path: Option<PathBuf>, // defaults None
|
/// the main source file
|
||||||
pub repl: bool, // defaults true if stdin is terminal
|
#[argh(positional)]
|
||||||
pub mode: Mode, // defaults Interpret
|
pub file: Option<PathBuf>,
|
||||||
}
|
|
||||||
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
|
|
||||||
|
|
||||||
impl Args {
|
/// files to include
|
||||||
pub fn new() -> Self {
|
#[argh(option, short = 'I')]
|
||||||
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret }
|
pub include: Vec<PathBuf>,
|
||||||
}
|
|
||||||
pub fn parse(mut self) -> Option<Self> {
|
/// the Repl mode to start in
|
||||||
let mut args = std::env::args();
|
#[argh(option, short = 'm', default = "Default::default()")]
|
||||||
let name = args.next().unwrap_or_default();
|
pub mode: Mode,
|
||||||
let mut unknown = false;
|
|
||||||
while let Some(arg) = args.next() {
|
/// whether to start the repl
|
||||||
match arg.deref() {
|
#[argh(switch, short = 'r')]
|
||||||
"--repl" => self.repl = true,
|
pub no_repl: bool,
|
||||||
"--no-repl" => self.repl = false,
|
}
|
||||||
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
|
|
||||||
"-m" | "--mode" => {
|
/// The CLI's operating mode
|
||||||
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default()
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
}
|
pub enum Mode {
|
||||||
arg => {
|
Tokenize,
|
||||||
eprintln!("Unknown argument: {arg}");
|
Beautify,
|
||||||
unknown = true;
|
#[default]
|
||||||
}
|
Interpret,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub fn ansi_color(self) -> &'static str {
|
||||||
|
use super::ansi::*;
|
||||||
|
match self {
|
||||||
|
Mode::Tokenize => ANSI_BRIGHT_BLUE,
|
||||||
|
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
|
||||||
|
// Mode::Resolve => ANSI_GREEN,
|
||||||
|
Mode::Interpret => ANSI_CYAN,
|
||||||
}
|
}
|
||||||
if unknown {
|
|
||||||
println!("Usage: {name} {HELP}");
|
|
||||||
None?
|
|
||||||
}
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
/// Returns the path to a file, if one was specified
|
|
||||||
pub fn path(&self) -> Option<&Path> {
|
|
||||||
self.path.as_deref()
|
|
||||||
}
|
|
||||||
/// Returns whether to start a REPL session or not
|
|
||||||
pub fn repl(&self) -> bool {
|
|
||||||
self.repl
|
|
||||||
}
|
|
||||||
/// Returns the repl Mode
|
|
||||||
pub fn mode(&self) -> Mode {
|
|
||||||
self.mode
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for Args {
|
|
||||||
fn default() -> Self {
|
impl FromStr for Mode {
|
||||||
Self::new()
|
type Err = &'static str;
|
||||||
|
fn from_str(s: &str) -> Result<Self, &'static str> {
|
||||||
|
Ok(match s {
|
||||||
|
"i" | "interpret" | "r" | "run" => Mode::Interpret,
|
||||||
|
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
|
||||||
|
// "r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
|
||||||
|
"t" | "tokenize" | "token" => Mode::Tokenize,
|
||||||
|
_ => Err("Recognized modes are: 'r' \"run\", 'p' \"pretty\", 't' \"token\"")?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod program {
|
pub mod program {
|
||||||
use std::{fmt::Display, io::Write};
|
use cl_interpret::{
|
||||||
|
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
|
||||||
use conlang::{
|
|
||||||
ast::{self, ast_impl::format::Pretty},
|
|
||||||
interpreter::{
|
|
||||||
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
|
|
||||||
},
|
|
||||||
// pretty_printer::{PrettyPrintable, Printer},
|
|
||||||
lexer::Lexer,
|
|
||||||
parser::{error::PResult, Parser},
|
|
||||||
resolver::{error::TyResult, Resolver},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use cl_ast::{self as ast, format::*};
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::{error::PResult, Parser};
|
||||||
|
// use conlang::resolver::{error::TyResult, Resolver};
|
||||||
|
use std::{fmt::Display, io::Write};
|
||||||
|
|
||||||
pub struct Parsable;
|
pub struct Parsable;
|
||||||
|
|
||||||
pub enum Parsed {
|
pub enum Parsed {
|
||||||
@@ -136,10 +144,6 @@ pub mod program {
|
|||||||
// println!("{self}")
|
// println!("{self}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
|
|
||||||
todo!("Program::resolve(\n{self},\n{resolver:?}\n)")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
|
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||||
match &self.data {
|
match &self.data {
|
||||||
Parsed::File(v) => v.interpret(env),
|
Parsed::File(v) => v.interpret(env),
|
||||||
@@ -155,18 +159,6 @@ pub mod program {
|
|||||||
// }
|
// }
|
||||||
// .map(|ty| println!("{ty}"))
|
// .map(|ty| println!("{ty}"))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// /// Runs the [Program] in the specified [Environment]
|
|
||||||
// pub fn run(&self, env: &mut Environment) -> IResult<()> {
|
|
||||||
// println!(
|
|
||||||
// "{}",
|
|
||||||
// match &self.data {
|
|
||||||
// Parsed::Program(start) => env.eval(start)?,
|
|
||||||
// Parsed::Expr(expr) => env.eval(expr)?,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Display for Program<'t, Parsed> {
|
impl<'t> Display for Program<'t, Parsed> {
|
||||||
@@ -178,172 +170,102 @@ pub mod program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl PrettyPrintable for Program<Parsed> {
|
|
||||||
// fn visit<W: Write>(&self, p: &mut Printer<W>) -> IOResult<()> {
|
|
||||||
// match &self.data {
|
|
||||||
// Parsed::Program(value) => value.visit(p),
|
|
||||||
// Parsed::Expr(value) => value.visit(p),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod cli {
|
pub mod cli {
|
||||||
use conlang::{interpreter::env::Environment, resolver::Resolver, token::Token};
|
//! Implement's the command line interface
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::Args,
|
args::{Args, Mode},
|
||||||
program::{Parsable, Parsed, Program},
|
program::{Parsable, Program},
|
||||||
};
|
repl::Repl,
|
||||||
use std::{
|
tools::print_token,
|
||||||
convert::Infallible,
|
|
||||||
error::Error,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
|
||||||
|
use cl_lexer::Lexer;
|
||||||
|
use cl_parser::Parser;
|
||||||
|
use std::{error::Error, path::Path};
|
||||||
|
|
||||||
// ANSI color escape sequences
|
/// Run the command line interface
|
||||||
const ANSI_RED: &str = "\x1b[31m";
|
pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
||||||
const ANSI_GREEN: &str = "\x1b[32m";
|
let Args { file, include, mode, no_repl } = args;
|
||||||
const ANSI_CYAN: &str = "\x1b[36m";
|
|
||||||
const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
|
|
||||||
const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
|
|
||||||
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
|
|
||||||
const ANSI_RESET: &str = "\x1b[0m";
|
|
||||||
const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
|
|
||||||
|
|
||||||
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
let mut env = Environment::new();
|
||||||
|
for path in include {
|
||||||
#[derive(Clone, Debug)]
|
load_file(&mut env, path)?;
|
||||||
pub enum CLI {
|
|
||||||
Repl(Repl),
|
|
||||||
File { mode: Mode, path: PathBuf },
|
|
||||||
Stdin { mode: Mode },
|
|
||||||
}
|
|
||||||
impl From<Args> for CLI {
|
|
||||||
fn from(value: Args) -> Self {
|
|
||||||
let Args { path, repl, mode } = value;
|
|
||||||
match (repl, path) {
|
|
||||||
(true, Some(path)) => {
|
|
||||||
let prog = std::fs::read_to_string(path).unwrap();
|
|
||||||
let code = conlang::parser::Parser::new(conlang::lexer::Lexer::new(&prog))
|
|
||||||
.file()
|
|
||||||
.unwrap();
|
|
||||||
let mut env = conlang::interpreter::env::Environment::new();
|
|
||||||
env.eval(&code).unwrap();
|
|
||||||
env.call("dump", &[])
|
|
||||||
.expect("calling dump in the environment shouldn't fail");
|
|
||||||
|
|
||||||
Self::Repl(Repl { mode, env, ..Default::default() })
|
|
||||||
}
|
|
||||||
(_, Some(path)) => Self::File { mode, path },
|
|
||||||
(true, None) => Self::Repl(Repl { mode, ..Default::default() }),
|
|
||||||
(false, None) => Self::Stdin { mode },
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl CLI {
|
if no_repl {
|
||||||
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
let code = match &file {
|
||||||
use std::{fs, io};
|
Some(file) => std::fs::read_to_string(file)?,
|
||||||
match self {
|
None => std::io::read_to_string(std::io::stdin())?,
|
||||||
CLI::Repl(repl) => repl.repl(),
|
};
|
||||||
CLI::File { mode, ref path } => {
|
let code = Program::new(&code);
|
||||||
// read file
|
|
||||||
Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?)
|
|
||||||
}
|
|
||||||
CLI::Stdin { mode } => {
|
|
||||||
Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn no_repl(mode: Mode, path: Option<&Path>, code: &str) {
|
|
||||||
let program = Program::new(code);
|
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Tokenize => {
|
Mode::Tokenize => tokenize(code, file),
|
||||||
for token in program.lex() {
|
Mode::Beautify => beautify(code),
|
||||||
if let Some(path) = path {
|
Mode::Interpret => interpret(code, &mut env),
|
||||||
print!("{}:", path.display());
|
}?;
|
||||||
}
|
} else {
|
||||||
match token {
|
if let Some(file) = file {
|
||||||
Ok(token) => print_token(&token),
|
load_file(&mut env, file)?;
|
||||||
Err(e) => println!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode::Beautify => Self::beautify(program),
|
|
||||||
Mode::Resolve => Self::resolve(program, Default::default()),
|
|
||||||
Mode::Interpret => Self::interpret(program, Environment::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn beautify(program: Program<Parsable>) {
|
|
||||||
match program.parse() {
|
|
||||||
Ok(program) => program.print(),
|
|
||||||
Err(e) => eprintln!("{e}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn resolve(program: Program<Parsable>, mut resolver: Resolver) {
|
|
||||||
let mut program = match program.parse() {
|
|
||||||
Ok(program) => program,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = program.resolve(&mut resolver) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn interpret(program: Program<Parsable>, mut interpreter: Environment) {
|
|
||||||
let program = match program.parse() {
|
|
||||||
Ok(program) => program,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(e) = program.run(&mut interpreter) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Err(e) = interpreter.call("main", &[]) {
|
|
||||||
eprintln!("{e}");
|
|
||||||
}
|
}
|
||||||
|
Repl::with_env(mode, env).repl()
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The CLI's operating mode
|
fn load_file(
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
env: &mut Environment,
|
||||||
pub enum Mode {
|
path: impl AsRef<Path>,
|
||||||
Tokenize,
|
) -> Result<ConValue, Box<dyn Error>> {
|
||||||
Beautify,
|
let file = std::fs::read_to_string(path)?;
|
||||||
Resolve,
|
let code = Parser::new(Lexer::new(&file)).file()?;
|
||||||
#[default]
|
env.eval(&code).map_err(Into::into)
|
||||||
Interpret,
|
|
||||||
}
|
}
|
||||||
impl Mode {
|
|
||||||
pub fn ansi_color(self) -> &'static str {
|
fn tokenize(
|
||||||
match self {
|
code: Program<Parsable>,
|
||||||
Mode::Tokenize => ANSI_BRIGHT_BLUE,
|
path: Option<impl AsRef<Path>>,
|
||||||
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
|
) -> Result<(), Box<dyn Error>> {
|
||||||
Mode::Resolve => ANSI_GREEN,
|
for token in code.lex() {
|
||||||
Mode::Interpret => ANSI_CYAN,
|
if let Some(ref path) = path {
|
||||||
|
print!("{}:", path.as_ref().display());
|
||||||
|
}
|
||||||
|
match token {
|
||||||
|
Ok(token) => print_token(&token),
|
||||||
|
Err(e) => println!("{e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
impl FromStr for Mode {
|
|
||||||
type Err = Infallible;
|
fn beautify(code: Program<Parsable>) -> Result<(), Box<dyn Error>> {
|
||||||
fn from_str(s: &str) -> Result<Self, Infallible> {
|
code.parse()?.print();
|
||||||
Ok(match s {
|
Ok(())
|
||||||
"i" | "interpret" | "run" => Mode::Interpret,
|
}
|
||||||
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
|
|
||||||
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
|
fn interpret(code: Program<Parsable>, env: &mut Environment) -> Result<(), Box<dyn Error>> {
|
||||||
"t" | "tokenize" | "tokens" => Mode::Tokenize,
|
match code.parse()?.run(env)? {
|
||||||
_ => Mode::Interpret,
|
ConValue::Empty => {}
|
||||||
})
|
ret => println!("{ret}"),
|
||||||
}
|
}
|
||||||
|
if env.get("main").is_ok() {
|
||||||
|
println!("-> {}", env.call("main", &[])?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod repl {
|
||||||
|
use crate::{
|
||||||
|
ansi::*,
|
||||||
|
args::Mode,
|
||||||
|
program::{Parsable, Parsed, Program},
|
||||||
|
tools::print_token,
|
||||||
|
};
|
||||||
|
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// Implements the interactive interpreter
|
/// Implements the interactive interpreter
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -351,8 +273,8 @@ pub mod cli {
|
|||||||
prompt_again: &'static str, // " ?>"
|
prompt_again: &'static str, // " ?>"
|
||||||
prompt_begin: &'static str, // "cl>"
|
prompt_begin: &'static str, // "cl>"
|
||||||
prompt_error: &'static str, // "! >"
|
prompt_error: &'static str, // "! >"
|
||||||
|
prompt_succs: &'static str, // " ->"
|
||||||
env: Environment,
|
env: Environment,
|
||||||
resolver: Resolver,
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,8 +284,8 @@ pub mod cli {
|
|||||||
prompt_begin: "cl>",
|
prompt_begin: "cl>",
|
||||||
prompt_again: " ?>",
|
prompt_again: " ?>",
|
||||||
prompt_error: "! >",
|
prompt_error: "! >",
|
||||||
|
prompt_succs: " =>",
|
||||||
env: Default::default(),
|
env: Default::default(),
|
||||||
resolver: Default::default(),
|
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,9 +293,19 @@ pub mod cli {
|
|||||||
|
|
||||||
/// Prompt functions
|
/// Prompt functions
|
||||||
impl Repl {
|
impl Repl {
|
||||||
pub fn prompt_error(&self, err: &impl Error) {
|
pub fn prompt_result<T: Display, E: Display>(&self, res: Result<T, E>) {
|
||||||
|
match &res {
|
||||||
|
Ok(v) => self.prompt_succs(v),
|
||||||
|
Err(e) => self.prompt_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prompt_error(&self, err: &impl Display) {
|
||||||
let Self { prompt_error: prompt, .. } = self;
|
let Self { prompt_error: prompt, .. } = self;
|
||||||
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}",)
|
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}")
|
||||||
|
}
|
||||||
|
pub fn prompt_succs(&self, value: &impl Display) {
|
||||||
|
let Self { prompt_succs: prompt, .. } = self;
|
||||||
|
println!("{ANSI_BRIGHT_GREEN}{prompt}{ANSI_RESET} {value}")
|
||||||
}
|
}
|
||||||
/// Resets the cursor to the start of the line, clears the terminal,
|
/// Resets the cursor to the start of the line, clears the terminal,
|
||||||
/// and sets the output color
|
/// and sets the output color
|
||||||
@@ -388,6 +320,10 @@ pub mod cli {
|
|||||||
pub fn new(mode: Mode) -> Self {
|
pub fn new(mode: Mode) -> Self {
|
||||||
Self { mode, ..Default::default() }
|
Self { mode, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
/// Constructs a new [Repl] with the provided [Mode] and [Environment]
|
||||||
|
pub fn with_env(mode: Mode, env: Environment) -> Self {
|
||||||
|
Self { mode, env, ..Default::default() }
|
||||||
|
}
|
||||||
/// Runs the main REPL loop
|
/// Runs the main REPL loop
|
||||||
pub fn repl(&mut self) {
|
pub fn repl(&mut self) {
|
||||||
use crate::repline::{error::Error, Repline};
|
use crate::repline::{error::Error, Repline};
|
||||||
@@ -456,23 +392,26 @@ pub mod cli {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn command(&mut self, line: &str) -> bool {
|
fn command(&mut self, line: &str) -> bool {
|
||||||
match line.trim() {
|
let Some(line) = line.trim().strip_prefix('$') else {
|
||||||
"$pretty" => self.mode = Mode::Beautify,
|
return false;
|
||||||
"$tokens" => self.mode = Mode::Tokenize,
|
};
|
||||||
"$type" => self.mode = Mode::Resolve,
|
if let Ok(mode) = line.parse() {
|
||||||
"$run" => self.mode = Mode::Interpret,
|
self.mode = mode;
|
||||||
"$mode" => println!("{:?} Mode", self.mode),
|
} else {
|
||||||
"$help" => self.help(),
|
match line {
|
||||||
_ => return false,
|
"$run" => self.mode = Mode::Interpret,
|
||||||
|
"mode" => println!("{:?} Mode", self.mode),
|
||||||
|
"help" => self.help(),
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
/// Dispatches calls to repl functions based on the program
|
/// Dispatches calls to repl functions based on the program
|
||||||
fn dispatch(&mut self, code: &mut Program<Parsed>) {
|
fn dispatch(&mut self, code: &mut Program<Parsed>) {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Tokenize => (),
|
Mode::Tokenize => {}
|
||||||
Mode::Beautify => self.beautify(code),
|
Mode::Beautify => self.beautify(code),
|
||||||
Mode::Resolve => self.typecheck(code),
|
|
||||||
Mode::Interpret => self.interpret(code),
|
Mode::Interpret => self.interpret(code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,21 +424,20 @@ pub mod cli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn interpret(&mut self, code: &Program<Parsed>) {
|
fn interpret(&mut self, code: &Program<Parsed>) {
|
||||||
if let Err(e) = code.run(&mut self.env) {
|
match code.run(&mut self.env) {
|
||||||
self.prompt_error(&e)
|
Ok(ConValue::Empty) => {}
|
||||||
}
|
res => self.prompt_result(res),
|
||||||
}
|
|
||||||
fn typecheck(&mut self, code: &mut Program<Parsed>) {
|
|
||||||
if let Err(e) = code.resolve(&mut self.resolver) {
|
|
||||||
self.prompt_error(&e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn beautify(&mut self, code: &Program<Parsed>) {
|
fn beautify(&mut self, code: &Program<Parsed>) {
|
||||||
code.print()
|
code.print()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn print_token(t: &Token) {
|
pub mod tools {
|
||||||
|
use cl_token::Token;
|
||||||
|
pub fn print_token(t: &Token) {
|
||||||
println!(
|
println!(
|
||||||
"{:02}:{:02}: {:#19} │{}│",
|
"{:02}:{:02}: {:#19} │{}│",
|
||||||
t.line(),
|
t.line(),
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
use cl_repl::{args::Args, cli::CLI};
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
CLI::from(Args::new().parse().unwrap_or_default()).run()
|
|
||||||
}
|
|
||||||
@@ -49,7 +49,7 @@ pub mod ignore {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #![deny(unused_must_use)]
|
/// #![deny(unused_must_use)]
|
||||||
/// # use cl_frontend::repline::ignore::Ignore;
|
/// # use cl_repl::repline::ignore::Ignore;
|
||||||
/// ().ignore();
|
/// ().ignore();
|
||||||
/// Err::<(), &str>("Foo").ignore();
|
/// Err::<(), &str>("Foo").ignore();
|
||||||
/// Some("Bar").ignore();
|
/// Some("Bar").ignore();
|
||||||
|
|||||||
10
cl-structures/Cargo.toml
Normal file
10
cl-structures/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-structures"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
45
cl-structures/src/lib.rs
Normal file
45
cl-structures/src/lib.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//! # Universally useful structures
|
||||||
|
//! - [Span](struct@span::Span): Stores a start and end [Loc](struct@span::Loc)
|
||||||
|
//! - [Loc](struct@span::Loc): Stores the index in a stream
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
|
pub mod span {
|
||||||
|
//! - [struct@Span]: Stores the start and end [struct@Loc] of a notable AST node
|
||||||
|
//! - [struct@Loc]: Stores the line/column of a notable AST node
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
/// Stores the start and end [locations](struct@Loc) within the token stream
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Span {
|
||||||
|
pub head: Loc,
|
||||||
|
pub tail: Loc,
|
||||||
|
}
|
||||||
|
pub fn Span(head: Loc, tail: Loc) -> Span {
|
||||||
|
Span { head, tail }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a read-only (line, column) location in a token stream
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Loc {
|
||||||
|
line: u32,
|
||||||
|
col: u32,
|
||||||
|
}
|
||||||
|
pub fn Loc(line: u32, col: u32) -> Loc {
|
||||||
|
Loc { line, col }
|
||||||
|
}
|
||||||
|
impl Loc {
|
||||||
|
pub fn line(self) -> u32 {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
pub fn col(self) -> u32 {
|
||||||
|
self.col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Loc {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Loc { line, col } = self;
|
||||||
|
write!(f, "{line}:{col}:")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
cl-token/Cargo.toml
Normal file
10
cl-token/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-token"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
13
cl-token/src/lib.rs
Normal file
13
cl-token/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//! # Token
|
||||||
|
//!
|
||||||
|
//! Stores a component of a file as a [Type], some [Data], and a line and column number
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![feature(decl_macro)]
|
||||||
|
|
||||||
|
pub mod token;
|
||||||
|
pub mod token_data;
|
||||||
|
pub mod token_type;
|
||||||
|
|
||||||
|
pub use token::Token;
|
||||||
|
pub use token_data::Data;
|
||||||
|
pub use token_type::{Keyword, Type};
|
||||||
@@ -1,20 +1,5 @@
|
|||||||
//! # Token
|
//! A [Token] contains a single unit of lexical information, and an optional bit of [Data]
|
||||||
//!
|
use super::{Data, Type};
|
||||||
//! Stores a component of a file as a [Type], some [Data], and a line and column number
|
|
||||||
|
|
||||||
pub mod token_data;
|
|
||||||
pub mod token_type;
|
|
||||||
pub mod preamble {
|
|
||||||
//! Common imports for working with [tokens](super)
|
|
||||||
pub use super::{
|
|
||||||
token_data::Data,
|
|
||||||
token_type::{Keyword, Type},
|
|
||||||
Token,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
use token_data::Data;
|
|
||||||
use token_type::Type;
|
|
||||||
|
|
||||||
/// Contains a single unit of lexical information,
|
/// Contains a single unit of lexical information,
|
||||||
/// and an optional bit of [Data]
|
/// and an optional bit of [Data]
|
||||||
11
cl-typeck/Cargo.toml
Normal file
11
cl-typeck/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "cl-typeck"
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cl-ast = { path = "../cl-ast" }
|
||||||
210
cl-typeck/src/lib.rs
Normal file
210
cl-typeck/src/lib.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
//! # The Conlang Type Checker
|
||||||
|
//!
|
||||||
|
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
|
||||||
|
|
||||||
|
#![warn(clippy::all)]
|
||||||
|
#![allow(unused)]
|
||||||
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use cl_ast::*;
|
||||||
|
|
||||||
|
pub mod intern {
|
||||||
|
//! Trivially-copyable, easily comparable typed indices for type system constructs
|
||||||
|
|
||||||
|
/// Creates newtype indices over [`usize`] for use elsewhere in the type checker
|
||||||
|
macro_rules! def_id {($($(#[$meta:meta])* $name:ident),*$(,)?) => {$(
|
||||||
|
$(#[$meta])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct $name(usize);
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
#[doc = concat!("Constructs a [`", stringify!($name), "`] from a [`usize`] without checking bounds.")]
|
||||||
|
/// # Safety
|
||||||
|
/// The provided value should be within the bounds of its associated container
|
||||||
|
pub unsafe fn from_raw_unchecked(value: usize) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
/// Gets the index of the type by-value
|
||||||
|
pub fn value(&self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From< $name > for usize {
|
||||||
|
fn from(value: $name) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*}}
|
||||||
|
|
||||||
|
// define the index types
|
||||||
|
def_id! {
|
||||||
|
/// Uniquely represents a Type
|
||||||
|
TypeID,
|
||||||
|
/// Uniquely represents a Value
|
||||||
|
ValueID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod typedef {
|
||||||
|
//! Representations of type definitions
|
||||||
|
// use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::intern::TypeID;
|
||||||
|
use cl_ast::{Item, Visibility};
|
||||||
|
|
||||||
|
/// The definition of a type
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct TypeDef {
|
||||||
|
name: String,
|
||||||
|
kind: Option<TypeKind>,
|
||||||
|
definition: Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum TypeKind {
|
||||||
|
/// A primitive type, built-in to the compiler
|
||||||
|
Intrinsic,
|
||||||
|
/// A user-defined structural product type
|
||||||
|
Struct(Vec<(String, Visibility, TypeID)>),
|
||||||
|
/// A user-defined union-like enum type
|
||||||
|
Enum(Vec<(String, TypeID)>),
|
||||||
|
/// A type alias
|
||||||
|
Alias(TypeID),
|
||||||
|
/// The unit type
|
||||||
|
Empty,
|
||||||
|
/// The Self type
|
||||||
|
SelfTy,
|
||||||
|
// TODO: other types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod valdef {
|
||||||
|
//! Representations of value definitions
|
||||||
|
|
||||||
|
use crate::intern::{TypeID, ValueID};
|
||||||
|
use cl_ast::Block;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ValueDef {
|
||||||
|
name: String,
|
||||||
|
kind: Option<ValueKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ValueKind {
|
||||||
|
Const(),
|
||||||
|
Static(),
|
||||||
|
Fn {
|
||||||
|
args: Vec<TypeID>,
|
||||||
|
rety: TypeID,
|
||||||
|
body: Block,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod typeinfo {
|
||||||
|
//! Stores typeck-time type inference info
|
||||||
|
|
||||||
|
use crate::intern::TypeID;
|
||||||
|
|
||||||
|
/// The Type struct represents all valid types, and can be trivially equality-compared
|
||||||
|
pub struct Type {
|
||||||
|
/// You can only have a pointer chain 65535 pointers long.
|
||||||
|
ref_depth: u16,
|
||||||
|
/// Types can be [Generic](TKind::Generic) or [Concrete](TKind::Concrete)
|
||||||
|
kind: TKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Types can be [Generic](TKind::Generic) or [Concrete](TKind::Concrete)
|
||||||
|
pub enum TKind {
|
||||||
|
/// A Concrete type has an associated [TypeDef](super::typedef::TypeDef)
|
||||||
|
Concrete(TypeID),
|
||||||
|
/// A Generic type is a *locally unique* comparable value,
|
||||||
|
/// valid only until the end of its inference context
|
||||||
|
Generic(usize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod type_context {
|
||||||
|
//! A type context stores a map from names to TypeIDs
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::intern::TypeID;
|
||||||
|
|
||||||
|
pub struct TypeCtx {
|
||||||
|
parent: Option<Box<TypeCtx>>,
|
||||||
|
concrete: HashMap<String, TypeID>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// What is an inference rule?
|
||||||
|
/// An inference rule is a specification with a set of predicates and a judgement
|
||||||
|
|
||||||
|
/// Let's give every type an ID
|
||||||
|
struct TypeID(usize);
|
||||||
|
|
||||||
|
/// Let's give every type some data:
|
||||||
|
|
||||||
|
struct TypeDef<'def> {
|
||||||
|
name: String,
|
||||||
|
definition: &'def Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
and store them in a big vector of type descriptions:
|
||||||
|
|
||||||
|
struct TypeMap<'def> {
|
||||||
|
types: Vec<TypeDef<'def>>,
|
||||||
|
}
|
||||||
|
// todo: insertion of a type should yield a TypeID
|
||||||
|
// todo: impl index with TypeID
|
||||||
|
|
||||||
|
Let's store type information as either a concrete type or a generic type:
|
||||||
|
|
||||||
|
/// The Type struct represents all valid types, and can be trivially equality-compared
|
||||||
|
pub struct Type {
|
||||||
|
/// You can only have a pointer chain 65535 pointers long.
|
||||||
|
ref_depth: u16,
|
||||||
|
kind: TKind,
|
||||||
|
}
|
||||||
|
pub enum TKind {
|
||||||
|
Concrete(TypeID),
|
||||||
|
Generic(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
And assume I can specify a rule based on its inputs and outputs:
|
||||||
|
|
||||||
|
Rule {
|
||||||
|
operation: If,
|
||||||
|
/// The inputs field is populated by
|
||||||
|
inputs: [Concrete(BOOL), Generic(0), Generic(0)],
|
||||||
|
outputs: Generic(0),
|
||||||
|
/// This rule is compiler-intrinsic!
|
||||||
|
through: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
Rule {
|
||||||
|
operation: Add,
|
||||||
|
inputs: [Concrete(I32), Concrete(I32)],
|
||||||
|
outputs: Concrete(I32),
|
||||||
|
/// This rule is not compiler-intrinsic (it is overloaded!)
|
||||||
|
through: Some(&ImplAddForI32::Add),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
These rules can be stored in some kind of rule database:
|
||||||
|
|
||||||
|
let rules: Hashmap<Operation, Vec<Rule>> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Potential solution:
|
||||||
|
Store reference to type field of each type expression in the AST
|
||||||
|
*/
|
||||||
1
libconlang/.gitignore
vendored
1
libconlang/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/target
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "conlang"
|
|
||||||
description = "The Conlang Programming Language"
|
|
||||||
keywords = ["interpreter", "programming", "language"]
|
|
||||||
authors.workspace = true
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
publish.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
unicode-xid = "0.2.4"
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//! # Universally useful structures
|
|
||||||
//! - [struct@Span]: Stores the start and end [struct@Loc] of a notable AST node
|
|
||||||
//! - [struct@Loc]: Stores the line/column of a notable AST node
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
use crate::lexer::Lexer;
|
|
||||||
|
|
||||||
/// Stores the start and end [locations](struct@Loc) within the token stream
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Span {
|
|
||||||
pub head: Loc,
|
|
||||||
pub tail: Loc,
|
|
||||||
}
|
|
||||||
pub fn Span(head: Loc, tail: Loc) -> Span {
|
|
||||||
Span { head, tail }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a read-only (line, column) location in a token stream
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Loc {
|
|
||||||
line: u32,
|
|
||||||
col: u32,
|
|
||||||
}
|
|
||||||
pub fn Loc(line: u32, col: u32) -> Loc {
|
|
||||||
Loc { line, col }
|
|
||||||
}
|
|
||||||
impl Loc {
|
|
||||||
pub fn line(self) -> u32 {
|
|
||||||
self.line
|
|
||||||
}
|
|
||||||
pub fn col(self) -> u32 {
|
|
||||||
self.col
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Loc {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let Loc { line, col } = self;
|
|
||||||
write!(f, "{line}:{col}:")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'t> From<&Lexer<'t>> for Loc {
|
|
||||||
fn from(value: &Lexer<'t>) -> Self {
|
|
||||||
Loc(value.line(), value.col())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
//! Conlang is an expression-based programming language with similarities to Rust and Python
|
|
||||||
#![warn(clippy::all)]
|
|
||||||
#![feature(decl_macro)]
|
|
||||||
|
|
||||||
pub mod common;
|
|
||||||
|
|
||||||
pub mod token;
|
|
||||||
|
|
||||||
pub mod ast;
|
|
||||||
|
|
||||||
pub mod lexer;
|
|
||||||
|
|
||||||
pub mod parser;
|
|
||||||
|
|
||||||
pub mod resolver;
|
|
||||||
|
|
||||||
pub mod interpreter;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
@@ -1,916 +0,0 @@
|
|||||||
//! Extremely early WIP of a static type-checker/resolver
|
|
||||||
//!
|
|
||||||
//! This will hopefully become a fully fledged static resolution pass in the future
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use scopeguard::Scoped;
|
|
||||||
pub mod scopeguard {
|
|
||||||
//! Implements a generic RAII scope-guard
|
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
pub trait Scoped: Sized {
|
|
||||||
fn frame(&mut self) -> Guard<Self> {
|
|
||||||
Guard::new(self)
|
|
||||||
}
|
|
||||||
///
|
|
||||||
fn enter_scope(&mut self);
|
|
||||||
fn exit_scope(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Guard<'scope, T: Scoped> {
|
|
||||||
inner: &'scope mut T,
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Guard<'scope, T> {
|
|
||||||
pub fn new(inner: &'scope mut T) -> Self {
|
|
||||||
inner.enter_scope();
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Deref for Guard<'scope, T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> DerefMut for Guard<'scope, T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'scope, T: Scoped> Drop for Guard<'scope, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.inner.exit_scope()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prints like [println] if `debug_assertions` are enabled
|
|
||||||
macro debugln($($t:tt)*) {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
println!($($t)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro debug($($t:tt)*) {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
print!($($t)*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use ty::Type;
|
|
||||||
pub mod ty {
|
|
||||||
//! Describes the type of a [Variable](super::Variable)
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
/// Describes the type of a [Variable](super::Variable)
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type {
|
|
||||||
#[default]
|
|
||||||
Empty,
|
|
||||||
Int,
|
|
||||||
Bool,
|
|
||||||
Char,
|
|
||||||
String,
|
|
||||||
Float,
|
|
||||||
Fn {
|
|
||||||
args: Vec<Type>,
|
|
||||||
ret: Box<Type>,
|
|
||||||
},
|
|
||||||
Range(Box<Type>),
|
|
||||||
Tuple(Vec<Type>),
|
|
||||||
Never,
|
|
||||||
/// [Inferred](Type::Inferred) is for error messages. DO NOT CONSTRUCT
|
|
||||||
Inferred,
|
|
||||||
Generic(String),
|
|
||||||
/// A function with a single parameter of [Type::ManyInferred]
|
|
||||||
/// is assumed to always be correct.
|
|
||||||
ManyInferred,
|
|
||||||
}
|
|
||||||
impl Type {
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self == &Type::Empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for Type {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Type::Empty => "Empty".fmt(f),
|
|
||||||
Type::Int => "integer".fmt(f),
|
|
||||||
Type::Bool => "bool".fmt(f),
|
|
||||||
Type::Char => "char".fmt(f),
|
|
||||||
Type::String => "String".fmt(f),
|
|
||||||
Type::Float => "float".fmt(f),
|
|
||||||
// TODO: clean this up
|
|
||||||
Type::Fn { args, ret } => {
|
|
||||||
"fn (".fmt(f)?;
|
|
||||||
let mut args = args.iter();
|
|
||||||
if let Some(arg) = args.next() {
|
|
||||||
arg.fmt(f)?;
|
|
||||||
}
|
|
||||||
for arg in args {
|
|
||||||
write!(f, ", {arg}")?
|
|
||||||
}
|
|
||||||
")".fmt(f)?;
|
|
||||||
if !ret.is_empty() {
|
|
||||||
write!(f, " -> {ret}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Type::Range(t) => write!(f, "{t}..{t}"),
|
|
||||||
Type::Tuple(t) => {
|
|
||||||
"(".fmt(f)?;
|
|
||||||
for (idx, ty) in t.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
", ".fmt(f)?;
|
|
||||||
}
|
|
||||||
ty.fmt(f)?;
|
|
||||||
}
|
|
||||||
")".fmt(f)
|
|
||||||
}
|
|
||||||
Type::Never => "!".fmt(f),
|
|
||||||
Type::Inferred => "_".fmt(f),
|
|
||||||
Type::Generic(name) => write!(f, "<{name}>"),
|
|
||||||
Type::ManyInferred => "..".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the life-cycle of a [Variable]: Whether it's bound, typed, or initialized
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Status {
|
|
||||||
#[default]
|
|
||||||
Bound,
|
|
||||||
Uninitialized(Type),
|
|
||||||
Initialized(Type),
|
|
||||||
}
|
|
||||||
impl Status {
|
|
||||||
/// Performs type-checking for a [Variable] assignment
|
|
||||||
pub fn assign(&mut self, ty: &Type) -> TyResult<()> {
|
|
||||||
match self {
|
|
||||||
// Variable uninitialized: initialize it
|
|
||||||
Status::Bound => {
|
|
||||||
*self = Status::Initialized(ty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Typecheck ok! Reuse the allocation for t
|
|
||||||
Status::Uninitialized(t) if t == ty => {
|
|
||||||
*self = Status::Initialized(std::mem::take(t));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Status::Initialized(t) if t == ty => Ok(()),
|
|
||||||
// Typecheck not ok.
|
|
||||||
Status::Uninitialized(e) | Status::Initialized(e) => {
|
|
||||||
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct Variable {
|
|
||||||
/// The unique, global index of this variable
|
|
||||||
pub index: usize,
|
|
||||||
/// The [Status] of this variable
|
|
||||||
pub status: Status,
|
|
||||||
/// The mutability qualifier of this variable
|
|
||||||
pub mutable: bool,
|
|
||||||
}
|
|
||||||
impl Variable {
|
|
||||||
/// Constructs a new variable with the provided index and mutability
|
|
||||||
pub fn new(index: usize, mutable: bool) -> Self {
|
|
||||||
Self { index, mutable, ..Default::default() }
|
|
||||||
}
|
|
||||||
/// Performs a type-checked assignment on self
|
|
||||||
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
let Variable { index, status, mutable } = self;
|
|
||||||
debug!("Typecheck for {name} #{index}: ");
|
|
||||||
let out = match (status, mutable) {
|
|
||||||
// Variable uninitialized: initialize it
|
|
||||||
(Status::Bound, _) => {
|
|
||||||
self.status = Status::Initialized(ty.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Typecheck ok! Reuse the allocation for t
|
|
||||||
(Status::Uninitialized(t), _) if t == ty => {
|
|
||||||
self.status = Status::Initialized(std::mem::take(t));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Reassignment of mutable variable is ok
|
|
||||||
(Status::Initialized(t), true) if t == ty => Ok(()),
|
|
||||||
// Reassignment of immutable variable is not ok
|
|
||||||
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
|
|
||||||
// Typecheck not ok.
|
|
||||||
(Status::Uninitialized(e) | Status::Initialized(e), _) => {
|
|
||||||
Err(Error::TypeMismatch { want: ty.clone(), got: e.clone() })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match &out {
|
|
||||||
Ok(_) => debugln!("Ok! ({ty})"),
|
|
||||||
Err(e) => debugln!("Error: {e:?}"),
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
/// Performs the type-checking for a modifying assignment
|
|
||||||
pub fn modify_assign(&self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
let Variable { index, status, mutable } = &self;
|
|
||||||
match (status, mutable) {
|
|
||||||
(Status::Initialized(t), true) if t == ty => Ok(()),
|
|
||||||
(Status::Initialized(t), true) => {
|
|
||||||
Err(Error::TypeMismatch { want: t.clone(), got: ty.clone() })
|
|
||||||
}
|
|
||||||
(Status::Initialized(_), false) => Err(Error::ImmutableAssign(name.into(), *index)),
|
|
||||||
(..) => Err(Error::Uninitialized(name.into(), *index)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
THE BIG IDEA:
|
|
||||||
- Each `let` statement binds a *different* variable.
|
|
||||||
- Shadowing is a FEATURE
|
|
||||||
- Traversing the tree before program launch allows the Resolver to assign
|
|
||||||
an index to each variable usage in the scope-tree
|
|
||||||
- These indices allow constant-time variable lookup in the interpreter!!!
|
|
||||||
- The added type-checking means fewer type errors!
|
|
||||||
|
|
||||||
REQUIREMENTS FOR FULL TYPE-CHECKING:
|
|
||||||
- Meaningful type expressions in function declarations
|
|
||||||
|
|
||||||
NECESSARY CONSIDERATIONS:
|
|
||||||
- Variable binding happens AFTER the initialization expression is run.
|
|
||||||
- If a variable is *entirely* unbound before being referenced,
|
|
||||||
it'll still error.
|
|
||||||
- This is *intentional*, and ALLOWS shadowing previous variables.
|
|
||||||
- In my experience, this is almost NEVER an error :P
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Scope {
|
|
||||||
/// A monotonically increasing counter of variable declarations
|
|
||||||
count: usize,
|
|
||||||
/// A dictionary keeping track of type and lifetime information
|
|
||||||
vars: HashMap<String, Variable>,
|
|
||||||
}
|
|
||||||
impl Scope {
|
|
||||||
/// Bind a [Variable] in Scope
|
|
||||||
pub fn insert(&mut self, name: &str, index: usize, mutable: bool) {
|
|
||||||
self.count += 1;
|
|
||||||
self.vars
|
|
||||||
.insert(name.to_string(), Variable::new(index, mutable));
|
|
||||||
}
|
|
||||||
/// Returns a reference to a [Variable], if `name` is bound
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Variable> {
|
|
||||||
self.vars.get(name)
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a [Variable], if `name` is bound
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
|
|
||||||
self.vars.get_mut(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Implements a dynamically scoped namespace
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Module {
|
|
||||||
modules: HashMap<String, Module>,
|
|
||||||
vars: HashMap<String, Variable>,
|
|
||||||
}
|
|
||||||
impl Module {
|
|
||||||
pub fn insert_var(&mut self, name: &str, index: usize, mutable: bool) -> TyResult<()> {
|
|
||||||
if self
|
|
||||||
.vars
|
|
||||||
.insert(name.into(), Variable::new(index, mutable))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
Err(Error::NonUniqueInModule(name.into()))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn insert_module(&mut self, name: String, module: Module) -> TyResult<()> {
|
|
||||||
if self.modules.insert(name.clone(), module).is_some() {
|
|
||||||
Err(Error::NonUniqueInModule(name + "(module)"))?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Returns a reference to a [Variable] in this Module, if `name` is bound
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Variable> {
|
|
||||||
self.vars.get(name)
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a [Variable] in this Module, if `name` is bound
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Variable> {
|
|
||||||
self.vars.get_mut(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_get(&self, name: &str, path: &[String]) -> Option<&Variable> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return self.get(name);
|
|
||||||
}
|
|
||||||
let module = self.modules.get(&path[0])?;
|
|
||||||
module
|
|
||||||
.resolve_get(name, &path[1..])
|
|
||||||
.or_else(|| self.get(name))
|
|
||||||
}
|
|
||||||
// Returns a reference to the module at a specified path
|
|
||||||
pub fn resolve(&self, path: &[String]) -> TyResult<&Module> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return Ok(self);
|
|
||||||
}
|
|
||||||
let module = self
|
|
||||||
.modules
|
|
||||||
.get(&path[0])
|
|
||||||
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
|
|
||||||
module.resolve(&path[1..])
|
|
||||||
}
|
|
||||||
/// Returns a mutable reference to a Module if one is bound
|
|
||||||
pub fn resolve_mut(&mut self, path: &[String]) -> TyResult<&mut Module> {
|
|
||||||
if path.is_empty() {
|
|
||||||
return Ok(self);
|
|
||||||
}
|
|
||||||
let module = self
|
|
||||||
.modules
|
|
||||||
.get_mut(&path[0])
|
|
||||||
.ok_or_else(|| Error::Unbound(path[0].clone()))?;
|
|
||||||
module.resolve_mut(&path[1..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Resolver {
|
|
||||||
/// A monotonically increasing counter of variable declarations
|
|
||||||
count: usize,
|
|
||||||
/// A stack of nested scopes *inside* a function
|
|
||||||
scopes: Vec<Scope>,
|
|
||||||
/// A stack of nested scopes *outside* a function
|
|
||||||
// TODO: Record the name of the module, and keep a stack of the current module
|
|
||||||
// for name resolution
|
|
||||||
modules: Module,
|
|
||||||
/// Describes the current path
|
|
||||||
module: Vec<String>,
|
|
||||||
/// A stack of types
|
|
||||||
types: Vec<Type>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scoped for Resolver {
|
|
||||||
fn enter_scope(&mut self) {
|
|
||||||
self.enter_scope();
|
|
||||||
}
|
|
||||||
fn exit_scope(&mut self) {
|
|
||||||
self.exit_scope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Resolver {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut new = Self {
|
|
||||||
count: Default::default(),
|
|
||||||
scopes: vec![Default::default()],
|
|
||||||
modules: Default::default(),
|
|
||||||
module: Default::default(),
|
|
||||||
types: Default::default(),
|
|
||||||
};
|
|
||||||
new.register_builtin("print", &[], &[Type::ManyInferred], Type::Empty)
|
|
||||||
.expect("print should not be bound in new Resolver");
|
|
||||||
new
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolver {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
/// Register a built-in function into the top-level module
|
|
||||||
pub fn register_builtin(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
path: &[String],
|
|
||||||
args: &[Type],
|
|
||||||
ret: Type,
|
|
||||||
) -> TyResult<()> {
|
|
||||||
let module = self.modules.resolve_mut(path)?;
|
|
||||||
module.vars.insert(
|
|
||||||
name.into(),
|
|
||||||
Variable {
|
|
||||||
index: 0,
|
|
||||||
status: Status::Initialized(Type::Fn { args: args.into(), ret: ret.into() }),
|
|
||||||
mutable: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Enters a Module Scope
|
|
||||||
pub fn enter_module(&mut self, name: &str) -> TyResult<()> {
|
|
||||||
let module = self.modules.resolve_mut(&self.module)?;
|
|
||||||
module.insert_module(name.into(), Default::default())?;
|
|
||||||
self.module.push(name.into());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Exits a Module Scope
|
|
||||||
pub fn exit_module(&mut self) -> Option<String> {
|
|
||||||
// Modules stay registered
|
|
||||||
self.module.pop()
|
|
||||||
}
|
|
||||||
/// Enters a Block Scope
|
|
||||||
pub fn enter_scope(&mut self) {
|
|
||||||
self.scopes.push(Default::default());
|
|
||||||
}
|
|
||||||
/// Exits a Block Scope, returning the value
|
|
||||||
pub fn exit_scope(&mut self) -> Option<usize> {
|
|
||||||
self.scopes.pop().map(|scope| scope.count)
|
|
||||||
}
|
|
||||||
/// Resolves a name to a [Variable]
|
|
||||||
pub fn get(&self, name: &str) -> TyResult<&Variable> {
|
|
||||||
if let Some(var) = self.scopes.iter().rev().find_map(|s| s.get(name)) {
|
|
||||||
return Ok(var);
|
|
||||||
}
|
|
||||||
self.modules
|
|
||||||
.resolve_get(name, &self.module)
|
|
||||||
.ok_or_else(|| Error::Unbound(name.into()))
|
|
||||||
}
|
|
||||||
/// Mutably resolves a name to a [Variable]
|
|
||||||
pub fn get_mut(&mut self, name: &str) -> TyResult<&mut Variable> {
|
|
||||||
if let Some(var) = self.scopes.iter_mut().rev().find_map(|s| s.get_mut(name)) {
|
|
||||||
return Ok(var);
|
|
||||||
}
|
|
||||||
self.modules
|
|
||||||
.resolve_mut(&self.module)?
|
|
||||||
.get_mut(name)
|
|
||||||
.ok_or_else(|| Error::Unbound(name.into()))
|
|
||||||
}
|
|
||||||
/// Binds a name in the current lexical scope
|
|
||||||
pub fn insert_scope(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
|
|
||||||
self.count += 1;
|
|
||||||
self.scopes
|
|
||||||
.last_mut()
|
|
||||||
.ok_or_else(|| panic!("Stack underflow in resolver?"))?
|
|
||||||
.insert(name, self.count, mutable);
|
|
||||||
Ok(self.count)
|
|
||||||
}
|
|
||||||
/// Binds a name in the current module
|
|
||||||
pub fn insert_module(&mut self, name: &str, mutable: bool) -> TyResult<usize> {
|
|
||||||
self.count += 1;
|
|
||||||
self.modules
|
|
||||||
.resolve_mut(&self.module)?
|
|
||||||
.insert_var(name, self.count, mutable)?;
|
|
||||||
Ok(self.count)
|
|
||||||
}
|
|
||||||
/// Performs scoped type-checking of variables
|
|
||||||
pub fn assign(&mut self, name: &str, ty: &Type) -> TyResult<()> {
|
|
||||||
self.get_mut(name)?.assign(name, ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
/// Manages a module scope
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// macro module(self, name: &str, inner: {...}) -> Result<_, Error>
|
|
||||||
/// ```
|
|
||||||
macro module($self:ident, $name:tt, $inner:tt) {{
|
|
||||||
$self.enter_module($name)?;
|
|
||||||
#[allow(clippy::redundant_closure_call)]
|
|
||||||
let scope = (|| $inner)(); // This is pretty gross, but hey, try {} syntax is unstable too
|
|
||||||
$self.exit_module();
|
|
||||||
scope
|
|
||||||
}}
|
|
||||||
|
|
||||||
impl Resolver {
|
|
||||||
pub fn visit_empty(&mut self) -> TyResult<()> {
|
|
||||||
debugln!("Got Empty");
|
|
||||||
self.types.push(Type::Empty);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Resolve {
|
|
||||||
/// Performs variable resolution on self, and returns the type of self.
|
|
||||||
///
|
|
||||||
/// For expressions, this is the type of the expression.
|
|
||||||
///
|
|
||||||
/// For declarations, this is Empty.
|
|
||||||
fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
Ok(Type::Empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod ast1 {
|
|
||||||
// #![allow(deprecated)]
|
|
||||||
// use super::*;
|
|
||||||
// use crate::ast::preamble::*;
|
|
||||||
// impl Resolve for Start {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Self(program) = self;
|
|
||||||
// program.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Program {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Self(module) = self;
|
|
||||||
// for decl in module {
|
|
||||||
// decl.resolve(resolver)?;
|
|
||||||
// }
|
|
||||||
// // TODO: record the number of module-level assignments into the AST
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Stmt {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Stmt::Let(value) => value.resolve(resolver),
|
|
||||||
// Stmt::Fn(value) => value.resolve(resolver),
|
|
||||||
// Stmt::Expr(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Let {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Let { name: Name { symbol: Identifier { name, index }, mutable, ty: _ }, init } =
|
|
||||||
// self;
|
|
||||||
// debugln!("ty> let {name} ...");
|
|
||||||
// if let Some(init) = init {
|
|
||||||
// let ty = init.resolve(resolver)?;
|
|
||||||
// *index = Some(resolver.insert_scope(name, *mutable)?);
|
|
||||||
// resolver.get_mut(name)?.assign(name, &ty)?;
|
|
||||||
// } else {
|
|
||||||
// resolver.insert_scope(name, *mutable)?;
|
|
||||||
// }
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for FnDecl {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let FnDecl { name: Name { symbol: Identifier { name, index }, .. }, args, body } =
|
|
||||||
// self; debugln!("ty> fn {name} ...");
|
|
||||||
// // register the name at module scope
|
|
||||||
// *index = Some(resolver.insert_module(name, false)?);
|
|
||||||
// // create a new lexical scope
|
|
||||||
// let scopes = std::mem::take(&mut resolver.scopes);
|
|
||||||
// // type-check the function body
|
|
||||||
// let out = {
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// let mut evaluated_args = vec![];
|
|
||||||
// for arg in args {
|
|
||||||
// evaluated_args.push(arg.resolve(&mut resolver)?)
|
|
||||||
// }
|
|
||||||
// let fn_decl = Type::Fn { args: evaluated_args.clone(), ret: Box::new(Type::Empty)
|
|
||||||
// }; resolver.get_mut(name)?.assign(name, &fn_decl)?;
|
|
||||||
// module!(resolver, name, { body.resolve(&mut resolver) })
|
|
||||||
// };
|
|
||||||
// let _ = std::mem::replace(&mut resolver.scopes, scopes);
|
|
||||||
// out
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Name {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// Ok(Type::Empty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Block {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Block { let_count: _, statements, expr } = self;
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// for stmt in statements {
|
|
||||||
// stmt.resolve(&mut resolver)?;
|
|
||||||
// }
|
|
||||||
// expr.resolve(&mut resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Expr {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Expr(expr) = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Operation {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Operation::Assign(value) => value.resolve(resolver),
|
|
||||||
// Operation::Binary(value) => value.resolve(resolver),
|
|
||||||
// Operation::Unary(value) => value.resolve(resolver),
|
|
||||||
// Operation::Call(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Assign {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Assign { target, operator, init } = self;
|
|
||||||
// // Evaluate the initializer expression
|
|
||||||
// let ty = init.resolve(resolver)?;
|
|
||||||
// // Resolve the variable
|
|
||||||
// match (operator, resolver.get_mut(&target.name)?) {
|
|
||||||
// (
|
|
||||||
// operator::Assign::Assign,
|
|
||||||
// Variable { status: Status::Initialized(_), mutable: false, index },
|
|
||||||
// ) => Err(Error::ImmutableAssign(target.name.clone(), *index)),
|
|
||||||
// // TODO: make typing more expressive for modifying assignment
|
|
||||||
// (_, variable) => variable
|
|
||||||
// .modify_assign(&target.name, &ty)
|
|
||||||
// .map(|_| Type::Empty),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Binary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Binary { first, other } = self;
|
|
||||||
|
|
||||||
// let mut first = first.resolve(resolver)?;
|
|
||||||
// for (op, other) in other {
|
|
||||||
// let other = other.resolve(resolver)?;
|
|
||||||
// first = resolver.resolve_binary_operator(first, other, op)?;
|
|
||||||
// }
|
|
||||||
// Ok(first)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Unary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Unary { operators, operand } = self;
|
|
||||||
// let mut operand = operand.resolve(resolver)?;
|
|
||||||
// for op in operators {
|
|
||||||
// operand = resolver.resolve_unary_operator(operand, op)?;
|
|
||||||
// }
|
|
||||||
// Ok(operand)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// /// Resolve [operator]s
|
|
||||||
// impl Resolver {
|
|
||||||
// fn resolve_binary_operator(
|
|
||||||
// &mut self,
|
|
||||||
// first: Type,
|
|
||||||
// other: Type,
|
|
||||||
// op: &operator::Binary,
|
|
||||||
// ) -> TyResult<Type> {
|
|
||||||
// // TODO: check type compatibility for binary ops
|
|
||||||
// // TODO: desugar binary ops into function calls, when member functions are a thing
|
|
||||||
// eprintln!("Resolve binary operators {first} {op:?} {other}");
|
|
||||||
// if first != other {
|
|
||||||
// Err(Error::TypeMismatch { want: first, got: other })
|
|
||||||
// } else {
|
|
||||||
// Ok(first)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// fn resolve_unary_operator(
|
|
||||||
// &mut self,
|
|
||||||
// operand: Type,
|
|
||||||
// op: &operator::Unary,
|
|
||||||
// ) -> TyResult<Type> {
|
|
||||||
// // TODO: Allow more expressive unary operator type conversions
|
|
||||||
// todo!("Resolve unary operators {op:?} {operand}")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Call {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Call::FnCall(value) => value.resolve(resolver),
|
|
||||||
// Call::Primary(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for FnCall {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let FnCall { callee, args } = self;
|
|
||||||
// let mut callee = callee.resolve(resolver)?;
|
|
||||||
// for argset in args {
|
|
||||||
// // arguments should always be a tuple here
|
|
||||||
// let arguments = argset.resolve(resolver)?;
|
|
||||||
// let Type::Tuple(arguments) = arguments else {
|
|
||||||
// Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Tuple(vec![Type::ManyInferred]),
|
|
||||||
// got: arguments,
|
|
||||||
// })?
|
|
||||||
// };
|
|
||||||
// // Verify that the callee is a function, and the arguments match.
|
|
||||||
// // We need the arguments
|
|
||||||
// let Type::Fn { args, ret } = callee else {
|
|
||||||
// return Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
|
|
||||||
// got: callee,
|
|
||||||
// })?;
|
|
||||||
// };
|
|
||||||
// for (want, got) in args.iter().zip(&arguments) {
|
|
||||||
// // TODO: verify generics
|
|
||||||
// if let Type::Generic(_) = want {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// if want != got {
|
|
||||||
// return Err(Error::TypeMismatch {
|
|
||||||
// want: Type::Fn { args: arguments, ret: Type::Inferred.into() },
|
|
||||||
// got: Type::Fn { args, ret },
|
|
||||||
// })?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// callee = *ret;
|
|
||||||
// }
|
|
||||||
// Ok(callee)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Primary {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Primary::Identifier(value) => value.resolve(resolver),
|
|
||||||
// Primary::Literal(value) => value.resolve(resolver),
|
|
||||||
// Primary::Block(value) => value.resolve(resolver),
|
|
||||||
// Primary::Group(value) => value.resolve(resolver),
|
|
||||||
// Primary::Branch(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Group {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// match self {
|
|
||||||
// Group::Tuple(tuple) => tuple.resolve(resolver),
|
|
||||||
// Group::Single(expr) => expr.resolve(resolver),
|
|
||||||
// Group::Empty => Ok(Type::Empty),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Tuple {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Tuple { elements } = self;
|
|
||||||
// let mut types = vec![];
|
|
||||||
// for expr in elements.iter_mut() {
|
|
||||||
// types.push(expr.resolve(resolver)?);
|
|
||||||
// }
|
|
||||||
// Ok(Type::Tuple(types))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Identifier {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Identifier { name, index: id_index } = self;
|
|
||||||
// let Variable { index, status, .. } = resolver.get(name)?;
|
|
||||||
// *id_index = Some(*index);
|
|
||||||
// let ty = match status {
|
|
||||||
// Status::Initialized(t) => t,
|
|
||||||
// _ => Err(Error::Uninitialized(name.to_owned(), *index))?,
|
|
||||||
// };
|
|
||||||
// debugln!("ty> Resolved {} #{index}: {ty}", name);
|
|
||||||
// Ok(ty.to_owned())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Literal {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// Ok(match self {
|
|
||||||
// Literal::String(_) => Type::String,
|
|
||||||
// Literal::Char(_) => Type::Char,
|
|
||||||
// Literal::Bool(_) => Type::Bool,
|
|
||||||
// Literal::Float(_) => Type::Float,
|
|
||||||
// Literal::Int(_) => Type::Int,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Flow {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish this
|
|
||||||
// match self {
|
|
||||||
// Flow::While(value) => value.resolve(resolver),
|
|
||||||
// Flow::If(value) => value.resolve(resolver),
|
|
||||||
// Flow::For(value) => value.resolve(resolver),
|
|
||||||
// Flow::Continue(value) => value.resolve(resolver),
|
|
||||||
// Flow::Return(value) => value.resolve(resolver),
|
|
||||||
// Flow::Break(value) => value.resolve(resolver),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for While {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish this
|
|
||||||
// // Visit else first, save that to a break-pattern stack in the Resolver,
|
|
||||||
// // and check it inside Break::resolve()
|
|
||||||
// let While { cond, body, else_ } = self;
|
|
||||||
// cond.resolve(resolver)?; // must be Type::Bool
|
|
||||||
// body.resolve(resolver)?; // discard
|
|
||||||
// else_.resolve(resolver) // compare with returns inside body
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for If {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let If { cond, body, else_ } = self;
|
|
||||||
// let cond = cond.resolve(resolver)?;
|
|
||||||
// if Type::Bool != cond {
|
|
||||||
// return Err(Error::TypeMismatch { want: Type::Bool, got: cond });
|
|
||||||
// }
|
|
||||||
// let body_ty = body.resolve(resolver)?;
|
|
||||||
// let else_ty = else_.resolve(resolver)?;
|
|
||||||
// if body_ty == else_ty {
|
|
||||||
// Ok(body_ty)
|
|
||||||
// } else {
|
|
||||||
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for For {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let For { var: Identifier { name, index }, iter, body, else_ } = self;
|
|
||||||
// debugln!("> for {name} in ...");
|
|
||||||
// // Visit the iter expression and get its type
|
|
||||||
// let range = iter.resolve(resolver)?;
|
|
||||||
// let ty = match range {
|
|
||||||
// Type::Range(t) => t,
|
|
||||||
// got => Err(Error::TypeMismatch { want: Type::Range(Type::Inferred.into()), got
|
|
||||||
// })?, };
|
|
||||||
// let body_ty = {
|
|
||||||
// let mut resolver = resolver.frame();
|
|
||||||
// // bind the variable in the loop scope
|
|
||||||
// *index = Some(resolver.insert_scope(name, false)?);
|
|
||||||
// resolver.get_mut(name)?.assign(name, &ty)?;
|
|
||||||
// body.resolve(&mut resolver)
|
|
||||||
// }?;
|
|
||||||
// // visit the else block
|
|
||||||
// let else_ty = else_.resolve(resolver)?;
|
|
||||||
// if body_ty != else_ty {
|
|
||||||
// Err(Error::TypeMismatch { want: body_ty, got: else_ty })
|
|
||||||
// } else {
|
|
||||||
// Ok(body_ty)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Else {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// let Else { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Resolve for Continue {
|
|
||||||
// fn resolve(&mut self, _resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// Ok(Type::Never)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Break {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// let Break { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Resolve for Return {
|
|
||||||
// fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
// // TODO: Finish control flow
|
|
||||||
// let Return { expr } = self;
|
|
||||||
// expr.resolve(resolver)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
mod ast {
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
use crate::ast::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
// heakc yea man, generics
|
|
||||||
impl<T: Resolve> Resolve for Option<T> {
|
|
||||||
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
match self {
|
|
||||||
Some(t) => t.resolve(resolver),
|
|
||||||
None => Ok(Type::Empty),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Resolve> Resolve for Box<T> {
|
|
||||||
fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<Type> {
|
|
||||||
self.as_mut().resolve(resolver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use error::{Error, TyResult};
|
|
||||||
pub mod error {
|
|
||||||
use super::Type;
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
pub type TyResult<T> = Result<T, Error>;
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
StackUnderflow,
|
|
||||||
// types
|
|
||||||
TypeMismatch { want: Type, got: Type },
|
|
||||||
// modules
|
|
||||||
NonUniqueInModule(String),
|
|
||||||
// lifetimes
|
|
||||||
Uninitialized(String, usize),
|
|
||||||
ImmutableAssign(String, usize),
|
|
||||||
Unbound(String),
|
|
||||||
}
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::StackUnderflow => "Stack underflow in Resolver".fmt(f),
|
|
||||||
Error::TypeMismatch { want, got } => {
|
|
||||||
write!(f, "Type error: {want} != {got}")
|
|
||||||
}
|
|
||||||
Error::ImmutableAssign(name, index) => {
|
|
||||||
write!(f, "Cannot mutate immutable variable {name}(#{index})")
|
|
||||||
}
|
|
||||||
Error::Uninitialized(name, index) => {
|
|
||||||
write!(f, "{name}(#{index}) was accessed before initialization")
|
|
||||||
}
|
|
||||||
Error::Unbound(name) => write!(f, "{name} not bound before use."),
|
|
||||||
Error::NonUniqueInModule(name) => {
|
|
||||||
write!(f, "Name {name} not unique at module scope!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
mod token {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod ast {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod lexer {
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use crate::{lexer::Lexer, token::preamble::*};
|
|
||||||
|
|
||||||
macro test_lexer_output_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
|
||||||
#[test]
|
|
||||||
fn $f() {$(
|
|
||||||
assert_eq!(
|
|
||||||
Lexer::new($test)
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.unwrap().ty())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
dbg!($expect)
|
|
||||||
);
|
|
||||||
)*}
|
|
||||||
)*}
|
|
||||||
|
|
||||||
macro test_lexer_data_type ($($f:ident {$($test:expr => $expect:expr),*$(,)?})*) {$(
|
|
||||||
#[test]
|
|
||||||
fn $f() {$(
|
|
||||||
assert_eq!(
|
|
||||||
Lexer::new($test)
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.unwrap().into_data())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
dbg!($expect)
|
|
||||||
);
|
|
||||||
)*}
|
|
||||||
)*}
|
|
||||||
|
|
||||||
/// Convert an `[ expr, ... ]` into a `[ *, ... ]`
|
|
||||||
macro td ($($id:expr),*) {
|
|
||||||
[$($id.into()),*]
|
|
||||||
}
|
|
||||||
|
|
||||||
mod ident {
|
|
||||||
use super::*;
|
|
||||||
macro ident ($($id:literal),*) {
|
|
||||||
[$(Data::Identifier($id.into())),*]
|
|
||||||
}
|
|
||||||
test_lexer_data_type! {
|
|
||||||
underscore { "_ _" => ident!["_", "_"] }
|
|
||||||
unicode { "_ε ε_" => ident!["_ε", "ε_"] }
|
|
||||||
many_underscore { "____________________________________" =>
|
|
||||||
ident!["____________________________________"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod keyword {
|
|
||||||
use super::*;
|
|
||||||
macro kw($($k:ident),*) {
|
|
||||||
[ $(Type::Keyword(Keyword::$k),)* ]
|
|
||||||
}
|
|
||||||
test_lexer_output_type! {
|
|
||||||
kw_break { "break break" => kw![Break, Break] }
|
|
||||||
kw_continue { "continue continue" => kw![Continue, Continue] }
|
|
||||||
kw_else { "else else" => kw![Else, Else] }
|
|
||||||
kw_false { "false false" => kw![False, False] }
|
|
||||||
kw_for { "for for" => kw![For, For] }
|
|
||||||
kw_fn { "fn fn" => kw![Fn, Fn] }
|
|
||||||
kw_if { "if if" => kw![If, If] }
|
|
||||||
kw_in { "in in" => kw![In, In] }
|
|
||||||
kw_let { "let let" => kw![Let, Let] }
|
|
||||||
kw_return { "return return" => kw![Return, Return] }
|
|
||||||
kw_true { "true true" => kw![True, True] }
|
|
||||||
kw_while { "while while" => kw![While, While] }
|
|
||||||
keywords { "break continue else false for fn if in let return true while" =>
|
|
||||||
kw![Break, Continue, Else, False, For, Fn, If, In, Let, Return, True, While] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod integer {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_data_type! {
|
|
||||||
hex {
|
|
||||||
"0x0 0x1 0x15 0x2100 0x8000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
dec {
|
|
||||||
"0d0 0d1 0d21 0d8448 0d32768" =>
|
|
||||||
td![0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
oct {
|
|
||||||
"0o0 0o1 0o25 0o20400 0o100000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
bin {
|
|
||||||
"0b0 0b1 0b10101 0b10000100000000 0b1000000000000000" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
baseless {
|
|
||||||
"0 1 21 8448 32768" =>
|
|
||||||
td![0x0, 0x1, 0x15, 0x2100, 0x8000]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod string {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_data_type! {
|
|
||||||
empty_string {
|
|
||||||
"\"\"" =>
|
|
||||||
td![String::from("")]
|
|
||||||
}
|
|
||||||
unicode_string {
|
|
||||||
"\"I 💙 🦈!\"" =>
|
|
||||||
td![String::from("I 💙 🦈!")]
|
|
||||||
}
|
|
||||||
escape_string {
|
|
||||||
" \"This is a shark: \\u{1f988}\" " =>
|
|
||||||
td![String::from("This is a shark: 🦈")]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod punct {
|
|
||||||
use super::*;
|
|
||||||
test_lexer_output_type! {
|
|
||||||
l_curly { "{ {" => [ Type::LCurly, Type::LCurly ] }
|
|
||||||
r_curly { "} }" => [ Type::RCurly, Type::RCurly ] }
|
|
||||||
l_brack { "[ [" => [ Type::LBrack, Type::LBrack ] }
|
|
||||||
r_brack { "] ]" => [ Type::RBrack, Type::RBrack ] }
|
|
||||||
l_paren { "( (" => [ Type::LParen, Type::LParen ] }
|
|
||||||
r_paren { ") )" => [ Type::RParen, Type::RParen ] }
|
|
||||||
amp { "& &" => [ Type::Amp, Type::Amp ] }
|
|
||||||
amp_amp { "&& &&" => [ Type::AmpAmp, Type::AmpAmp ] }
|
|
||||||
amp_eq { "&= &=" => [ Type::AmpEq, Type::AmpEq ] }
|
|
||||||
arrow { "-> ->" => [ Type::Arrow, Type::Arrow] }
|
|
||||||
at { "@ @" => [ Type::At, Type::At] }
|
|
||||||
backslash { "\\ \\" => [ Type::Backslash, Type::Backslash] }
|
|
||||||
bang { "! !" => [ Type::Bang, Type::Bang] }
|
|
||||||
bangbang { "!! !!" => [ Type::BangBang, Type::BangBang] }
|
|
||||||
bangeq { "!= !=" => [ Type::BangEq, Type::BangEq] }
|
|
||||||
bar { "| |" => [ Type::Bar, Type::Bar] }
|
|
||||||
barbar { "|| ||" => [ Type::BarBar, Type::BarBar] }
|
|
||||||
bareq { "|= |=" => [ Type::BarEq, Type::BarEq] }
|
|
||||||
colon { ": :" => [ Type::Colon, Type::Colon] }
|
|
||||||
comma { ", ," => [ Type::Comma, Type::Comma] }
|
|
||||||
dot { ". ." => [ Type::Dot, Type::Dot] }
|
|
||||||
dotdot { ".. .." => [ Type::DotDot, Type::DotDot] }
|
|
||||||
dotdoteq { "..= ..=" => [ Type::DotDotEq, Type::DotDotEq] }
|
|
||||||
eq { "= =" => [ Type::Eq, Type::Eq] }
|
|
||||||
eqeq { "== ==" => [ Type::EqEq, Type::EqEq] }
|
|
||||||
fatarrow { "=> =>" => [ Type::FatArrow, Type::FatArrow] }
|
|
||||||
grave { "` `" => [ Type::Grave, Type::Grave] }
|
|
||||||
gt { "> >" => [ Type::Gt, Type::Gt] }
|
|
||||||
gteq { ">= >=" => [ Type::GtEq, Type::GtEq] }
|
|
||||||
gtgt { ">> >>" => [ Type::GtGt, Type::GtGt] }
|
|
||||||
gtgteq { ">>= >>=" => [ Type::GtGtEq, Type::GtGtEq] }
|
|
||||||
hash { "# #" => [ Type::Hash, Type::Hash] }
|
|
||||||
lt { "< <" => [ Type::Lt, Type::Lt] }
|
|
||||||
lteq { "<= <=" => [ Type::LtEq, Type::LtEq] }
|
|
||||||
ltlt { "<< <<" => [ Type::LtLt, Type::LtLt] }
|
|
||||||
ltlteq { "<<= <<=" => [ Type::LtLtEq, Type::LtLtEq] }
|
|
||||||
minus { "- -" => [ Type::Minus, Type::Minus] }
|
|
||||||
minuseq { "-= -=" => [ Type::MinusEq, Type::MinusEq] }
|
|
||||||
plus { "+ +" => [ Type::Plus, Type::Plus] }
|
|
||||||
pluseq { "+= +=" => [ Type::PlusEq, Type::PlusEq] }
|
|
||||||
question { "? ?" => [ Type::Question, Type::Question] }
|
|
||||||
rem { "% %" => [ Type::Rem, Type::Rem] }
|
|
||||||
remeq { "%= %=" => [ Type::RemEq, Type::RemEq] }
|
|
||||||
semi { "; ;" => [ Type::Semi, Type::Semi] }
|
|
||||||
slash { "/ /" => [ Type::Slash, Type::Slash] }
|
|
||||||
slasheq { "/= /=" => [ Type::SlashEq, Type::SlashEq] }
|
|
||||||
star { "* *" => [ Type::Star, Type::Star] }
|
|
||||||
stareq { "*= *=" => [ Type::StarEq, Type::StarEq] }
|
|
||||||
tilde { "~ ~" => [ Type::Tilde, Type::Tilde] }
|
|
||||||
xor { "^ ^" => [ Type::Xor, Type::Xor] }
|
|
||||||
xoreq { "^= ^=" => [ Type::XorEq, Type::XorEq] }
|
|
||||||
xorxor { "^^ ^^" => [ Type::XorXor, Type::XorXor] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod parser {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
mod interpreter {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
// Calculate Fibonacci numbers
|
// Calculate Fibonacci numbers
|
||||||
|
|
||||||
fn main() -> i128 {
|
fn main() {
|
||||||
let num = 10;
|
for num in 0..=30 {
|
||||||
print("fib(", num, "): ", fib(num));
|
print("fib(", num, ") = ", fib(num))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements the classic recursive definition of fib()
|
/// Implements the classic recursive definition of fib()
|
||||||
fn fib(a: i128) -> i128 {
|
fn fib(a: i64) -> i64 {
|
||||||
if a > 1 {
|
if a > 1 {
|
||||||
fib(a - 1) + fib(a - 2)
|
fib(a - 1) + fib(a - 2)
|
||||||
} else {
|
} else {
|
||||||
11
stdlib/lib.cl
Normal file
11
stdlib/lib.cl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//! # The Conlang Standard Library
|
||||||
|
|
||||||
|
/// 32-bit signed integer type
|
||||||
|
#[intrinsic = "i32"]
|
||||||
|
type i32;
|
||||||
|
|
||||||
|
/// 32-bit unsigned integer type
|
||||||
|
#[intrinsic = "u32"]
|
||||||
|
type u32;
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user