Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc3cbbf450 | |||
| 2c36ccc0cf | |||
| 265db668ed | |||
| fa51f14db5 | |||
| 3b0190b389 | |||
| 21c9909f0c | |||
| 290ede2fa3 | |||
| 2091cce570 | |||
| 902494e95a | |||
| a213c7f70a | |||
| 8dfddb739e | |||
| a31d285d99 | |||
| a036ce260d | |||
| 4a52d2bc6a | |||
| 614d20ea2c | |||
| 7b40ddc845 | |||
| bdf0bb68ca | |||
| 8ee318f26b | |||
| ba148ef5de | |||
| 8cbe570811 | |||
| 66c29d601c | |||
| 9f9a21b4c3 | |||
| 2cdf112aa6 | |||
| af35dd1bb3 | |||
| ecde44910f | |||
| a74cd0b8ac | |||
| 2eade74d3a | |||
| 9cae7e4eb8 | |||
| a07312bf92 | |||
| 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
|
||||
---
|
||||
# Feature Progress
|
||||
<!-- Describe the steps for implementing this feature in libconlang -->
|
||||
<!-- Describe the steps for implementing this feature -->
|
||||
- [ ] <!-- Step 1 of implementing a feature -->
|
||||
|
||||
# Feature description
|
||||
@@ -21,4 +21,4 @@ since it most closely matches what I'm currently aiming for
|
||||
-->
|
||||
```rust
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -1,10 +1,19 @@
|
||||
[workspace]
|
||||
members = ["libconlang", "cl-repl"]
|
||||
members = [
|
||||
"cl-repl",
|
||||
"cl-typeck",
|
||||
"cl-interpret",
|
||||
"cl-structures",
|
||||
"cl-token",
|
||||
"cl-ast",
|
||||
"cl-parser",
|
||||
"cl-lexer",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
repository = "https://git.soft.fish/j/Conlang"
|
||||
version = "0.0.3"
|
||||
version = "0.0.5"
|
||||
authors = ["John Breaux <j@soft.fish>"]
|
||||
edition = "2021"
|
||||
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" }
|
||||
@@ -5,7 +5,10 @@ mod display {
|
||||
//! Implements [Display] for [AST](super::super) Types
|
||||
use super::*;
|
||||
pub use delimiters::*;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Display, Write},
|
||||
};
|
||||
mod delimiters {
|
||||
#![allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -13,6 +16,8 @@ mod display {
|
||||
pub open: &'t str,
|
||||
pub close: &'t str,
|
||||
}
|
||||
/// Delimits with braces decorated with spaces `" {n"`, ..., `"\n}"`
|
||||
pub const SPACED_BRACES: Delimiters = Delimiters { open: " {\n", close: "\n}" };
|
||||
/// Delimits with braces on separate lines `{\n`, ..., `\n}`
|
||||
pub const BRACES: Delimiters = Delimiters { open: "{\n", close: "\n}" };
|
||||
/// Delimits with parentheses on separate lines `{\n`, ..., `\n}`
|
||||
@@ -190,9 +195,7 @@ mod display {
|
||||
match self {
|
||||
StructKind::Empty => ';'.fmt(f),
|
||||
StructKind::Tuple(v) => delimit(separate(v, ", "), INLINE_PARENS)(f),
|
||||
StructKind::Struct(v) => {
|
||||
delimit(separate(v, ",\n"), Delimiters { open: " {\n", ..BRACES })(f)
|
||||
}
|
||||
StructKind::Struct(v) => delimit(separate(v, ",\n"), SPACED_BRACES)(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,8 +214,8 @@ mod display {
|
||||
impl Display for EnumKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EnumKind::NoVariants => todo!(),
|
||||
EnumKind::Variants(v) => separate(v, ", ")(f),
|
||||
EnumKind::NoVariants => ';'.fmt(f),
|
||||
EnumKind::Variants(v) => delimit(separate(v, ",\n"), SPACED_BRACES)(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,9 +229,9 @@ mod display {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VariantKind::Plain => Ok(()),
|
||||
VariantKind::CLike(n) => n.fmt(f),
|
||||
VariantKind::CLike(n) => write!(f, " = {n}"),
|
||||
VariantKind::Tuple(v) => delimit(separate(v, ", "), INLINE_PARENS)(f),
|
||||
VariantKind::Struct(v) => delimit(separate(v, ",\n"), BRACES)(f),
|
||||
VariantKind::Struct(v) => delimit(separate(v, ", "), INLINE_BRACES)(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,13 +310,16 @@ mod display {
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.kind {
|
||||
self.kind.fmt(f)
|
||||
}
|
||||
}
|
||||
impl Display for ExprKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExprKind::Assign(v) => v.fmt(f),
|
||||
ExprKind::Binary(v) => v.fmt(f),
|
||||
ExprKind::Unary(v) => v.fmt(f),
|
||||
ExprKind::Index(v) => v.fmt(f),
|
||||
ExprKind::Call(v) => v.fmt(f),
|
||||
ExprKind::Member(v) => v.fmt(f),
|
||||
ExprKind::Path(v) => v.fmt(f),
|
||||
ExprKind::Literal(v) => v.fmt(f),
|
||||
ExprKind::Array(v) => v.fmt(f),
|
||||
@@ -334,8 +340,8 @@ mod display {
|
||||
}
|
||||
impl Display for Assign {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head, op, tail } = self;
|
||||
write!(f, "{head} {op} {tail}")
|
||||
let Self { kind, parts } = self;
|
||||
write!(f, "{} {kind} {}", parts.0, parts.1)
|
||||
}
|
||||
}
|
||||
impl Display for AssignKind {
|
||||
@@ -358,12 +364,13 @@ mod display {
|
||||
}
|
||||
impl Display for Binary {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head, tail } = self;
|
||||
write!(f, "{head}")?;
|
||||
for (kind, expr) in tail {
|
||||
write!(f, " {kind} {expr}")?;
|
||||
let Self { kind, parts } = self;
|
||||
let (head, tail) = parts.borrow();
|
||||
match kind {
|
||||
BinaryKind::Dot => write!(f, "{head}{kind}{tail}"),
|
||||
BinaryKind::Call => write!(f, "{head}{tail}"),
|
||||
_ => write!(f, "{head} {kind} {tail}"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Display for BinaryKind {
|
||||
@@ -391,17 +398,15 @@ mod display {
|
||||
BinaryKind::Div => "/",
|
||||
BinaryKind::Rem => "%",
|
||||
BinaryKind::Dot => ".",
|
||||
BinaryKind::Call => "()",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
impl Display for Unary {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { ops: kinds, tail } = self;
|
||||
for kind in kinds {
|
||||
kind.fmt(f)?
|
||||
}
|
||||
tail.fmt(f)
|
||||
let Self { kind, tail } = self;
|
||||
write!(f, "{kind}{tail}")
|
||||
}
|
||||
}
|
||||
impl Display for UnaryKind {
|
||||
@@ -416,29 +421,11 @@ mod display {
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
impl Display for Call {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { callee, args } = self;
|
||||
callee.fmt(f)?;
|
||||
for args in args {
|
||||
args.fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Display for Tuple {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
delimit(separate(&self.exprs, ", "), INLINE_PARENS)(f)
|
||||
}
|
||||
}
|
||||
impl Display for Member {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head: parent, tail: children } = self;
|
||||
write!(f, "{parent}.")?;
|
||||
separate(children, ".")(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Display for Index {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head, indices } = self;
|
||||
@@ -449,11 +436,6 @@ mod display {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Display for Indices {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
delimit(separate(&self.exprs, ", "), INLINE_SQUARE)(f)
|
||||
}
|
||||
}
|
||||
impl Display for Path {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { absolute, parts } = self;
|
||||
@@ -563,107 +545,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 {
|
||||
//! Converts between major enums and enum variants
|
||||
use super::*;
|
||||
@@ -725,8 +606,6 @@ mod convert {
|
||||
Assign => ExprKind::Assign,
|
||||
Binary => ExprKind::Binary,
|
||||
Unary => ExprKind::Unary,
|
||||
Call => ExprKind::Call,
|
||||
Member => ExprKind::Member,
|
||||
Index => ExprKind::Index,
|
||||
Path => ExprKind::Path,
|
||||
Literal => ExprKind::Literal,
|
||||
@@ -747,18 +626,7 @@ mod convert {
|
||||
bool => Literal::Bool,
|
||||
char => Literal::Char,
|
||||
u128 => Literal::Int,
|
||||
&str => Literal::String,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tuple> for Indices {
|
||||
fn from(value: Tuple) -> Self {
|
||||
Self { exprs: value.exprs }
|
||||
}
|
||||
}
|
||||
impl From<Indices> for Tuple {
|
||||
fn from(value: Indices) -> Self {
|
||||
Self { exprs: value.exprs }
|
||||
String => Literal::String,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
//! Contains definitions of AST Nodes, to be derived by a [parser](super::parser).
|
||||
//! Contains definitions of Conlang AST Nodes.
|
||||
//!
|
||||
//! # Notable nodes
|
||||
//! - [Item] and [ItemKind]: Top-level constructs
|
||||
@@ -9,10 +9,15 @@
|
||||
//! - [AssignKind], [BinaryKind], and [UnaryKind] operators
|
||||
//! - [Ty] and [TyKind]: Type qualifiers
|
||||
//! - [Path]: Path expressions
|
||||
use crate::common::*;
|
||||
#![warn(clippy::all)]
|
||||
#![feature(decl_macro)]
|
||||
|
||||
use cl_structures::span::*;
|
||||
|
||||
pub mod ast_impl;
|
||||
pub mod format;
|
||||
|
||||
/// Whether a binding ([Static] or [Let]) or reference is mutable or not
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Mutability {
|
||||
#[default]
|
||||
@@ -20,6 +25,7 @@ pub enum Mutability {
|
||||
Mut,
|
||||
}
|
||||
|
||||
/// Whether an [Item] is visible outside of the current [Module]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Visibility {
|
||||
#[default]
|
||||
@@ -27,23 +33,26 @@ pub enum Visibility {
|
||||
Public,
|
||||
}
|
||||
|
||||
/// A list of [Item]s
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct File {
|
||||
pub items: Vec<Item>,
|
||||
}
|
||||
|
||||
// Metadata decorators
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Attrs {
|
||||
pub meta: Vec<Meta>,
|
||||
}
|
||||
|
||||
/// A metadata decorator
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Meta {
|
||||
pub name: Identifier,
|
||||
pub kind: MetaKind,
|
||||
}
|
||||
|
||||
/// Information attached to [Meta]data
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MetaKind {
|
||||
Plain,
|
||||
@@ -52,7 +61,7 @@ pub enum MetaKind {
|
||||
}
|
||||
|
||||
// Items
|
||||
/// Stores an [ItemKind] and associated metadata
|
||||
/// Anything that can appear at the top level of a [File]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Item {
|
||||
pub extents: Span,
|
||||
@@ -61,36 +70,37 @@ pub struct Item {
|
||||
pub kind: ItemKind,
|
||||
}
|
||||
|
||||
/// Stores a concrete Item
|
||||
/// What kind of [Item] is this?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ItemKind {
|
||||
// TODO: Import declaration ("use") item
|
||||
// TODO: Trait declaration ("trait") item?
|
||||
/// A [module](Module)
|
||||
Module(Module),
|
||||
/// A [type alias](Alias)
|
||||
Alias(Alias),
|
||||
/// An [enumerated type](Enum), with a discriminant and optional data
|
||||
Enum(Enum),
|
||||
/// A [structure](Struct)
|
||||
Struct(Struct),
|
||||
/// A [constant](Const)
|
||||
Const(Const),
|
||||
/// A [static](Static) variable
|
||||
Static(Static),
|
||||
/// A [module](Module)
|
||||
Module(Module),
|
||||
/// A [function definition](Function)
|
||||
Function(Function),
|
||||
/// A [structure](Struct)
|
||||
Struct(Struct),
|
||||
/// An [enumerated type](Enum)
|
||||
Enum(Enum),
|
||||
/// An [implementation](Impl)
|
||||
Impl(Impl),
|
||||
}
|
||||
|
||||
/// An alias to another [Ty]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Alias {
|
||||
pub to: Box<Ty>,
|
||||
pub to: Identifier,
|
||||
pub from: Option<Box<Ty>>,
|
||||
}
|
||||
|
||||
/// Stores a `const` value
|
||||
/// A compile-time constant
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Const {
|
||||
pub name: Identifier,
|
||||
@@ -98,7 +108,7 @@ pub struct Const {
|
||||
pub init: Box<Expr>,
|
||||
}
|
||||
|
||||
/// Stores a `static` variable
|
||||
/// A `static` variable
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Static {
|
||||
pub mutable: Mutability,
|
||||
@@ -107,20 +117,21 @@ pub struct Static {
|
||||
pub init: Box<Expr>,
|
||||
}
|
||||
|
||||
/// Stores a collection of [Items](Item)
|
||||
/// An ordered collection of [Items](Item)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Module {
|
||||
pub name: Identifier,
|
||||
pub kind: ModuleKind,
|
||||
}
|
||||
|
||||
/// The contents of a [Module], if they're in the same file
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ModuleKind {
|
||||
Inline(File),
|
||||
Outline,
|
||||
}
|
||||
|
||||
/// Contains code, and the interface to that code
|
||||
/// Code, and the interface to that code
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Function {
|
||||
pub name: Identifier,
|
||||
@@ -129,6 +140,7 @@ pub struct Function {
|
||||
pub rety: Option<Box<Ty>>,
|
||||
}
|
||||
|
||||
/// A single parameter for a [Function]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Param {
|
||||
pub mutability: Mutability,
|
||||
@@ -136,12 +148,14 @@ pub struct Param {
|
||||
pub ty: Box<Ty>,
|
||||
}
|
||||
|
||||
/// A user-defined product type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Struct {
|
||||
pub name: Identifier,
|
||||
pub kind: StructKind,
|
||||
}
|
||||
|
||||
/// Either a [Struct]'s [StructMember]s or tuple [Ty]pes, if present.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StructKind {
|
||||
Empty,
|
||||
@@ -149,6 +163,7 @@ pub enum StructKind {
|
||||
Struct(Vec<StructMember>),
|
||||
}
|
||||
|
||||
/// The [Visibility], [Identifier], and [Ty]pe of a single [Struct] member
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StructMember {
|
||||
pub vis: Visibility,
|
||||
@@ -156,12 +171,14 @@ pub struct StructMember {
|
||||
pub ty: Ty,
|
||||
}
|
||||
|
||||
/// A user-defined sum type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Enum {
|
||||
pub name: Identifier,
|
||||
pub kind: EnumKind,
|
||||
}
|
||||
|
||||
/// An [Enum]'s [Variant]s, if it has a variant block
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EnumKind {
|
||||
/// Represents an enum with no variants
|
||||
@@ -169,12 +186,14 @@ pub enum EnumKind {
|
||||
Variants(Vec<Variant>),
|
||||
}
|
||||
|
||||
/// A single [Enum] variant
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Variant {
|
||||
pub name: Identifier,
|
||||
pub kind: VariantKind,
|
||||
}
|
||||
|
||||
/// Whether the [Variant] has a C-like constant value, a tuple, or [StructMember]s
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum VariantKind {
|
||||
Plain,
|
||||
@@ -183,6 +202,7 @@ pub enum VariantKind {
|
||||
Struct(Vec<StructMember>),
|
||||
}
|
||||
|
||||
/// Sub-[items](Item) (associated functions, etc.) for a [Ty]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Impl {
|
||||
pub target: Ty,
|
||||
@@ -196,13 +216,14 @@ pub enum ImplKind {
|
||||
Trait { impl_trait: Path, for_type: Box<Ty> },
|
||||
}
|
||||
|
||||
/// # Static Type Information
|
||||
/// A type expression
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Ty {
|
||||
pub extents: Span,
|
||||
pub kind: TyKind,
|
||||
}
|
||||
|
||||
/// Information about a [Ty]pe expression
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TyKind {
|
||||
Never,
|
||||
@@ -214,29 +235,34 @@ pub enum TyKind {
|
||||
Fn(TyFn),
|
||||
}
|
||||
|
||||
/// A tuple of [Ty]pes
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TyTuple {
|
||||
pub types: Vec<Ty>,
|
||||
}
|
||||
|
||||
/// A [Ty]pe-reference expression as (number of `&`, [Path])
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TyRef {
|
||||
pub count: u16,
|
||||
pub to: Path,
|
||||
}
|
||||
|
||||
/// The args and return value for a function pointer [Ty]pe
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TyFn {
|
||||
pub args: TyTuple,
|
||||
pub rety: Option<Box<Ty>>,
|
||||
}
|
||||
|
||||
// Path
|
||||
/// A path to an [Item] in the [Module] tree
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Path {
|
||||
pub absolute: bool,
|
||||
pub parts: Vec<PathPart>,
|
||||
}
|
||||
|
||||
/// A single component of a [Path]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PathPart {
|
||||
SuperKw,
|
||||
@@ -245,10 +271,11 @@ pub enum PathPart {
|
||||
}
|
||||
|
||||
// TODO: Capture token?
|
||||
/// A name
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Identifier(pub String);
|
||||
|
||||
/// Stores an abstract statement, and associated metadata
|
||||
/// An abstract statement, and associated metadata
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Stmt {
|
||||
pub extents: Span,
|
||||
@@ -256,12 +283,14 @@ pub struct Stmt {
|
||||
pub semi: Semi,
|
||||
}
|
||||
|
||||
/// Whether or not a [Stmt] is followed by a semicolon
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Semi {
|
||||
Terminated,
|
||||
Unterminated,
|
||||
}
|
||||
|
||||
/// Whether the [Stmt] is a [Let], [Item], or [Expr] statement
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum StmtKind {
|
||||
Empty,
|
||||
@@ -270,6 +299,7 @@ pub enum StmtKind {
|
||||
Expr(Box<Expr>),
|
||||
}
|
||||
|
||||
/// A local variable declaration [Stmt]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Let {
|
||||
pub mutable: Mutability,
|
||||
@@ -278,25 +308,22 @@ pub struct Let {
|
||||
pub init: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// Stores an abstract expression, and associated metadata
|
||||
/// An expression, the beating heart of the language
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Expr {
|
||||
pub extents: Span,
|
||||
pub kind: ExprKind,
|
||||
}
|
||||
|
||||
/// Any of the different [Expr]essions
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ExprKind {
|
||||
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
|
||||
Assign(Box<Assign>),
|
||||
Assign(Assign),
|
||||
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
|
||||
Binary(Binary),
|
||||
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
|
||||
Unary(Unary),
|
||||
/// A [Member] access expression: [`Expr`] (`.` [`Expr`])+
|
||||
Member(Member),
|
||||
/// A [Call] expression, with arguments: a(foo, bar)
|
||||
Call(Call),
|
||||
/// An Array [Index] expression: a[10, 20, 30]
|
||||
Index(Index),
|
||||
/// A [path expression](Path): `::`? [PathPart] (`::` [PathPart])*
|
||||
@@ -332,11 +359,11 @@ pub enum ExprKind {
|
||||
Continue(Continue),
|
||||
}
|
||||
|
||||
/// An [Assign]ment expression: [`Expr`] ([`AssignKind`] [`Expr`])\+
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Assign {
|
||||
pub head: Expr,
|
||||
pub op: AssignKind,
|
||||
pub tail: Box<Expr>,
|
||||
pub kind: AssignKind,
|
||||
pub parts: Box<(ExprKind, ExprKind)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -355,12 +382,14 @@ pub enum AssignKind {
|
||||
Rem,
|
||||
}
|
||||
|
||||
/// A [Binary] expression: [`Expr`] ([`BinaryKind`] [`Expr`])\+
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Binary {
|
||||
pub head: Box<Expr>,
|
||||
pub tail: Vec<(BinaryKind, Expr)>,
|
||||
pub kind: BinaryKind,
|
||||
pub parts: Box<(ExprKind, ExprKind)>,
|
||||
}
|
||||
|
||||
/// A [Binary] operator
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BinaryKind {
|
||||
Lt,
|
||||
@@ -385,15 +414,18 @@ pub enum BinaryKind {
|
||||
Div,
|
||||
Rem,
|
||||
Dot,
|
||||
Call,
|
||||
}
|
||||
|
||||
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Unary {
|
||||
pub ops: Vec<UnaryKind>,
|
||||
pub tail: Box<Expr>,
|
||||
pub kind: UnaryKind,
|
||||
pub tail: Box<ExprKind>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
/// A [Unary] operator
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum UnaryKind {
|
||||
Deref,
|
||||
Neg,
|
||||
@@ -403,36 +435,14 @@ pub enum UnaryKind {
|
||||
/// Unused
|
||||
Tilde,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Member {
|
||||
pub head: Box<Expr>,
|
||||
pub tail: Vec<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MemberKind {
|
||||
Dot,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Call {
|
||||
pub callee: Box<Expr>,
|
||||
pub args: Vec<Tuple>,
|
||||
}
|
||||
|
||||
/// Index operator: Member (`[` Expr `]`)*
|
||||
/// A repeated [Index] expression: a[10, 20, 30][40, 50, 60]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Index {
|
||||
pub head: Box<Expr>,
|
||||
pub indices: Vec<Indices>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Indices {
|
||||
pub exprs: Vec<Expr>,
|
||||
pub head: Box<ExprKind>,
|
||||
pub indices: Vec<Expr>,
|
||||
}
|
||||
|
||||
/// A [Literal]: 0x42, 1e123, 2.4, "Hello"
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Literal {
|
||||
Bool(bool),
|
||||
@@ -441,39 +451,47 @@ pub enum Literal {
|
||||
String(String),
|
||||
}
|
||||
|
||||
/// An [Array] literal: `[` [`Expr`] (`,` [`Expr`])\* `]`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Array {
|
||||
pub values: Vec<Expr>,
|
||||
}
|
||||
|
||||
/// An Array literal constructed with [repeat syntax](ArrayRep)
|
||||
/// `[` [Expr] `;` [Literal] `]`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ArrayRep {
|
||||
pub value: Box<Expr>,
|
||||
pub repeat: Box<Expr>,
|
||||
pub value: Box<ExprKind>,
|
||||
pub repeat: Box<ExprKind>,
|
||||
}
|
||||
|
||||
/// An address-of expression: `&` `mut`? [`Expr`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AddrOf {
|
||||
pub count: usize,
|
||||
pub mutable: Mutability,
|
||||
pub expr: Box<Expr>,
|
||||
pub expr: Box<ExprKind>,
|
||||
}
|
||||
|
||||
/// A [Block] expression: `{` [`Stmt`]\* [`Expr`]? `}`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Block {
|
||||
pub stmts: Vec<Stmt>,
|
||||
}
|
||||
|
||||
/// A [Grouping](Group) expression `(` [`Expr`] `)`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Group {
|
||||
pub expr: Box<Expr>,
|
||||
pub expr: Box<ExprKind>,
|
||||
}
|
||||
|
||||
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Tuple {
|
||||
pub exprs: Vec<Expr>,
|
||||
}
|
||||
|
||||
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct While {
|
||||
pub cond: Box<Expr>,
|
||||
@@ -481,6 +499,7 @@ pub struct While {
|
||||
pub fail: Else,
|
||||
}
|
||||
|
||||
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct If {
|
||||
pub cond: Box<Expr>,
|
||||
@@ -488,6 +507,7 @@ pub struct If {
|
||||
pub fail: Else,
|
||||
}
|
||||
|
||||
/// A [For] expression: `for` Pattern `in` [`Expr`] [`Block`] [`Else`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct For {
|
||||
pub bind: Identifier, // TODO: Patterns?
|
||||
@@ -496,20 +516,24 @@ pub struct For {
|
||||
pub fail: Else,
|
||||
}
|
||||
|
||||
/// The (optional) `else` clause of a [While], [If], or [For] expression
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Else {
|
||||
pub body: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// A [Break] expression: `break` [`Expr`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Break {
|
||||
pub body: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// A [Return] expression `return` [`Expr`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Return {
|
||||
pub body: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// A continue expression: `continue`
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Continue;
|
||||
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)
|
||||
}
|
||||
}
|
||||
447
cl-interpret/src/interpret.rs
Normal file
447
cl-interpret/src/interpret.rs
Normal file
@@ -0,0 +1,447 @@
|
||||
//! 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 std::borrow::Borrow;
|
||||
|
||||
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 {
|
||||
#[inline]
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { extents: _, kind } = self;
|
||||
kind.interpret(env)
|
||||
}
|
||||
}
|
||||
impl Interpret for ExprKind {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
match self {
|
||||
ExprKind::Assign(v) => v.interpret(env),
|
||||
ExprKind::Binary(v) => v.interpret(env),
|
||||
ExprKind::Unary(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 { kind: op, parts } = self;
|
||||
let (head, tail) = parts.borrow();
|
||||
// Resolve the head pattern
|
||||
let head = match &head {
|
||||
ExprKind::Path(Path { parts, .. }) if parts.len() == 1 => {
|
||||
match parts.last().expect("parts should not be empty") {
|
||||
PathPart::SuperKw => Err(Error::NotAssignable)?,
|
||||
PathPart::SelfKw => todo!("Assignment to `self`"),
|
||||
PathPart::Ident(Identifier(s)) => s,
|
||||
}
|
||||
}
|
||||
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)?,
|
||||
};
|
||||
// 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 { kind, parts } = self;
|
||||
let (head, tail) = parts.borrow();
|
||||
|
||||
let head = head.interpret(env)?;
|
||||
|
||||
// Short-circuiting ops
|
||||
match kind {
|
||||
BinaryKind::LogAnd => {
|
||||
return if head.truthy()? {
|
||||
tail.interpret(env)
|
||||
} else {
|
||||
Ok(head)
|
||||
}; // Short circuiting
|
||||
}
|
||||
BinaryKind::LogOr => {
|
||||
return if !head.truthy()? {
|
||||
tail.interpret(env)
|
||||
} else {
|
||||
Ok(head)
|
||||
}; // Short circuiting
|
||||
}
|
||||
BinaryKind::LogXor => {
|
||||
return Ok(ConValue::Bool(
|
||||
head.truthy()? ^ tail.interpret(env)?.truthy()?,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let tail = tail.interpret(env)?;
|
||||
match kind {
|
||||
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!"),
|
||||
BinaryKind::Call => match tail {
|
||||
ConValue::Empty => head.call(env, &[]),
|
||||
ConValue::Tuple(args) => head.call(env, &args),
|
||||
_ => Err(Error::TypeError),
|
||||
},
|
||||
_ => Ok(head),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for Unary {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Unary { kind, tail } = self;
|
||||
let operand = tail.interpret(env)?;
|
||||
match kind {
|
||||
UnaryKind::Deref => env.call("deref", &[operand]),
|
||||
UnaryKind::Neg => env.call("neg", &[operand]),
|
||||
UnaryKind::Not => env.call("not", &[operand]),
|
||||
UnaryKind::At => {
|
||||
println!("{operand}");
|
||||
Ok(operand)
|
||||
}
|
||||
UnaryKind::Tilde => unimplemented!("Tilde operator"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Interpret for Index {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { head, indices } = self;
|
||||
let mut head = head.interpret(env)?;
|
||||
for index in indices {
|
||||
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))?,
|
||||
))
|
||||
}
|
||||
}
|
||||
614
cl-interpret/src/lib.rs
Normal file
614
cl-interpret/src/lib.rs
Normal file
@@ -0,0 +1,614 @@
|
||||
//! 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;
|
||||
|
||||
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 could not be indexed
|
||||
NotIndexable,
|
||||
/// An array index went out of bounds
|
||||
OobIndex(usize, usize),
|
||||
/// An expression is not assignable
|
||||
NotAssignable,
|
||||
/// 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 => {
|
||||
write!(f, "expression cannot be indexed")
|
||||
}
|
||||
Error::OobIndex(idx, len) => {
|
||||
write!(f, "Index out of bounds: index was {idx}. but len is {len}")
|
||||
}
|
||||
Error::NotAssignable => {
|
||||
write!(f, "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)]
|
||||
use crate::{
|
||||
ast::*,
|
||||
interpreter::{env::Environment, temp_type_impl::ConValue, Interpret},
|
||||
lexer::Lexer,
|
||||
parser::Parser,
|
||||
};
|
||||
use crate::{env::Environment, temp_type_impl::ConValue, Interpret};
|
||||
use cl_ast::*;
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
pub use macros::*;
|
||||
|
||||
mod macros {
|
||||
@@ -49,7 +47,7 @@ mod macros {
|
||||
//! env_eq!(env.x, 10); // like assert_eq! for Environments
|
||||
//! ```
|
||||
#![allow(unused_macros)]
|
||||
use crate::interpreter::IResult;
|
||||
use crate::IResult;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -189,7 +187,7 @@ mod fn_declarations {
|
||||
assert_eval!(env, fn empty_fn() {});
|
||||
// TODO: true equality for functions
|
||||
assert_eq!(
|
||||
"fn empty_fn",
|
||||
"fn empty_fn () {\n \n}",
|
||||
format!(
|
||||
"{}",
|
||||
env.get("empty_fn")
|
||||
@@ -212,7 +210,7 @@ mod fn_declarations {
|
||||
}
|
||||
|
||||
mod operators {
|
||||
use crate::ast::Tuple;
|
||||
use cl_ast::Tuple;
|
||||
|
||||
use super::*;
|
||||
#[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-ident = "1.0.12"
|
||||
@@ -1,10 +1,16 @@
|
||||
//! Converts a text file into tokens
|
||||
use crate::token::preamble::*;
|
||||
#![warn(clippy::all)]
|
||||
#![feature(decl_macro)]
|
||||
use cl_structures::span::Loc;
|
||||
use cl_token::{TokenKind as Kind, *};
|
||||
use std::{
|
||||
iter::Peekable,
|
||||
str::{Chars, FromStr},
|
||||
};
|
||||
use unicode_xid::UnicodeXID;
|
||||
use unicode_ident::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod lexer_iter {
|
||||
//! Iterator over a [`Lexer`], returning [`LResult<Token>`]s
|
||||
@@ -45,7 +51,8 @@ pub mod lexer_iter {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use conlang::lexer::Lexer;
|
||||
/// # use cl_lexer::Lexer;
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// // Read in your code from somewhere
|
||||
/// let some_code = "
|
||||
/// fn main () {
|
||||
@@ -55,16 +62,17 @@ pub mod lexer_iter {
|
||||
/// // Create a lexer over your code
|
||||
/// let mut lexer = Lexer::new(some_code);
|
||||
/// // Scan for a single token
|
||||
/// let first_token = lexer.scan().unwrap();
|
||||
/// let first_token = lexer.scan()?;
|
||||
/// println!("{first_token:?}");
|
||||
/// // Loop over all the rest of the tokens
|
||||
/// for token in lexer {
|
||||
/// # let token: Result<_,()> = Ok(token.unwrap());
|
||||
/// # let token: Result<_,()> = Ok(token?);
|
||||
/// match token {
|
||||
/// Ok(token) => println!("{token:?}"),
|
||||
/// Err(e) => eprintln!("{e:?}"),
|
||||
/// }
|
||||
/// }
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Lexer<'t> {
|
||||
@@ -89,40 +97,40 @@ impl<'t> Lexer<'t> {
|
||||
/// Scans through the text, searching for the next [Token]
|
||||
pub fn scan(&mut self) -> LResult<Token> {
|
||||
match self.skip_whitespace().peek()? {
|
||||
'{' => self.consume()?.produce(Type::LCurly, ()),
|
||||
'}' => self.consume()?.produce(Type::RCurly, ()),
|
||||
'[' => self.consume()?.produce(Type::LBrack, ()),
|
||||
']' => self.consume()?.produce(Type::RBrack, ()),
|
||||
'(' => self.consume()?.produce(Type::LParen, ()),
|
||||
')' => self.consume()?.produce(Type::RParen, ()),
|
||||
'{' => self.consume()?.produce_op(Punct::LCurly),
|
||||
'}' => self.consume()?.produce_op(Punct::RCurly),
|
||||
'[' => self.consume()?.produce_op(Punct::LBrack),
|
||||
']' => self.consume()?.produce_op(Punct::RBrack),
|
||||
'(' => self.consume()?.produce_op(Punct::LParen),
|
||||
')' => self.consume()?.produce_op(Punct::RParen),
|
||||
'&' => self.consume()?.amp(),
|
||||
'@' => self.consume()?.produce(Type::At, ()),
|
||||
'\\' => self.consume()?.produce(Type::Backslash, ()),
|
||||
'@' => self.consume()?.produce_op(Punct::At),
|
||||
'\\' => self.consume()?.produce_op(Punct::Backslash),
|
||||
'!' => self.consume()?.bang(),
|
||||
'|' => self.consume()?.bar(),
|
||||
':' => self.consume()?.colon(),
|
||||
',' => self.consume()?.produce(Type::Comma, ()),
|
||||
',' => self.consume()?.produce_op(Punct::Comma),
|
||||
'.' => self.consume()?.dot(),
|
||||
'=' => self.consume()?.equal(),
|
||||
'`' => self.consume()?.produce(Type::Grave, ()),
|
||||
'`' => self.consume()?.produce_op(Punct::Grave),
|
||||
'>' => self.consume()?.greater(),
|
||||
'#' => self.consume()?.hash(),
|
||||
'<' => self.consume()?.less(),
|
||||
'-' => self.consume()?.minus(),
|
||||
'+' => self.consume()?.plus(),
|
||||
'?' => self.consume()?.produce(Type::Question, ()),
|
||||
'?' => self.consume()?.produce_op(Punct::Question),
|
||||
'%' => self.consume()?.rem(),
|
||||
';' => self.consume()?.produce(Type::Semi, ()),
|
||||
';' => self.consume()?.produce_op(Punct::Semi),
|
||||
'/' => self.consume()?.slash(),
|
||||
'*' => self.consume()?.star(),
|
||||
'~' => self.consume()?.produce(Type::Tilde, ()),
|
||||
'~' => self.consume()?.produce_op(Punct::Tilde),
|
||||
'^' => self.consume()?.xor(),
|
||||
'0' => self.consume()?.int_with_base(),
|
||||
'1'..='9' => self.digits::<10>(),
|
||||
'"' => self.consume()?.string(),
|
||||
'\'' => self.consume()?.character(),
|
||||
'_' => self.identifier(),
|
||||
i if i.is_xid_start() => self.identifier(),
|
||||
i if is_xid_start(i) => self.identifier(),
|
||||
e => {
|
||||
let err = Err(Error::unexpected_char(e, self.line(), self.col()));
|
||||
let _ = self.consume();
|
||||
@@ -149,11 +157,14 @@ impl<'t> Lexer<'t> {
|
||||
.copied()
|
||||
.ok_or(Error::end_of_file(self.line(), self.col()))
|
||||
}
|
||||
fn produce(&mut self, ty: Type, data: impl Into<Data>) -> LResult<Token> {
|
||||
fn produce(&mut self, kind: TokenKind, data: impl Into<TokenData>) -> LResult<Token> {
|
||||
let loc = self.start_loc;
|
||||
self.start_loc = self.current_loc;
|
||||
self.start = self.current;
|
||||
Ok(Token::new(ty, data, loc.0, loc.1))
|
||||
Ok(Token::new(kind, data, loc.0, loc.1))
|
||||
}
|
||||
fn produce_op(&mut self, kind: Punct) -> LResult<Token> {
|
||||
self.produce(TokenKind::Punct(kind), ())
|
||||
}
|
||||
fn skip_whitespace(&mut self) -> &mut Self {
|
||||
while let Ok(c) = self.peek() {
|
||||
@@ -184,120 +195,120 @@ impl<'t> Lexer<'t> {
|
||||
impl<'t> Lexer<'t> {
|
||||
fn amp(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('&') => self.consume()?.produce(Type::AmpAmp, ()),
|
||||
Ok('=') => self.consume()?.produce(Type::AmpEq, ()),
|
||||
_ => self.produce(Type::Amp, ()),
|
||||
Ok('&') => self.consume()?.produce_op(Punct::AmpAmp),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::AmpEq),
|
||||
_ => self.produce_op(Punct::Amp),
|
||||
}
|
||||
}
|
||||
fn bang(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('!') => self.consume()?.produce(Type::BangBang, ()),
|
||||
Ok('=') => self.consume()?.produce(Type::BangEq, ()),
|
||||
_ => self.produce(Type::Bang, ()),
|
||||
Ok('!') => self.consume()?.produce_op(Punct::BangBang),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::BangEq),
|
||||
_ => self.produce_op(Punct::Bang),
|
||||
}
|
||||
}
|
||||
fn bar(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('|') => self.consume()?.produce(Type::BarBar, ()),
|
||||
Ok('=') => self.consume()?.produce(Type::BarEq, ()),
|
||||
_ => self.produce(Type::Bar, ()),
|
||||
Ok('|') => self.consume()?.produce_op(Punct::BarBar),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::BarEq),
|
||||
_ => self.produce_op(Punct::Bar),
|
||||
}
|
||||
}
|
||||
fn colon(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok(':') => self.consume()?.produce(Type::ColonColon, ()),
|
||||
_ => self.produce(Type::Colon, ()),
|
||||
Ok(':') => self.consume()?.produce_op(Punct::ColonColon),
|
||||
_ => self.produce_op(Punct::Colon),
|
||||
}
|
||||
}
|
||||
fn dot(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('.') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce(Type::DotDotEq, ())
|
||||
self.consume()?.produce_op(Punct::DotDotEq)
|
||||
} else {
|
||||
self.produce(Type::DotDot, ())
|
||||
self.produce_op(Punct::DotDot)
|
||||
}
|
||||
}
|
||||
_ => self.produce(Type::Dot, ()),
|
||||
_ => self.produce_op(Punct::Dot),
|
||||
}
|
||||
}
|
||||
fn equal(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::EqEq, ()),
|
||||
Ok('>') => self.consume()?.produce(Type::FatArrow, ()),
|
||||
_ => self.produce(Type::Eq, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::EqEq),
|
||||
Ok('>') => self.consume()?.produce_op(Punct::FatArrow),
|
||||
_ => self.produce_op(Punct::Eq),
|
||||
}
|
||||
}
|
||||
fn greater(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::GtEq, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::GtEq),
|
||||
Ok('>') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce(Type::GtGtEq, ())
|
||||
self.consume()?.produce_op(Punct::GtGtEq)
|
||||
} else {
|
||||
self.produce(Type::GtGt, ())
|
||||
self.produce_op(Punct::GtGt)
|
||||
}
|
||||
}
|
||||
_ => self.produce(Type::Gt, ()),
|
||||
_ => self.produce_op(Punct::Gt),
|
||||
}
|
||||
}
|
||||
fn hash(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('!') => self.consume()?.produce(Type::HashBang, ()),
|
||||
_ => self.produce(Type::Hash, ()),
|
||||
Ok('!') => self.consume()?.produce_op(Punct::HashBang),
|
||||
_ => self.produce_op(Punct::Hash),
|
||||
}
|
||||
}
|
||||
fn less(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::LtEq, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::LtEq),
|
||||
Ok('<') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce(Type::LtLtEq, ())
|
||||
self.consume()?.produce_op(Punct::LtLtEq)
|
||||
} else {
|
||||
self.produce(Type::LtLt, ())
|
||||
self.produce_op(Punct::LtLt)
|
||||
}
|
||||
}
|
||||
_ => self.produce(Type::Lt, ()),
|
||||
_ => self.produce_op(Punct::Lt),
|
||||
}
|
||||
}
|
||||
fn minus(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::MinusEq, ()),
|
||||
Ok('>') => self.consume()?.produce(Type::Arrow, ()),
|
||||
_ => self.produce(Type::Minus, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::MinusEq),
|
||||
Ok('>') => self.consume()?.produce_op(Punct::Arrow),
|
||||
_ => self.produce_op(Punct::Minus),
|
||||
}
|
||||
}
|
||||
fn plus(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::PlusEq, ()),
|
||||
_ => self.produce(Type::Plus, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::PlusEq),
|
||||
_ => self.produce_op(Punct::Plus),
|
||||
}
|
||||
}
|
||||
fn rem(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::RemEq, ()),
|
||||
_ => self.produce(Type::Rem, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::RemEq),
|
||||
_ => self.produce_op(Punct::Rem),
|
||||
}
|
||||
}
|
||||
fn slash(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::SlashEq, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::SlashEq),
|
||||
Ok('/') => self.consume()?.line_comment(),
|
||||
Ok('*') => self.consume()?.block_comment(),
|
||||
_ => self.produce(Type::Slash, ()),
|
||||
_ => self.produce_op(Punct::Slash),
|
||||
}
|
||||
}
|
||||
fn star(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::StarEq, ()),
|
||||
_ => self.produce(Type::Star, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::StarEq),
|
||||
_ => self.produce_op(Punct::Star),
|
||||
}
|
||||
}
|
||||
fn xor(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce(Type::XorEq, ()),
|
||||
Ok('^') => self.consume()?.produce(Type::XorXor, ()),
|
||||
_ => self.produce(Type::Xor, ()),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::XorEq),
|
||||
Ok('^') => self.consume()?.produce_op(Punct::XorXor),
|
||||
_ => self.produce_op(Punct::Xor),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +318,7 @@ impl<'t> Lexer<'t> {
|
||||
while Ok('\n') != self.peek() {
|
||||
self.consume()?;
|
||||
}
|
||||
self.produce(Type::Comment, ())
|
||||
self.produce(Kind::Comment, ())
|
||||
}
|
||||
fn block_comment(&mut self) -> LResult<Token> {
|
||||
while let Ok(c) = self.next() {
|
||||
@@ -315,7 +326,7 @@ impl<'t> Lexer<'t> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.produce(Type::Comment, ())
|
||||
self.produce(Kind::Comment, ())
|
||||
}
|
||||
}
|
||||
/// Identifiers
|
||||
@@ -325,15 +336,15 @@ impl<'t> Lexer<'t> {
|
||||
while let Ok(c) = self.xid_continue() {
|
||||
out.push(c)
|
||||
}
|
||||
if let Ok(keyword) = Keyword::from_str(&out) {
|
||||
self.produce(Type::Keyword(keyword), ())
|
||||
if let Ok(keyword) = Kind::from_str(&out) {
|
||||
self.produce(keyword, ())
|
||||
} else {
|
||||
self.produce(Type::Identifier, Data::Identifier(out.into()))
|
||||
self.produce(Kind::Identifier, TokenData::String(out))
|
||||
}
|
||||
}
|
||||
fn xid_start(&mut self) -> LResult<char> {
|
||||
match self.peek()? {
|
||||
xid if xid == '_' || xid.is_xid_start() => {
|
||||
xid if xid == '_' || is_xid_start(xid) => {
|
||||
self.consume()?;
|
||||
Ok(xid)
|
||||
}
|
||||
@@ -342,7 +353,7 @@ impl<'t> Lexer<'t> {
|
||||
}
|
||||
fn xid_continue(&mut self) -> LResult<char> {
|
||||
match self.peek()? {
|
||||
xid if xid.is_xid_continue() => {
|
||||
xid if is_xid_continue(xid) => {
|
||||
self.consume()?;
|
||||
Ok(xid)
|
||||
}
|
||||
@@ -359,7 +370,7 @@ impl<'t> Lexer<'t> {
|
||||
Ok('o') => self.consume()?.digits::<8>(),
|
||||
Ok('b') => self.consume()?.digits::<2>(),
|
||||
Ok('0'..='9') => self.digits::<10>(),
|
||||
_ => self.produce(Type::Integer, 0),
|
||||
_ => self.produce(Kind::Literal, 0),
|
||||
}
|
||||
}
|
||||
fn digits<const B: u32>(&mut self) -> LResult<Token> {
|
||||
@@ -367,7 +378,7 @@ impl<'t> Lexer<'t> {
|
||||
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_alphanumeric) {
|
||||
value = value * B as u128 + self.digit::<B>()? as u128;
|
||||
}
|
||||
self.produce(Type::Integer, value)
|
||||
self.produce(Kind::Literal, value)
|
||||
}
|
||||
fn digit<const B: u32>(&mut self) -> LResult<u32> {
|
||||
let digit = self.peek()?;
|
||||
@@ -388,12 +399,12 @@ impl<'t> Lexer<'t> {
|
||||
{
|
||||
value.push(self.unescape()?)
|
||||
}
|
||||
self.consume()?.produce(Type::String, value)
|
||||
self.consume()?.produce(Kind::Literal, value)
|
||||
}
|
||||
fn character(&mut self) -> LResult<Token> {
|
||||
let out = self.unescape()?;
|
||||
match self.peek()? {
|
||||
'\'' => self.consume()?.produce(Type::Character, out),
|
||||
'\'' => self.consume()?.produce(Kind::Literal, out),
|
||||
_ => Err(Error::unmatched_delimiters('\'', self.line(), self.col())),
|
||||
}
|
||||
}
|
||||
@@ -445,6 +456,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};
|
||||
pub mod error {
|
||||
//! [Error] type for the [Lexer](super::Lexer)
|
||||
@@ -463,7 +480,7 @@ pub mod error {
|
||||
pub enum Reason {
|
||||
/// Found an opening delimiter of type [char], but not the expected closing delimiter
|
||||
UnmatchedDelimiters(char),
|
||||
/// Found a character that doesn't belong to any [Type](crate::token::token_type::Type)
|
||||
/// Found a character that doesn't belong to any [TokenKind](cl_token::TokenKind)
|
||||
UnexpectedChar(char),
|
||||
/// Found a character that's not valid in identifiers while looking for an identifier
|
||||
NotIdentifier(char),
|
||||
171
cl-lexer/src/tests.rs
Normal file
171
cl-lexer/src/tests.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
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),*) {
|
||||
[$(TokenData::String($id.into())),*]
|
||||
}
|
||||
test_lexer_data_type! {
|
||||
underscore { "_ _" => ident!["_", "_"] }
|
||||
unicode { "_ε ε_" => ident!["_ε", "ε_"] }
|
||||
many_underscore { "____________________________________" =>
|
||||
ident!["____________________________________"] }
|
||||
}
|
||||
}
|
||||
mod keyword {
|
||||
use super::*;
|
||||
macro kw($($k:ident),*) {
|
||||
[ $(TokenKind::$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 {
|
||||
macro op($op:ident) {
|
||||
TokenKind::Punct(Punct::$op)
|
||||
}
|
||||
|
||||
use super::*;
|
||||
test_lexer_output_type! {
|
||||
l_curly { "{ {" => [ op!(LCurly), op!(LCurly) ] }
|
||||
r_curly { "} }" => [ op!(RCurly), op!(RCurly) ] }
|
||||
l_brack { "[ [" => [ op!(LBrack), op!(LBrack) ] }
|
||||
r_brack { "] ]" => [ op!(RBrack), op!(RBrack) ] }
|
||||
l_paren { "( (" => [ op!(LParen), op!(LParen) ] }
|
||||
r_paren { ") )" => [ op!(RParen), op!(RParen) ] }
|
||||
amp { "& &" => [ op!(Amp), op!(Amp) ] }
|
||||
amp_amp { "&& &&" => [ op!(AmpAmp), op!(AmpAmp) ] }
|
||||
amp_eq { "&= &=" => [ op!(AmpEq), op!(AmpEq) ] }
|
||||
arrow { "-> ->" => [ op!(Arrow), op!(Arrow)] }
|
||||
at { "@ @" => [ op!(At), op!(At)] }
|
||||
backslash { "\\ \\" => [ op!(Backslash), op!(Backslash)] }
|
||||
bang { "! !" => [ op!(Bang), op!(Bang)] }
|
||||
bangbang { "!! !!" => [ op!(BangBang), op!(BangBang)] }
|
||||
bangeq { "!= !=" => [ op!(BangEq), op!(BangEq)] }
|
||||
bar { "| |" => [ op!(Bar), op!(Bar)] }
|
||||
barbar { "|| ||" => [ op!(BarBar), op!(BarBar)] }
|
||||
bareq { "|= |=" => [ op!(BarEq), op!(BarEq)] }
|
||||
colon { ": :" => [ op!(Colon), op!(Colon)] }
|
||||
comma { ", ," => [ op!(Comma), op!(Comma)] }
|
||||
dot { ". ." => [ op!(Dot), op!(Dot)] }
|
||||
dotdot { ".. .." => [ op!(DotDot), op!(DotDot)] }
|
||||
dotdoteq { "..= ..=" => [ op!(DotDotEq), op!(DotDotEq)] }
|
||||
eq { "= =" => [ op!(Eq), op!(Eq)] }
|
||||
eqeq { "== ==" => [ op!(EqEq), op!(EqEq)] }
|
||||
fatarrow { "=> =>" => [ op!(FatArrow), op!(FatArrow)] }
|
||||
grave { "` `" => [ op!(Grave), op!(Grave)] }
|
||||
gt { "> >" => [ op!(Gt), op!(Gt)] }
|
||||
gteq { ">= >=" => [ op!(GtEq), op!(GtEq)] }
|
||||
gtgt { ">> >>" => [ op!(GtGt), op!(GtGt)] }
|
||||
gtgteq { ">>= >>=" => [ op!(GtGtEq), op!(GtGtEq)] }
|
||||
hash { "# #" => [ op!(Hash), op!(Hash)] }
|
||||
lt { "< <" => [ op!(Lt), op!(Lt)] }
|
||||
lteq { "<= <=" => [ op!(LtEq), op!(LtEq)] }
|
||||
ltlt { "<< <<" => [ op!(LtLt), op!(LtLt)] }
|
||||
ltlteq { "<<= <<=" => [ op!(LtLtEq), op!(LtLtEq)] }
|
||||
minus { "- -" => [ op!(Minus), op!(Minus)] }
|
||||
minuseq { "-= -=" => [ op!(MinusEq), op!(MinusEq)] }
|
||||
plus { "+ +" => [ op!(Plus), op!(Plus)] }
|
||||
pluseq { "+= +=" => [ op!(PlusEq), op!(PlusEq)] }
|
||||
question { "? ?" => [ op!(Question), op!(Question)] }
|
||||
rem { "% %" => [ op!(Rem), op!(Rem)] }
|
||||
remeq { "%= %=" => [ op!(RemEq), op!(RemEq)] }
|
||||
semi { "; ;" => [ op!(Semi), op!(Semi)] }
|
||||
slash { "/ /" => [ op!(Slash), op!(Slash)] }
|
||||
slasheq { "/= /=" => [ op!(SlashEq), op!(SlashEq)] }
|
||||
star { "* *" => [ op!(Star), op!(Star)] }
|
||||
stareq { "*= *=" => [ op!(StarEq), op!(StarEq)] }
|
||||
tilde { "~ ~" => [ op!(Tilde), op!(Tilde)] }
|
||||
xor { "^ ^" => [ op!(Xor), op!(Xor)] }
|
||||
xoreq { "^= ^=" => [ op!(XorEq), op!(XorEq)] }
|
||||
xorxor { "^^ ^^" => [ op!(XorXor), op!(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(TokenKind),
|
||||
Expected {
|
||||
want: TokenKind,
|
||||
got: TokenKind,
|
||||
},
|
||||
/// 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}`, 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;
|
||||
1099
cl-parser/src/parser.rs
Normal file
1099
cl-parser/src/parser.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,5 +10,14 @@ publish.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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"
|
||||
argh = "0.1.12"
|
||||
|
||||
[dev-dependencies]
|
||||
cl-structures = { path = "../cl-structures" }
|
||||
cl-typeck = { path = "../cl-typeck" }
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
//! Collects identifiers into a list
|
||||
|
||||
use cl_repl::repline::Repline;
|
||||
use conlang::{common::Loc, lexer::Lexer, parser::Parser};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
|
||||
while let Ok(line) = rl.read() {
|
||||
let mut parser = Parser::new(Lexer::new(&line));
|
||||
let code = match parser.stmt() {
|
||||
Ok(code) => {
|
||||
rl.accept();
|
||||
code
|
||||
}
|
||||
Err(_) => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let c = Collector::from(&code);
|
||||
print!("\x1b[G\x1b[J{c}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ties the [Collector] location stack to the program stack
|
||||
pub struct CollectorLoc<'loc, 'code> {
|
||||
inner: &'loc mut Collector<'code>,
|
||||
}
|
||||
impl<'l, 'c> CollectorLoc<'l, 'c> {
|
||||
pub fn new(c: &'l mut Collector<'c>, loc: Loc) -> Self {
|
||||
c.location.push(loc);
|
||||
Self { inner: c }
|
||||
}
|
||||
}
|
||||
impl<'l, 'c> Deref for CollectorLoc<'l, 'c> {
|
||||
type Target = Collector<'c>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
impl<'l, 'c> DerefMut for CollectorLoc<'l, 'c> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
impl<'l, 'c> Drop for CollectorLoc<'l, 'c> {
|
||||
fn drop(&mut self) {
|
||||
let Self { inner: c } = self;
|
||||
c.location.pop();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Collector<'code> {
|
||||
location: Vec<Loc>,
|
||||
defs: HashMap<&'code str, Vec<Loc>>,
|
||||
refs: HashMap<&'code str, Vec<Loc>>,
|
||||
}
|
||||
|
||||
impl<'c> Collector<'c> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn location<'loc>(&'loc mut self, loc: Loc) -> CollectorLoc<'loc, 'c> {
|
||||
CollectorLoc::new(self, loc)
|
||||
}
|
||||
pub fn collect(&mut self, collectible: &'c impl Collectible<'c>) -> &mut Self {
|
||||
collectible.collect(self);
|
||||
self
|
||||
}
|
||||
pub fn define(&mut self, name: &'c str) -> &mut Self {
|
||||
// println!("Inserted definition of {name}");
|
||||
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
|
||||
self.defs
|
||||
.entry(name)
|
||||
.and_modify(|c| c.push(loc))
|
||||
.or_insert_with(|| vec![loc]);
|
||||
self
|
||||
}
|
||||
pub fn reference(&mut self, name: &'c str) -> &mut Self {
|
||||
// println!("Inserted usage of {name}");
|
||||
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
|
||||
self.refs
|
||||
.entry(name)
|
||||
.and_modify(|c| c.push(loc))
|
||||
.or_insert_with(|| vec![loc]);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'c> Display for Collector<'c> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { location: _, defs, refs } = self;
|
||||
writeln!(f, "Definitions:")?;
|
||||
for (name, locs) in defs {
|
||||
for loc in locs {
|
||||
writeln!(f, "{loc} {name}")?;
|
||||
}
|
||||
}
|
||||
writeln!(f, "Usages:")?;
|
||||
for (name, locs) in refs {
|
||||
for loc in locs {
|
||||
writeln!(f, "{loc} {name}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, C: Collectible<'c>> From<&'c C> for Collector<'c> {
|
||||
fn from(value: &'c C) -> Self {
|
||||
let mut c = Self::new();
|
||||
c.collect(value);
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
use collectible::Collectible;
|
||||
pub mod collectible {
|
||||
|
||||
use super::Collector;
|
||||
use conlang::ast::*;
|
||||
pub trait Collectible<'code> {
|
||||
fn collect(&'code self, c: &mut Collector<'code>);
|
||||
}
|
||||
|
||||
impl<'c> Collectible<'c> for File {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let File { items } = self;
|
||||
for item in items {
|
||||
item.collect(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Item {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { extents, attrs: _, vis: _, kind } = self;
|
||||
kind.collect(&mut c.location(extents.head));
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for ItemKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
ItemKind::Alias(f) => f.collect(c),
|
||||
ItemKind::Const(f) => f.collect(c),
|
||||
ItemKind::Static(f) => f.collect(c),
|
||||
ItemKind::Module(f) => f.collect(c),
|
||||
ItemKind::Function(f) => f.collect(c),
|
||||
ItemKind::Struct(f) => f.collect(c),
|
||||
ItemKind::Enum(f) => f.collect(c),
|
||||
ItemKind::Impl(f) => f.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Alias {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { to, from } = self;
|
||||
c.collect(to).collect(from);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Const {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), ty, init } = self;
|
||||
c.define(name).collect(init).collect(ty);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Static {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { mutable: _, name: Identifier(name), ty, init } = self;
|
||||
c.define(name).collect(init).collect(ty);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Module {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), kind } = self;
|
||||
c.define(name).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for ModuleKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
ModuleKind::Inline(f) => f.collect(c),
|
||||
ModuleKind::Outline => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Function {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), args, body, rety } = self;
|
||||
c.define(name).collect(args).collect(body).collect(rety);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Struct {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), kind } = self;
|
||||
c.define(name).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for StructKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
StructKind::Empty => {}
|
||||
StructKind::Tuple(k) => k.collect(c),
|
||||
StructKind::Struct(k) => k.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for StructMember {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { vis: _, name: Identifier(name), ty } = self;
|
||||
c.define(name).collect(ty);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Enum {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), kind } = self;
|
||||
c.define(name).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for EnumKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
EnumKind::NoVariants => {}
|
||||
EnumKind::Variants(v) => v.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Variant {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { name: Identifier(name), kind } = self;
|
||||
c.define(name).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for VariantKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
VariantKind::Plain => {}
|
||||
VariantKind::CLike(_) => {}
|
||||
VariantKind::Tuple(v) => v.collect(c),
|
||||
VariantKind::Struct(v) => v.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Impl {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { target, body } = self;
|
||||
c.collect(target).collect(body);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Block {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { stmts } = self;
|
||||
for stmt in stmts {
|
||||
stmt.collect(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Stmt {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { extents, kind, semi: _ } = self;
|
||||
c.location(extents.head).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for StmtKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
StmtKind::Empty => {}
|
||||
StmtKind::Local(s) => s.collect(c),
|
||||
StmtKind::Item(s) => s.collect(c),
|
||||
StmtKind::Expr(s) => s.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Let {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { mutable: _, name: Identifier(name), ty, init } = self;
|
||||
c.collect(init).collect(ty).define(name);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Expr {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { extents, kind } = self;
|
||||
c.location(extents.head).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for ExprKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
ExprKind::Assign(k) => k.collect(c),
|
||||
ExprKind::Binary(k) => k.collect(c),
|
||||
ExprKind::Unary(k) => k.collect(c),
|
||||
ExprKind::Member(k) => k.collect(c),
|
||||
ExprKind::Call(k) => k.collect(c),
|
||||
ExprKind::Index(k) => k.collect(c),
|
||||
ExprKind::Path(k) => k.collect(c),
|
||||
ExprKind::Literal(k) => k.collect(c),
|
||||
ExprKind::Array(k) => k.collect(c),
|
||||
ExprKind::ArrayRep(k) => k.collect(c),
|
||||
ExprKind::AddrOf(k) => k.collect(c),
|
||||
ExprKind::Block(k) => k.collect(c),
|
||||
ExprKind::Empty => {}
|
||||
ExprKind::Group(k) => k.collect(c),
|
||||
ExprKind::Tuple(k) => k.collect(c),
|
||||
ExprKind::While(k) => k.collect(c),
|
||||
ExprKind::If(k) => k.collect(c),
|
||||
ExprKind::For(k) => k.collect(c),
|
||||
ExprKind::Break(k) => k.collect(c),
|
||||
ExprKind::Return(k) => k.collect(c),
|
||||
ExprKind::Continue(k) => k.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Assign {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { head, op: _, tail } = self;
|
||||
c.collect(head).collect(tail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Binary {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { head, tail } = self;
|
||||
c.collect(head);
|
||||
for (_, tail) in tail {
|
||||
c.collect(tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Unary {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { ops: _, tail } = self;
|
||||
c.collect(tail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Member {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { head, tail } = self;
|
||||
c.collect(head).collect(tail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Call {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { callee, args } = self;
|
||||
c.collect(callee).collect(args);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Tuple {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { exprs } = self;
|
||||
c.collect(exprs);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Index {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { head, indices } = self;
|
||||
c.collect(head).collect(indices);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Indices {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { exprs } = self;
|
||||
c.collect(exprs);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Array {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { values } = self;
|
||||
c.collect(values);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for ArrayRep {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { value, repeat } = self;
|
||||
c.collect(value).collect(repeat);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for AddrOf {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { count: _, mutable: _, expr } = self;
|
||||
c.collect(expr);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Group {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { expr } = self;
|
||||
c.collect(expr);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for While {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
c.collect(cond).collect(pass).collect(fail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Else {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { body } = self;
|
||||
c.collect(body);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for If {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
c.collect(cond).collect(pass).collect(fail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for For {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { bind: Identifier(name), cond, pass, fail } = self;
|
||||
c.collect(cond).define(name).collect(pass).collect(fail);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Break {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { body } = self;
|
||||
c.collect(body);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Return {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { body } = self;
|
||||
c.collect(body);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Continue {
|
||||
fn collect(&'c self, _c: &mut Collector<'c>) {}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Literal {
|
||||
fn collect(&'c self, _c: &mut Collector<'c>) {}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Identifier {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self(name) = self;
|
||||
c.reference(name);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Param {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { mutability: _, name, ty } = self;
|
||||
c.collect(name).collect(ty);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Ty {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { extents, kind } = self;
|
||||
c.location(extents.head).collect(kind);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for TyKind {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
TyKind::Never => {}
|
||||
TyKind::Empty => {}
|
||||
TyKind::SelfTy => {}
|
||||
TyKind::Path(t) => t.collect(c),
|
||||
TyKind::Tuple(t) => t.collect(c),
|
||||
TyKind::Ref(t) => t.collect(c),
|
||||
TyKind::Fn(t) => t.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for Path {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { absolute: _, parts } = self;
|
||||
for part in parts {
|
||||
c.collect(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for PathPart {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
match self {
|
||||
PathPart::SuperKw => {}
|
||||
PathPart::SelfKw => {}
|
||||
PathPart::Ident(i) => i.collect(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for TyTuple {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { types } = self;
|
||||
for ty in types {
|
||||
c.collect(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for TyRef {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { count: _, to } = self;
|
||||
c.collect(to);
|
||||
}
|
||||
}
|
||||
impl<'c> Collectible<'c> for TyFn {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
let Self { args, rety } = self;
|
||||
c.collect(args).collect(rety);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Vec<C> {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
for i in self {
|
||||
c.collect(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Option<C> {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
if let Some(i) = self {
|
||||
c.collect(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'c, C: Collectible<'c>> Collectible<'c> for Box<C> {
|
||||
fn collect(&'c self, c: &mut Collector<'c>) {
|
||||
c.collect(self.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
||||
#![allow(unused_imports)]
|
||||
use conlang::lexer::Lexer;
|
||||
use cl_lexer::Lexer;
|
||||
use cl_token::Token;
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{stdin, IsTerminal, Read},
|
||||
@@ -57,7 +58,7 @@ fn lex_tokens(file: &str, path: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_token(t: conlang::token::Token) {
|
||||
fn print_token(t: Token) {
|
||||
println!(
|
||||
"{:02}:{:02}: {:#19} │{}│",
|
||||
t.line(),
|
||||
|
||||
109
cl-repl/examples/typeck.rs
Normal file
109
cl-repl/examples/typeck.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use cl_repl::repline::{error::Error as RlError, Repline};
|
||||
use cl_typeck::{name_collector::NameCollector, project::Project};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut prj = Project::default();
|
||||
let mut tcol = NameCollector::new(&mut prj);
|
||||
|
||||
println!(
|
||||
"--- {} v{} 💪🦈 ---",
|
||||
env!("CARGO_BIN_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
|
||||
read_and(
|
||||
"\x1b[33m",
|
||||
"cl>",
|
||||
"? >",
|
||||
|line| -> Result<_, Box<dyn Error>> {
|
||||
if line.trim_start().is_empty() {
|
||||
query(&tcol)?;
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
let mut parser = Parser::new(Lexer::new(line));
|
||||
let code = match parser.file() {
|
||||
Ok(code) => code,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
tcol.file(&code)?;
|
||||
Ok(Response::Accept)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub enum Response {
|
||||
Accept,
|
||||
Deny,
|
||||
Break,
|
||||
}
|
||||
|
||||
fn read_and(
|
||||
color: &str,
|
||||
begin: &str,
|
||||
again: &str,
|
||||
mut f: impl FnMut(&str) -> Result<Response, Box<dyn Error>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::new(color, begin, again);
|
||||
loop {
|
||||
let line = match rl.read() {
|
||||
Err(RlError::CtrlC(_)) => break,
|
||||
Err(RlError::CtrlD(line)) => {
|
||||
rl.deny();
|
||||
line
|
||||
}
|
||||
Ok(line) => line,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
print!("\x1b[G\x1b[J");
|
||||
match f(&line) {
|
||||
Ok(Response::Accept) => rl.accept(),
|
||||
Ok(Response::Deny) => rl.deny(),
|
||||
Ok(Response::Break) => break,
|
||||
Err(e) => print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query(prj: &Project) -> Result<(), Box<dyn Error>> {
|
||||
use cl_typeck::{
|
||||
definition::{Def, DefKind},
|
||||
type_kind::TypeKind,
|
||||
};
|
||||
read_and("\x1b[35m", "qy>", "? >", |line| {
|
||||
if line.trim_start().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
match line {
|
||||
"$all\n" => println!("{prj:#?}"),
|
||||
_ => {
|
||||
// parse it as a path, and convert the path into a borrowed path
|
||||
let path = Parser::new(Lexer::new(line)).path()?;
|
||||
|
||||
let Some((type_id, path)) = prj.get_type((&path).into(), prj.module_root) else {
|
||||
return Ok(Response::Deny);
|
||||
};
|
||||
let Def { name, vis, meta: _, kind, source: _, module } = &prj[type_id];
|
||||
match (kind, prj.get_value(path, type_id)) {
|
||||
(_, Some((val, path))) => {
|
||||
println!("value {}; {path}\n{:#?}", usize::from(val), prj[val])
|
||||
}
|
||||
(DefKind::Type(TypeKind::Module), None) => println!(
|
||||
"{vis}mod \"{name}\" (#{}); {path}\n{:#?}",
|
||||
usize::from(type_id),
|
||||
module
|
||||
),
|
||||
(_, None) => println!(
|
||||
"type {name}(#{}); {path}\n{:#?}",
|
||||
usize::from(type_id),
|
||||
prj.pool[type_id]
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
639
cl-repl/examples/yaml.rs
Normal file
639
cl-repl/examples/yaml.rs
Normal file
@@ -0,0 +1,639 @@
|
||||
//! Pretty prints a conlang AST in yaml
|
||||
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use cl_repl::repline::{error::Error as RlError, Repline};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
|
||||
loop {
|
||||
let line = match rl.read() {
|
||||
Err(RlError::CtrlC(_)) => break,
|
||||
Err(RlError::CtrlD(line)) => {
|
||||
rl.deny();
|
||||
line
|
||||
}
|
||||
Ok(line) => line,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(&line));
|
||||
let code = match parser.stmt() {
|
||||
Ok(code) => {
|
||||
rl.accept();
|
||||
code
|
||||
}
|
||||
Err(e) => {
|
||||
print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
print!("\x1b[G\x1b[J\x1b[A");
|
||||
Yamler::new().yaml(&code);
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub use yamler::Yamler;
|
||||
pub mod yamler {
|
||||
use crate::yamlify::Yamlify;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::Write,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Yamler {
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl Yamler {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn indent(&mut self) -> Section {
|
||||
Section::new(self)
|
||||
}
|
||||
|
||||
/// Prints a [Yamlify] value
|
||||
#[inline]
|
||||
pub fn yaml<T: Yamlify>(&mut self, yaml: &T) -> &mut Self {
|
||||
yaml.yaml(self);
|
||||
self
|
||||
}
|
||||
|
||||
fn increase(&mut self) {
|
||||
self.depth += 1;
|
||||
}
|
||||
|
||||
fn decrease(&mut self) {
|
||||
self.depth -= 1;
|
||||
}
|
||||
|
||||
fn print_indentation(&self, writer: &mut impl Write) {
|
||||
for _ in 0..self.depth {
|
||||
let _ = write!(writer, " ");
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a section header and increases indentation
|
||||
pub fn key(&mut self, name: impl Display) -> Section {
|
||||
println!();
|
||||
self.print_indentation(&mut std::io::stdout().lock());
|
||||
print!("- {name}:");
|
||||
self.indent()
|
||||
}
|
||||
|
||||
/// Prints a yaml key value pair: `- name: "value"`
|
||||
pub fn pair<D: Display, T: Yamlify>(&mut self, name: D, value: T) -> &mut Self {
|
||||
self.key(name).yaml(&value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Prints a yaml scalar value: `"name"``
|
||||
pub fn value<D: Display>(&mut self, value: D) -> &mut Self {
|
||||
print!(" {value}");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list<D: Yamlify>(&mut self, list: &[D]) -> &mut Self {
|
||||
for (idx, value) in list.iter().enumerate() {
|
||||
self.pair(idx, value);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the start and end of an indented block (a "section")
|
||||
pub struct Section<'y> {
|
||||
yamler: &'y mut Yamler,
|
||||
}
|
||||
|
||||
impl<'y> Section<'y> {
|
||||
pub fn new(yamler: &'y mut Yamler) -> Self {
|
||||
yamler.increase();
|
||||
Self { yamler }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Deref for Section<'y> {
|
||||
type Target = Yamler;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
impl<'y> DerefMut for Section<'y> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Drop for Section<'y> {
|
||||
fn drop(&mut self) {
|
||||
let Self { yamler } = self;
|
||||
yamler.decrease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod yamlify {
|
||||
use super::yamler::Yamler;
|
||||
use cl_ast::*;
|
||||
|
||||
pub trait Yamlify {
|
||||
fn yaml(&self, y: &mut Yamler);
|
||||
}
|
||||
|
||||
impl Yamlify for File {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let File { items } = self;
|
||||
y.key("File").yaml(items);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Visibility {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Visibility::Public = self {
|
||||
y.pair("vis", "pub");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for Mutability {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Mutability::Mut = self {
|
||||
y.pair("mut", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Yamlify for Attrs {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { meta } = self;
|
||||
y.key("Attrs").yaml(meta);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Meta {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Meta").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for MetaKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
MetaKind::Plain => y,
|
||||
MetaKind::Equals(value) => y.pair("equals", value),
|
||||
MetaKind::Func(args) => y.pair("args", args),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Yamlify for Item {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, attrs, vis, kind } = self;
|
||||
y.key("Item").yaml(attrs).yaml(vis).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ItemKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ItemKind::Alias(f) => y.yaml(f),
|
||||
ItemKind::Const(f) => y.yaml(f),
|
||||
ItemKind::Static(f) => y.yaml(f),
|
||||
ItemKind::Module(f) => y.yaml(f),
|
||||
ItemKind::Function(f) => y.yaml(f),
|
||||
ItemKind::Struct(f) => y.yaml(f),
|
||||
ItemKind::Enum(f) => y.yaml(f),
|
||||
ItemKind::Impl(f) => y.yaml(f),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Alias {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { to, from } = self;
|
||||
y.key("Alias").pair("to", to).pair("from", from);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Const {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, ty, init } = self;
|
||||
y.key("Const")
|
||||
.pair("name", name)
|
||||
.pair("ty", ty)
|
||||
.pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Static {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
y.key(name).yaml(mutable).pair("ty", ty).pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Module {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Module").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ModuleKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ModuleKind::Inline(f) => y.yaml(f),
|
||||
ModuleKind::Outline => y,
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Function {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, args, body, rety } = self;
|
||||
y.key("Function")
|
||||
.pair("name", name)
|
||||
.pair("args", args)
|
||||
.pair("body", body)
|
||||
.pair("rety", rety);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Struct {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Struct").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for StructKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
StructKind::Empty => y,
|
||||
StructKind::Tuple(k) => y.yaml(k),
|
||||
StructKind::Struct(k) => y.yaml(k),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for StructMember {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { vis, name, ty } = self;
|
||||
y.key("StructMember").yaml(vis).pair("name", name).yaml(ty);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Enum {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Enum").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for EnumKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
EnumKind::NoVariants => y,
|
||||
EnumKind::Variants(v) => y.yaml(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Variant {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Variant").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for VariantKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
VariantKind::Plain => y,
|
||||
VariantKind::CLike(v) => y.yaml(v),
|
||||
VariantKind::Tuple(v) => y.yaml(v),
|
||||
VariantKind::Struct(v) => y.yaml(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Impl {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { target, body } = self;
|
||||
y.key("Impl").pair("target", target).pair("body", body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Block {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { stmts } = self;
|
||||
y.key("Block").yaml(stmts);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Stmt {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind, semi } = self;
|
||||
y.key("Stmt").yaml(kind).yaml(semi);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Semi {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Semi::Terminated = self {
|
||||
y.pair("terminated", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for StmtKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
StmtKind::Empty => y,
|
||||
StmtKind::Local(s) => y.yaml(s),
|
||||
StmtKind::Item(s) => y.yaml(s),
|
||||
StmtKind::Expr(s) => y.yaml(s),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Let {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
y.key("Let")
|
||||
.pair("name", name)
|
||||
.yaml(mutable)
|
||||
.pair("ty", ty)
|
||||
.pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Expr {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind } = self;
|
||||
y.yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ExprKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ExprKind::Assign(k) => k.yaml(y),
|
||||
ExprKind::Binary(k) => k.yaml(y),
|
||||
ExprKind::Unary(k) => k.yaml(y),
|
||||
ExprKind::Index(k) => k.yaml(y),
|
||||
ExprKind::Path(k) => k.yaml(y),
|
||||
ExprKind::Literal(k) => k.yaml(y),
|
||||
ExprKind::Array(k) => k.yaml(y),
|
||||
ExprKind::ArrayRep(k) => k.yaml(y),
|
||||
ExprKind::AddrOf(k) => k.yaml(y),
|
||||
ExprKind::Block(k) => k.yaml(y),
|
||||
ExprKind::Empty => {}
|
||||
ExprKind::Group(k) => k.yaml(y),
|
||||
ExprKind::Tuple(k) => k.yaml(y),
|
||||
ExprKind::While(k) => k.yaml(y),
|
||||
ExprKind::If(k) => k.yaml(y),
|
||||
ExprKind::For(k) => k.yaml(y),
|
||||
ExprKind::Break(k) => k.yaml(y),
|
||||
ExprKind::Return(k) => k.yaml(y),
|
||||
ExprKind::Continue(k) => k.yaml(y),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for Assign {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, parts } = self;
|
||||
y.key("Assign")
|
||||
.pair("kind", kind)
|
||||
.pair("head", &parts.0)
|
||||
.pair("tail", &parts.1);
|
||||
}
|
||||
}
|
||||
impl Yamlify for AssignKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Binary {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, parts } = self;
|
||||
y.key("Binary")
|
||||
.pair("kind", kind)
|
||||
.pair("head", &parts.0)
|
||||
.pair("tail", &parts.1);
|
||||
}
|
||||
}
|
||||
impl Yamlify for BinaryKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Unary {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, tail } = self;
|
||||
y.key("Unary").pair("kind", kind).pair("tail", tail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for UnaryKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Tuple {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { exprs } = self;
|
||||
y.key("Tuple").list(exprs);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Index {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { head, indices } = self;
|
||||
y.key("Index").pair("head", head).list(indices);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Array {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { values } = self;
|
||||
y.key("Array").list(values);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ArrayRep {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { value, repeat } = self;
|
||||
y.key("ArrayRep")
|
||||
.pair("value", value)
|
||||
.pair("repeat", repeat);
|
||||
}
|
||||
}
|
||||
impl Yamlify for AddrOf {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { count, mutable, expr } = self;
|
||||
y.key("AddrOf")
|
||||
.yaml(mutable)
|
||||
.pair("count", count)
|
||||
.pair("expr", expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Group {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { expr } = self;
|
||||
y.key("Group").yaml(expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for While {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
y.key("While")
|
||||
.pair("cond", cond)
|
||||
.pair("pass", pass)
|
||||
.pair("fail", fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Else {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Else").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for If {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
y.key("If").pair("cond", cond).pair("pass", pass).yaml(fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for For {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { bind, cond, pass, fail } = self;
|
||||
y.key("For")
|
||||
.pair("bind", bind)
|
||||
.pair("cond", cond)
|
||||
.pair("pass", pass)
|
||||
.yaml(fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Break {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Break").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Return {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Return").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Continue {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.key("Continue");
|
||||
}
|
||||
}
|
||||
impl Yamlify for Literal {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(format_args!("\"{self}\""));
|
||||
}
|
||||
}
|
||||
impl Yamlify for Identifier {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self(name) = self;
|
||||
y.value(name);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Param {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutability, name, ty } = self;
|
||||
y.key("Param")
|
||||
.yaml(mutability)
|
||||
.pair("name", name)
|
||||
.pair("ty", ty);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Ty {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind } = self;
|
||||
y.key("Ty").yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
TyKind::Never => y.value("Never"),
|
||||
TyKind::Empty => y.value("Empty"),
|
||||
TyKind::SelfTy => y.value("Self"),
|
||||
TyKind::Path(t) => y.yaml(t),
|
||||
TyKind::Tuple(t) => y.yaml(t),
|
||||
TyKind::Ref(t) => y.yaml(t),
|
||||
TyKind::Fn(t) => y.yaml(t),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Path {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { absolute, parts } = self;
|
||||
let mut y = y.key("Path");
|
||||
if *absolute {
|
||||
y.pair("absolute", absolute);
|
||||
}
|
||||
for part in parts {
|
||||
y.pair("part", part);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for PathPart {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
PathPart::SuperKw => y.value("super"),
|
||||
PathPart::SelfKw => y.value("self"),
|
||||
PathPart::Ident(i) => y.yaml(i),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyTuple {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { types } = self;
|
||||
let mut y = y.key("TyTuple");
|
||||
for ty in types {
|
||||
y.yaml(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyRef {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { count, to } = self;
|
||||
y.key("TyRef").pair("count", count).pair("to", to);
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyFn {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { args, rety } = self;
|
||||
y.key("TyFn").pair("args", args).pair("rety", rety);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Yamlify> Yamlify for Option<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Some(v) = self {
|
||||
y.yaml(v);
|
||||
} else {
|
||||
y.value("");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Yamlify> Yamlify for Box<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.yaml(&**self);
|
||||
}
|
||||
}
|
||||
impl<T: Yamlify> Yamlify for Vec<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
for thing in self {
|
||||
y.yaml(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for () {
|
||||
fn yaml(&self, _y: &mut Yamler) {}
|
||||
}
|
||||
|
||||
impl<T: Yamlify> Yamlify for &T {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
(*self).yaml(y)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! scalar {
|
||||
($($t:ty),*$(,)?) => {
|
||||
$(impl Yamlify for $t {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
scalar! {
|
||||
bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, &str, String
|
||||
}
|
||||
}
|
||||
13
cl-repl/src/bin/conlang.rs
Normal file
13
cl-repl/src/bin/conlang.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use cl_repl::{cli::run, tools::is_terminal};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
if is_terminal() {
|
||||
println!(
|
||||
"--- {} v{} 💪🦈 ---",
|
||||
env!("CARGO_BIN_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
}
|
||||
run(argh::from_env())
|
||||
}
|
||||
@@ -3,85 +3,94 @@
|
||||
//! # TODO
|
||||
//! - [ ] Readline-like line editing
|
||||
//! - [ ] Raw mode?
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod ansi {
|
||||
// ANSI color escape sequences
|
||||
pub const ANSI_RED: &str = "\x1b[31m";
|
||||
pub 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 {
|
||||
use crate::cli::Mode;
|
||||
use std::{
|
||||
io::{stdin, IsTerminal},
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use crate::tools::is_terminal;
|
||||
use argh::FromArgs;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// The Conlang prototype debug interface
|
||||
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Args {
|
||||
pub path: Option<PathBuf>, // defaults None
|
||||
pub repl: bool, // defaults true if stdin is terminal
|
||||
pub mode: Mode, // defaults Interpret
|
||||
}
|
||||
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
|
||||
/// the main source file
|
||||
#[argh(positional)]
|
||||
pub file: Option<PathBuf>,
|
||||
|
||||
impl Args {
|
||||
pub fn new() -> Self {
|
||||
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret }
|
||||
}
|
||||
pub fn parse(mut self) -> Option<Self> {
|
||||
let mut args = std::env::args();
|
||||
let name = args.next().unwrap_or_default();
|
||||
let mut unknown = false;
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.deref() {
|
||||
"--repl" => self.repl = true,
|
||||
"--no-repl" => self.repl = false,
|
||||
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
|
||||
"-m" | "--mode" => {
|
||||
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default()
|
||||
}
|
||||
arg => {
|
||||
eprintln!("Unknown argument: {arg}");
|
||||
unknown = true;
|
||||
}
|
||||
}
|
||||
/// files to include
|
||||
#[argh(option, short = 'I')]
|
||||
pub include: Vec<PathBuf>,
|
||||
|
||||
/// the Repl mode to start in
|
||||
#[argh(option, short = 'm', default = "Default::default()")]
|
||||
pub mode: Mode,
|
||||
|
||||
/// whether to start the repl (`true` or `false`)
|
||||
#[argh(option, short = 'r', default = "is_terminal()")]
|
||||
pub repl: bool,
|
||||
}
|
||||
|
||||
/// The CLI's operating mode
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Mode {
|
||||
Tokenize,
|
||||
Beautify,
|
||||
#[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 {
|
||||
Self::new()
|
||||
|
||||
impl FromStr for Mode {
|
||||
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 {
|
||||
use std::{fmt::Display, io::Write};
|
||||
|
||||
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_interpret::{
|
||||
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
|
||||
};
|
||||
|
||||
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 enum Parsed {
|
||||
@@ -136,10 +145,6 @@ pub mod program {
|
||||
// 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> {
|
||||
match &self.data {
|
||||
Parsed::File(v) => v.interpret(env),
|
||||
@@ -155,18 +160,6 @@ pub mod program {
|
||||
// }
|
||||
// .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> {
|
||||
@@ -178,172 +171,105 @@ 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 {
|
||||
use conlang::{interpreter::env::Environment, resolver::Resolver, token::Token};
|
||||
|
||||
//! Implement's the command line interface
|
||||
use crate::{
|
||||
args::Args,
|
||||
program::{Parsable, Parsed, Program},
|
||||
};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
args::{Args, Mode},
|
||||
program::{Parsable, Program},
|
||||
repl::Repl,
|
||||
tools::print_token,
|
||||
};
|
||||
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
|
||||
const ANSI_RED: &str = "\x1b[31m";
|
||||
const ANSI_GREEN: &str = "\x1b[32m";
|
||||
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";
|
||||
/// Run the command line interface
|
||||
pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
||||
let Args { file, include, mode, repl } = args;
|
||||
|
||||
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
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 },
|
||||
}
|
||||
let mut env = Environment::new();
|
||||
for path in include {
|
||||
load_file(&mut env, path)?;
|
||||
}
|
||||
}
|
||||
impl CLI {
|
||||
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
use std::{fs, io};
|
||||
match self {
|
||||
CLI::Repl(repl) => repl.repl(),
|
||||
CLI::File { mode, ref path } => {
|
||||
// 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())?)
|
||||
}
|
||||
|
||||
if repl {
|
||||
if let Some(file) = file {
|
||||
load_file(&mut env, file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn no_repl(mode: Mode, path: Option<&Path>, code: &str) {
|
||||
let program = Program::new(code);
|
||||
Repl::with_env(mode, env).repl()
|
||||
} else {
|
||||
let code = match &file {
|
||||
Some(file) => std::fs::read_to_string(file)?,
|
||||
None => std::io::read_to_string(std::io::stdin())?,
|
||||
};
|
||||
let code = Program::new(&code);
|
||||
|
||||
match mode {
|
||||
Mode::Tokenize => {
|
||||
for token in program.lex() {
|
||||
if let Some(path) = path {
|
||||
print!("{}:", path.display());
|
||||
}
|
||||
match token {
|
||||
Ok(token) => print_token(&token),
|
||||
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}");
|
||||
}
|
||||
Mode::Tokenize => tokenize(code, file),
|
||||
Mode::Beautify => beautify(code),
|
||||
Mode::Interpret => interpret(code, &mut env),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The CLI's operating mode
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Mode {
|
||||
Tokenize,
|
||||
Beautify,
|
||||
Resolve,
|
||||
#[default]
|
||||
Interpret,
|
||||
fn load_file(
|
||||
env: &mut Environment,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<ConValue, Box<dyn Error>> {
|
||||
let file = std::fs::read_to_string(path)?;
|
||||
let code = Parser::new(Lexer::new(&file)).file()?;
|
||||
env.eval(&code).map_err(Into::into)
|
||||
}
|
||||
impl Mode {
|
||||
pub fn ansi_color(self) -> &'static str {
|
||||
match self {
|
||||
Mode::Tokenize => ANSI_BRIGHT_BLUE,
|
||||
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
|
||||
Mode::Resolve => ANSI_GREEN,
|
||||
Mode::Interpret => ANSI_CYAN,
|
||||
|
||||
fn tokenize(
|
||||
code: Program<Parsable>,
|
||||
path: Option<impl AsRef<Path>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
for token in code.lex() {
|
||||
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 from_str(s: &str) -> Result<Self, Infallible> {
|
||||
Ok(match s {
|
||||
"i" | "interpret" | "run" => Mode::Interpret,
|
||||
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
|
||||
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
|
||||
"t" | "tokenize" | "tokens" => Mode::Tokenize,
|
||||
_ => Mode::Interpret,
|
||||
})
|
||||
|
||||
fn beautify(code: Program<Parsable>) -> Result<(), Box<dyn Error>> {
|
||||
code.parse()?.print();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn interpret(code: Program<Parsable>, env: &mut Environment) -> Result<(), Box<dyn Error>> {
|
||||
match code.parse()?.run(env)? {
|
||||
ConValue::Empty => {}
|
||||
ret => println!("{ret}"),
|
||||
}
|
||||
if env.get("main").is_ok() {
|
||||
match env.call("main", &[])? {
|
||||
ConValue::Empty => {}
|
||||
ret => println!("{ret}"),
|
||||
}
|
||||
}
|
||||
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
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -351,8 +277,8 @@ pub mod cli {
|
||||
prompt_again: &'static str, // " ?>"
|
||||
prompt_begin: &'static str, // "cl>"
|
||||
prompt_error: &'static str, // "! >"
|
||||
prompt_succs: &'static str, // " ->"
|
||||
env: Environment,
|
||||
resolver: Resolver,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
@@ -362,8 +288,8 @@ pub mod cli {
|
||||
prompt_begin: "cl>",
|
||||
prompt_again: " ?>",
|
||||
prompt_error: "! >",
|
||||
prompt_succs: " =>",
|
||||
env: Default::default(),
|
||||
resolver: Default::default(),
|
||||
mode: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -371,9 +297,19 @@ pub mod cli {
|
||||
|
||||
/// Prompt functions
|
||||
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;
|
||||
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_GREEN}{value}{ANSI_RESET}")
|
||||
}
|
||||
/// Resets the cursor to the start of the line, clears the terminal,
|
||||
/// and sets the output color
|
||||
@@ -388,9 +324,14 @@ pub mod cli {
|
||||
pub fn new(mode: Mode) -> Self {
|
||||
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
|
||||
pub fn repl(&mut self) {
|
||||
use crate::repline::{error::Error, Repline};
|
||||
|
||||
let mut rl = Repline::new(self.mode.ansi_color(), self.prompt_begin, self.prompt_again);
|
||||
fn clear_line() {
|
||||
print!("\x1b[G\x1b[J");
|
||||
@@ -456,23 +397,26 @@ pub mod cli {
|
||||
);
|
||||
}
|
||||
fn command(&mut self, line: &str) -> bool {
|
||||
match line.trim() {
|
||||
"$pretty" => self.mode = Mode::Beautify,
|
||||
"$tokens" => self.mode = Mode::Tokenize,
|
||||
"$type" => self.mode = Mode::Resolve,
|
||||
"$run" => self.mode = Mode::Interpret,
|
||||
"$mode" => println!("{:?} Mode", self.mode),
|
||||
"$help" => self.help(),
|
||||
_ => return false,
|
||||
let Some(line) = line.trim().strip_prefix('$') else {
|
||||
return false;
|
||||
};
|
||||
if let Ok(mode) = line.parse() {
|
||||
self.mode = mode;
|
||||
} else {
|
||||
match line {
|
||||
"$run" => self.mode = Mode::Interpret,
|
||||
"mode" => println!("{:?} Mode", self.mode),
|
||||
"help" => self.help(),
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
/// Dispatches calls to repl functions based on the program
|
||||
fn dispatch(&mut self, code: &mut Program<Parsed>) {
|
||||
match self.mode {
|
||||
Mode::Tokenize => (),
|
||||
Mode::Tokenize => {}
|
||||
Mode::Beautify => self.beautify(code),
|
||||
Mode::Resolve => self.typecheck(code),
|
||||
Mode::Interpret => self.interpret(code),
|
||||
}
|
||||
}
|
||||
@@ -485,21 +429,22 @@ pub mod cli {
|
||||
}
|
||||
}
|
||||
fn interpret(&mut self, code: &Program<Parsed>) {
|
||||
if let Err(e) = code.run(&mut self.env) {
|
||||
self.prompt_error(&e)
|
||||
}
|
||||
}
|
||||
fn typecheck(&mut self, code: &mut Program<Parsed>) {
|
||||
if let Err(e) = code.resolve(&mut self.resolver) {
|
||||
self.prompt_error(&e)
|
||||
match code.run(&mut self.env) {
|
||||
Ok(ConValue::Empty) => {}
|
||||
res => self.prompt_result(res),
|
||||
}
|
||||
}
|
||||
fn beautify(&mut self, code: &Program<Parsed>) {
|
||||
code.print()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_token(t: &Token) {
|
||||
pub mod tools {
|
||||
use cl_token::Token;
|
||||
use std::io::IsTerminal;
|
||||
/// Prints a token in the particular way cl-repl does
|
||||
pub fn print_token(t: &Token) {
|
||||
println!(
|
||||
"{:02}:{:02}: {:#19} │{}│",
|
||||
t.line(),
|
||||
@@ -508,6 +453,10 @@ pub mod cli {
|
||||
t.data(),
|
||||
)
|
||||
}
|
||||
/// gets whether stdin AND stdout are a terminal, for pipelining
|
||||
pub fn is_terminal() -> bool {
|
||||
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod repline;
|
||||
|
||||
@@ -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
|
||||
/// ```rust
|
||||
/// #![deny(unused_must_use)]
|
||||
/// # use cl_frontend::repline::ignore::Ignore;
|
||||
/// # use cl_repl::repline::ignore::Ignore;
|
||||
/// ().ignore();
|
||||
/// Err::<(), &str>("Foo").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]
|
||||
131
cl-structures/src/intern_pool.rs
Normal file
131
cl-structures/src/intern_pool.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
//! Trivially-copyable, easily comparable typed indices, and a [Pool] to contain them
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use cl_structures::intern_pool::*;
|
||||
//! // first, create a new InternKey type (this ensures type safety)
|
||||
//! make_intern_key!{
|
||||
//! NumbersKey
|
||||
//! }
|
||||
//!
|
||||
//! // then, create a pool with that type
|
||||
//! let mut numbers: Pool<i32, NumbersKey> = Pool::new();
|
||||
//! let first = numbers.insert(1);
|
||||
//! let second = numbers.insert(2);
|
||||
//! let third = numbers.insert(3);
|
||||
//!
|
||||
//! // You can access elements immutably with `get`
|
||||
//! assert_eq!(Some(&3), numbers.get(third));
|
||||
//! assert_eq!(Some(&2), numbers.get(second));
|
||||
//! // or by indexing
|
||||
//! assert_eq!(1, numbers[first]);
|
||||
//!
|
||||
//! // Or mutably
|
||||
//! *numbers.get_mut(first).unwrap() = 100000;
|
||||
//!
|
||||
//! assert_eq!(Some(&100000), numbers.get(first));
|
||||
//! ```
|
||||
|
||||
/// Creates newtype indices over [`usize`] for use as [Pool] keys.
|
||||
#[macro_export]
|
||||
macro_rules! make_intern_key {($($(#[$meta:meta])* $name:ident),*$(,)?) => {$(
|
||||
$(#[$meta])*
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct $name(usize);
|
||||
|
||||
impl $crate::intern_pool::InternKey for $name {
|
||||
#[doc = concat!("Constructs a [`", stringify!($name), "`] from a [`usize`] without checking bounds.\n")]
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided value should be within the bounds of its associated container
|
||||
unsafe fn from_raw_unchecked(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
fn get(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl From< $name > for usize {
|
||||
fn from(value: $name) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
)*}}
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
pub use make_intern_key;
|
||||
|
||||
/// An index into a [Pool]. For full type-safety,
|
||||
/// there should be a unique [InternKey] for each [Pool]
|
||||
pub trait InternKey: std::fmt::Debug {
|
||||
/// Constructs an [`InternKey`] from a [`usize`] without checking bounds.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided value should be within the bounds of its associated container.
|
||||
// ID::from_raw_unchecked here isn't *actually* unsafe, since bounds should always be
|
||||
// checked, however, the function has unverifiable preconditions.
|
||||
unsafe fn from_raw_unchecked(value: usize) -> Self;
|
||||
/// Gets the index of the [`InternKey`] by value
|
||||
fn get(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Pool<T, ID: InternKey> {
|
||||
pool: Vec<T>,
|
||||
id_type: std::marker::PhantomData<ID>,
|
||||
}
|
||||
|
||||
impl<T, ID: InternKey> Pool<T, ID> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn get(&self, index: ID) -> Option<&T> {
|
||||
self.pool.get(index.get())
|
||||
}
|
||||
pub fn get_mut(&mut self, index: ID) -> Option<&mut T> {
|
||||
self.pool.get_mut(index.get())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
self.pool.iter()
|
||||
}
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
self.pool.iter_mut()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: T) -> ID {
|
||||
let id = self.pool.len();
|
||||
self.pool.push(value);
|
||||
|
||||
// Safety: value was pushed to `self.pool[id]`
|
||||
unsafe { ID::from_raw_unchecked(id) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, ID: InternKey> Default for Pool<T, ID> {
|
||||
fn default() -> Self {
|
||||
Self { pool: vec![], id_type: std::marker::PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, ID: InternKey> Index<ID> for Pool<T, ID> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: ID) -> &Self::Output {
|
||||
match self.pool.get(index.get()) {
|
||||
None => panic!("Index {:?} out of bounds in pool!", index),
|
||||
Some(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T, ID: InternKey> IndexMut<ID> for Pool<T, ID> {
|
||||
fn index_mut(&mut self, index: ID) -> &mut Self::Output {
|
||||
match self.pool.get_mut(index.get()) {
|
||||
None => panic!("Index {:?} out of bounds in pool!", index),
|
||||
Some(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
14
cl-structures/src/lib.rs
Normal file
14
cl-structures/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! # 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)]
|
||||
#![feature(inline_const, dropck_eyepatch, decl_macro)]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
pub mod span;
|
||||
|
||||
pub mod tree;
|
||||
|
||||
pub mod stack;
|
||||
|
||||
pub mod intern_pool;
|
||||
@@ -1,8 +1,6 @@
|
||||
//! # 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)]
|
||||
@@ -38,9 +36,3 @@ impl std::fmt::Display for Loc {
|
||||
write!(f, "{line}:{col}:")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> From<&Lexer<'t>> for Loc {
|
||||
fn from(value: &Lexer<'t>) -> Self {
|
||||
Loc(value.line(), value.col())
|
||||
}
|
||||
}
|
||||
747
cl-structures/src/stack.rs
Normal file
747
cl-structures/src/stack.rs
Normal file
@@ -0,0 +1,747 @@
|
||||
//! A contiguous collection with constant capacity.
|
||||
//!
|
||||
//! Since the capacity of a [Stack] may be [*known at compile time*](Sized),
|
||||
//! it may live on the call stack.
|
||||
//!
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Unlike a [Vec], the [Stack] doesn't grow when it reaches capacity.
|
||||
//! ```should_panic
|
||||
//! # use cl_structures::stack::*;
|
||||
//! let mut v = stack![1];
|
||||
//! v.push("This should work");
|
||||
//! v.push("This will panic!");
|
||||
//! ```
|
||||
//! To get around this limitation, the methods [try_push](Stack::try_push) and
|
||||
//! [try_insert](Stack::try_insert) are provided:
|
||||
//! ```
|
||||
//! # use cl_structures::stack::*;
|
||||
//! let mut v = stack![1];
|
||||
//! v.push("This should work");
|
||||
//! v.try_push("This should produce an err").unwrap_err();
|
||||
//! ```
|
||||
//!
|
||||
//! As the name suggests, a [Stack] enforces a stack discipline:
|
||||
//! ```
|
||||
//! # use cl_structures::stack::*;
|
||||
//! let mut v = stack![100];
|
||||
//!
|
||||
//! assert_eq!(100, v.capacity());
|
||||
//! assert_eq!(0, v.len());
|
||||
//!
|
||||
//! // Elements are pushed one at a time onto the stack
|
||||
//! v.push("foo");
|
||||
//! v.push("bar");
|
||||
//! assert_eq!(2, v.len());
|
||||
//!
|
||||
//! // The stack can be used anywhere a slice is expected
|
||||
//! assert_eq!(Some(&"foo"), v.get(0));
|
||||
//! assert_eq!(Some(&"bar"), v.last());
|
||||
//!
|
||||
//! // Elements are popped from the stack in reverse order
|
||||
//! assert_eq!(Some("bar"), v.pop());
|
||||
//! assert_eq!(Some("foo"), v.pop());
|
||||
//! assert_eq!(None, v.pop());
|
||||
//! ```
|
||||
|
||||
// yar har! here there be unsafe code! Tread carefully.
|
||||
|
||||
use core::slice;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
mem::{ManuallyDrop, MaybeUninit},
|
||||
ops::{Deref, DerefMut},
|
||||
ptr,
|
||||
};
|
||||
|
||||
/// Creates a [`stack`] containing the arguments
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Creates a *full* [`Stack`] containing a list of elements
|
||||
/// ```
|
||||
/// # use cl_structures::stack::stack;
|
||||
/// let mut v = stack![1, 2, 3];
|
||||
///
|
||||
/// assert_eq!(Some(3), v.pop());
|
||||
/// assert_eq!(Some(2), v.pop());
|
||||
/// assert_eq!(Some(1), v.pop());
|
||||
/// assert_eq!(None, v.pop());
|
||||
/// ```
|
||||
///
|
||||
/// Creates a *full* [`Stack`] from a given element and size
|
||||
/// ```
|
||||
/// # use cl_structures::stack::stack;
|
||||
/// let mut v = stack![1; 2];
|
||||
///
|
||||
/// assert_eq!(Some(1), v.pop());
|
||||
/// assert_eq!(Some(1), v.pop());
|
||||
/// assert_eq!(None, v.pop());
|
||||
/// ```
|
||||
///
|
||||
/// Creates an *empty* [`Stack`] from a given size
|
||||
/// ```
|
||||
/// # use cl_structures::stack::{Stack, stack};
|
||||
/// let mut v = stack![10];
|
||||
///
|
||||
/// assert_eq!(0, v.len());
|
||||
/// assert_eq!(10, v.capacity());
|
||||
///
|
||||
/// v.push(10);
|
||||
/// assert_eq!(Some(&10), v.last());
|
||||
/// ```
|
||||
pub macro stack {
|
||||
($count:literal) => {
|
||||
Stack::<_, $count>::new()
|
||||
},
|
||||
($value:expr ; $count:literal) => {{
|
||||
let mut stack: Stack<_, $count> = Stack::new();
|
||||
for _ in 0..$count {
|
||||
stack.push($value)
|
||||
}
|
||||
stack
|
||||
}},
|
||||
($($values:expr),* $(,)?) => {
|
||||
Stack::from([$($values),*])
|
||||
}
|
||||
}
|
||||
|
||||
/// A contiguous collection with constant capacity
|
||||
pub struct Stack<T, const N: usize> {
|
||||
_data: PhantomData<T>,
|
||||
buf: [MaybeUninit<T>; N],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone, const N: usize> Clone for Stack<T, N> {
|
||||
fn clone(&self) -> Self {
|
||||
let mut new = Self::new();
|
||||
for value in self.iter() {
|
||||
new.push(value.clone())
|
||||
}
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug, const N: usize> Debug for Stack<T, N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_list().entries(self.iter()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Default for Stack<T, N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Deref for Stack<T, N> {
|
||||
type Target = [T];
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// Safety:
|
||||
// - We have ensured all elements from 0 to len have been initialized
|
||||
// - self.elem[0] came from a reference, and so is aligned to T
|
||||
// unsafe { &*(&self.buf[0..self.len] as *const [_] as *const [T]) }
|
||||
unsafe { slice::from_raw_parts(self.buf.as_ptr().cast(), self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> DerefMut for Stack<T, N> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// Safety:
|
||||
// - See Deref
|
||||
unsafe { slice::from_raw_parts_mut(self.buf.as_mut_ptr().cast(), self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
// requires dropck-eyepatch for elements with contravariant lifetimes
|
||||
unsafe impl<#[may_dangle] T, const N: usize> Drop for Stack<T, N> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
// Safety: We have ensured that all elements in the list are
|
||||
unsafe { core::ptr::drop_in_place(self.as_mut_slice()) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Extend<T> for Stack<T, N> {
|
||||
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||
for value in iter {
|
||||
self.push(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> From<[T; N]> for Stack<T, N> {
|
||||
fn from(value: [T; N]) -> Self {
|
||||
let value = ManuallyDrop::new(value);
|
||||
if std::mem::size_of::<[T; N]>() == 0 {
|
||||
// Safety: since [T; N] is zero-sized, and there are no other fields,
|
||||
// it should be okay to interpret N as Self
|
||||
unsafe { ptr::read(&N as *const _ as *const _) }
|
||||
} else {
|
||||
// Safety:
|
||||
// - `value` is ManuallyDrop, so its destructor won't run
|
||||
// - All elements are assumed to be initialized (so len is N)
|
||||
Self {
|
||||
buf: unsafe { ptr::read(&value as *const _ as *const _) },
|
||||
len: N,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Stack<T, N> {
|
||||
/// Constructs a new, empty [Stack]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v: Stack<_, 3> = Stack::new();
|
||||
///
|
||||
/// v.try_push(1).unwrap();
|
||||
/// v.try_push(2).unwrap();
|
||||
/// v.try_push(3).unwrap();
|
||||
/// // Trying to push a 4th element will fail, and return the failed element
|
||||
/// assert_eq!(4, v.try_push(4).unwrap_err());
|
||||
///
|
||||
/// assert_eq!(Some(3), v.pop());
|
||||
/// ```
|
||||
pub const fn new() -> Self {
|
||||
Self { buf: [const { MaybeUninit::uninit() }; N], len: 0, _data: PhantomData }
|
||||
}
|
||||
|
||||
/// Constructs a new [Stack] from an array of [`MaybeUninit<T>`] and an initialized length
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Elements from `0..len` must be initialized
|
||||
/// - len must not exceed the length of the array
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// # use core::mem::MaybeUninit;
|
||||
/// let mut v = unsafe { Stack::from_raw_parts([MaybeUninit::new(100)], 1) };
|
||||
///
|
||||
/// assert_eq!(1, v.len());
|
||||
/// assert_eq!(1, v.capacity());
|
||||
/// assert_eq!(Some(100), v.pop());
|
||||
/// assert_eq!(None, v.pop());
|
||||
/// ```
|
||||
pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self {
|
||||
Self { buf, len, _data: PhantomData }
|
||||
}
|
||||
|
||||
/// Converts a [Stack] into an array of [`MaybeUninit<T>`] and the initialized length
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v: Stack<_, 10> = Stack::new();
|
||||
/// v.push(0);
|
||||
/// v.push(1);
|
||||
///
|
||||
/// let (buf, len) = v.into_raw_parts();
|
||||
///
|
||||
/// assert_eq!(0, unsafe { buf[0].assume_init() });
|
||||
/// assert_eq!(1, unsafe { buf[1].assume_init() });
|
||||
/// assert_eq!(2, len);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) {
|
||||
let this = ManuallyDrop::new(self);
|
||||
// Safety: since
|
||||
(unsafe { ptr::read(&this.buf) }, this.len)
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the stack's buffer
|
||||
pub const fn as_ptr(&self) -> *const T {
|
||||
self.buf.as_ptr().cast()
|
||||
}
|
||||
|
||||
/// Returns an unsafe mutable pointer to the stack's buffer
|
||||
pub fn as_mut_ptr(&mut self) -> *mut T {
|
||||
self.buf.as_mut_ptr().cast()
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the entire vector
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a mutable slice containing the entire vector
|
||||
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the total number of elements the stack can hold
|
||||
pub const fn capacity(&self) -> usize {
|
||||
N
|
||||
}
|
||||
|
||||
/// Moves an existing stack into an allocation of a (potentially) different size,
|
||||
/// truncating if necessary.
|
||||
///
|
||||
/// This can be used to easily construct a half-empty stack
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// You can grow a stack to fit more elements
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let v = Stack::from([0, 1, 2, 3, 4]);
|
||||
/// assert_eq!(5, v.capacity());
|
||||
///
|
||||
/// let mut v = v.resize::<10>();
|
||||
/// assert_eq!(10, v.capacity());
|
||||
///
|
||||
/// v.push(5);
|
||||
/// ```
|
||||
///
|
||||
/// You can truncate a stack, dropping elements off the end
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let v = Stack::from([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
/// assert_eq!(8, v.capacity());
|
||||
///
|
||||
/// let v = v.resize::<5>();
|
||||
/// assert_eq!(5, v.capacity());
|
||||
/// ```
|
||||
pub fn resize<const M: usize>(mut self) -> Stack<T, M> {
|
||||
// Drop elements until new length is reached
|
||||
while self.len > M {
|
||||
drop(self.pop());
|
||||
}
|
||||
let (old, len) = self.into_raw_parts();
|
||||
let mut new: Stack<T, M> = Stack::new();
|
||||
|
||||
// Safety:
|
||||
// - new and old are separate allocations
|
||||
// - len <= M
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(old.as_ptr(), new.buf.as_mut_ptr(), len);
|
||||
}
|
||||
|
||||
new.len = len;
|
||||
new
|
||||
}
|
||||
|
||||
/// Push a new element onto the end of the stack
|
||||
///
|
||||
/// # May Panic
|
||||
///
|
||||
/// Panics if the new length exceeds capacity
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v: Stack<_, 4> = Stack::new();
|
||||
///
|
||||
/// v.push(0);
|
||||
/// v.push(1);
|
||||
/// v.push(2);
|
||||
/// v.push(3);
|
||||
/// assert_eq!(&[0, 1, 2, 3], v.as_slice());
|
||||
/// ```
|
||||
pub fn push(&mut self, value: T) {
|
||||
if self.len >= N {
|
||||
panic!("Attempted to push into full stack")
|
||||
}
|
||||
// Safety: len is confirmed to be less than capacity
|
||||
unsafe { self.push_unchecked(value) };
|
||||
}
|
||||
|
||||
/// Push a new element onto the end of the stack
|
||||
///
|
||||
/// Returns [`Err(value)`](Result::Err) if the new length would exceed capacity
|
||||
pub fn try_push(&mut self, value: T) -> Result<(), T> {
|
||||
if self.len >= N {
|
||||
return Err(value);
|
||||
}
|
||||
// Safety: len is confirmed to be less than capacity
|
||||
unsafe { self.push_unchecked(value) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push a new element onto the end of the stack, without checking capacity
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// len after push must not exceed capacity N
|
||||
#[inline]
|
||||
unsafe fn push_unchecked(&mut self, value: T) {
|
||||
unsafe { ptr::write(self.as_mut_ptr().add(self.len), value) }
|
||||
self.len += 1; // post inc
|
||||
}
|
||||
|
||||
/// Pops the last element off the end of the stack, and returns it
|
||||
///
|
||||
/// Returns None if the stack is empty
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v = Stack::from([0, 1, 2, 3]);
|
||||
///
|
||||
/// assert_eq!(Some(3), v.pop());
|
||||
/// assert_eq!(Some(2), v.pop());
|
||||
/// assert_eq!(Some(1), v.pop());
|
||||
/// assert_eq!(Some(0), v.pop());
|
||||
/// assert_eq!(None, v.pop());
|
||||
/// ```
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
if self.len == 0 {
|
||||
None
|
||||
} else {
|
||||
self.len -= 1;
|
||||
// Safety: MaybeUninit<T> implies ManuallyDrop<T>,
|
||||
// therefore should not get dropped twice
|
||||
Some(unsafe { ptr::read(self.as_ptr().add(self.len).cast()) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes and returns the element at the given index,
|
||||
/// shifting other elements toward the start
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||
///
|
||||
/// assert_eq!(2, v.remove(2));
|
||||
/// assert_eq!(&[0, 1, 3, 4], v.as_slice());
|
||||
/// ```
|
||||
pub fn remove(&mut self, index: usize) -> T {
|
||||
if index >= self.len {
|
||||
panic!("Index {index} exceeded length {}", self.len)
|
||||
}
|
||||
let len = self.len - 1;
|
||||
let base = self.as_mut_ptr();
|
||||
let out = unsafe { ptr::read(base.add(index)) };
|
||||
|
||||
unsafe { ptr::copy(base.add(index + 1), base.add(index), len - index) };
|
||||
self.len = len;
|
||||
out
|
||||
}
|
||||
|
||||
/// Removes and returns the element at the given index,
|
||||
/// swapping it with the last element.
|
||||
///
|
||||
/// # May Panic
|
||||
///
|
||||
/// Panics if `index >= len`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||
///
|
||||
/// assert_eq!(2, v.swap_remove(2));
|
||||
///
|
||||
/// assert_eq!(&[0, 1, 4, 3], v.as_slice());
|
||||
/// ```
|
||||
pub fn swap_remove(&mut self, index: usize) -> T {
|
||||
if index >= self.len {
|
||||
panic!("Index {index} exceeds length {}", self.len);
|
||||
}
|
||||
let len = self.len - 1;
|
||||
let ptr = self.as_mut_ptr();
|
||||
let out = unsafe { ptr::read(ptr.add(index)) };
|
||||
|
||||
unsafe { ptr::copy(ptr.add(len), ptr.add(index), 1) };
|
||||
self.len = len;
|
||||
out
|
||||
}
|
||||
|
||||
/// Inserts an element at position `index` in the stack,
|
||||
/// shifting all elements after it to the right.
|
||||
///
|
||||
/// # May Panic
|
||||
///
|
||||
/// Panics if `index > len` or [`self.is_full()`](Stack::is_full)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
/// let mut v = Stack::from([0, 1, 2, 3, 4]).resize::<6>();
|
||||
///
|
||||
/// v.insert(3, 0xbeef);
|
||||
/// assert_eq!(&[0, 1, 2, 0xbeef, 3, 4], v.as_slice());
|
||||
/// ```
|
||||
pub fn insert(&mut self, index: usize, data: T) {
|
||||
if index > self.len {
|
||||
panic!("Index {index} exceeded length {}", self.len)
|
||||
}
|
||||
if self.is_full() {
|
||||
panic!("Attempted to insert into full stack")
|
||||
}
|
||||
unsafe { self.insert_unchecked(index, data) };
|
||||
}
|
||||
|
||||
/// Attempts to insert an element at position `index` in the stack,
|
||||
/// shifting all elements after it to the right.
|
||||
///
|
||||
/// If the stack is at capacity, returns the original element and an [InsertFailed] error.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use cl_structures::stack::Stack;
|
||||
/// let mut v: Stack<_, 2> = Stack::new();
|
||||
///
|
||||
/// assert_eq!(Ok(()), v.try_insert(0, 0));
|
||||
/// ```
|
||||
pub fn try_insert(&mut self, index: usize, data: T) -> Result<(), (T, InsertFailed<N>)> {
|
||||
if index > self.len {
|
||||
return Err((data, InsertFailed::Bounds(index)));
|
||||
}
|
||||
if self.is_full() {
|
||||
return Err((data, InsertFailed::Full));
|
||||
}
|
||||
// Safety: index < self.len && !self.is_full()
|
||||
unsafe { self.insert_unchecked(index, data) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// - index must be less than self.len
|
||||
/// - length after insertion must be <= N
|
||||
#[inline]
|
||||
unsafe fn insert_unchecked(&mut self, index: usize, data: T) {
|
||||
let base = self.as_mut_ptr();
|
||||
|
||||
unsafe { ptr::copy(base.add(index), base.add(index + 1), self.len - index) }
|
||||
|
||||
self.len += 1;
|
||||
self.buf[index] = MaybeUninit::new(data);
|
||||
}
|
||||
|
||||
/// Clears the stack
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::Stack;
|
||||
///
|
||||
/// let mut v = Stack::from([0, 1, 2, 3, 4]);
|
||||
/// assert_eq!(v.as_slice(), &[0, 1, 2, 3, 4]);
|
||||
///
|
||||
/// v.clear();
|
||||
/// assert_eq!(v.as_slice(), &[]);
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
// Hopefully copy elision takes care of this lmao
|
||||
drop(std::mem::take(self))
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the stack
|
||||
/// ```
|
||||
/// # use cl_structures::stack::*;
|
||||
/// let v = Stack::from([0, 1, 2, 3, 4]);
|
||||
///
|
||||
/// assert_eq!(5, v.len());
|
||||
/// ```
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns true if the stack is at (or over) capacity
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::*;
|
||||
/// let v = Stack::from([(); 10]);
|
||||
///
|
||||
/// assert!(v.is_full());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.len >= N
|
||||
}
|
||||
|
||||
/// Returns true if the stack contains no elements
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use cl_structures::stack::*;
|
||||
/// let v: Stack<(), 10> = Stack::new();
|
||||
///
|
||||
/// assert!(v.is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum InsertFailed<const N: usize> {
|
||||
Bounds(usize),
|
||||
Full,
|
||||
}
|
||||
impl<const N: usize> std::error::Error for InsertFailed<N> {}
|
||||
impl<const N: usize> std::fmt::Display for InsertFailed<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InsertFailed::Bounds(idx) => write!(f, "Index {idx} exceeded length {N}"),
|
||||
InsertFailed::Full => {
|
||||
write!(f, "Attempt to insert into full stack (length {N})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn zero_sized() {
|
||||
let v: Stack<(), { usize::MAX }> = Stack::new();
|
||||
assert_eq!(usize::MAX, v.capacity());
|
||||
assert_eq!(std::mem::size_of::<usize>(), std::mem::size_of_val(&v))
|
||||
}
|
||||
#[test]
|
||||
#[cfg_attr(debug_assertions, ignore = "calls ().drop() usize::MAX times")]
|
||||
fn from_usize_max_zst_array() {
|
||||
let mut v = Stack::from([(); usize::MAX]);
|
||||
assert_eq!(v.len(), usize::MAX);
|
||||
v.pop();
|
||||
assert_eq!(v.len(), usize::MAX - 1);
|
||||
}
|
||||
#[test]
|
||||
fn new() {
|
||||
let v: Stack<(), 255> = Stack::new();
|
||||
assert_eq!(0, v.len());
|
||||
assert_eq!(255, v.capacity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push() {
|
||||
let mut v: Stack<_, 64> = Stack::new();
|
||||
v.push(10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Attempted to push into full stack"]
|
||||
fn push_overflow() {
|
||||
let mut v = Stack::from([]);
|
||||
v.push(10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop() {
|
||||
let mut v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
assert_eq!(Some(9), v.pop());
|
||||
assert_eq!(Some(8), v.pop());
|
||||
assert_eq!(Some(7), v.pop());
|
||||
assert_eq!(Some(6), v.pop());
|
||||
assert_eq!(Some(5), v.pop());
|
||||
assert_eq!(Some(4), v.pop());
|
||||
assert_eq!(Some(3), v.pop());
|
||||
assert_eq!(Some(2), v.pop());
|
||||
assert_eq!(Some(1), v.pop());
|
||||
assert_eq!(None, v.pop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_smaller() {
|
||||
let v = Stack::from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
let mut v = v.resize::<2>();
|
||||
|
||||
assert_eq!(2, v.capacity());
|
||||
|
||||
assert_eq!(Some(2), v.pop());
|
||||
assert_eq!(Some(1), v.pop());
|
||||
assert_eq!(None, v.pop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resize_bigger() {
|
||||
let v = Stack::from([1, 2, 3, 4]);
|
||||
let mut v: Stack<_, 10> = v.resize();
|
||||
|
||||
assert_eq!(Some(4), v.pop());
|
||||
assert_eq!(Some(3), v.pop());
|
||||
assert_eq!(Some(2), v.pop());
|
||||
assert_eq!(Some(1), v.pop());
|
||||
assert_eq!(None, v.pop());
|
||||
}
|
||||
#[test]
|
||||
fn dangle() {
|
||||
let mut v: Stack<_, 2> = Stack::new();
|
||||
let a = 0;
|
||||
let b = 1;
|
||||
v.push(&a);
|
||||
v.push(&b);
|
||||
println!("{v:?}");
|
||||
}
|
||||
#[test]
|
||||
fn remove() {
|
||||
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||
|
||||
assert_eq!(3, v.remove(3));
|
||||
assert_eq!(4, v.remove(3));
|
||||
assert_eq!(5, v.remove(3));
|
||||
assert_eq!(Some(2), v.pop());
|
||||
assert_eq!(Some(1), v.pop());
|
||||
assert_eq!(Some(0), v.pop());
|
||||
assert_eq!(None, v.pop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_remove() {
|
||||
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||
assert_eq!(3, v.swap_remove(3));
|
||||
assert_eq!(&[0, 1, 2, 5, 4], v.as_slice());
|
||||
}
|
||||
#[test]
|
||||
fn swap_remove_last() {
|
||||
let mut v = Stack::from([0, 1, 2, 3, 4, 5]);
|
||||
assert_eq!(5, v.swap_remove(5));
|
||||
assert_eq!(&[0, 1, 2, 3, 4], v.as_slice())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert() {
|
||||
let mut v = Stack::from([0, 1, 2, 4, 5, 0x41414141]);
|
||||
v.pop();
|
||||
v.insert(3, 3);
|
||||
|
||||
assert_eq!(&[0, 1, 2, 3, 4, 5], v.as_slice())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Attempted to insert into full stack"]
|
||||
fn insert_overflow() {
|
||||
let mut v = Stack::from([0]);
|
||||
v.insert(0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop() {
|
||||
let v = Stack::from([
|
||||
Box::new(0),
|
||||
Box::new(1),
|
||||
Box::new(2),
|
||||
Box::new(3),
|
||||
Box::new(4),
|
||||
]);
|
||||
std::mem::drop(std::hint::black_box(v));
|
||||
}
|
||||
}
|
||||
221
cl-structures/src/tree.rs
Normal file
221
cl-structures/src/tree.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
//! An insert-only unordered tree, backed by a [Vec]
|
||||
//!
|
||||
//! # Examples
|
||||
//! ```
|
||||
//! use cl_structures::tree::{Tree, Node};
|
||||
//! // A tree can be created
|
||||
//! let mut tree = Tree::new();
|
||||
//! // Provided with a root node
|
||||
//! let root = tree.root("This is the root node").unwrap();
|
||||
//!
|
||||
//! // Nodes can be accessed by indexing
|
||||
//! assert_eq!(*tree[root].as_ref(), "This is the root node");
|
||||
//! // Nodes' data can be accessed directly by calling `get`/`get_mut`
|
||||
//! assert_eq!(tree.get(root).unwrap(), &"This is the root node")
|
||||
//! ```
|
||||
|
||||
// TODO: implement an Entry-style API for doing traversal algorithms
|
||||
|
||||
pub use self::tree_ref::Ref;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
pub mod tree_ref;
|
||||
|
||||
/// An insert-only unordered tree, backed by a [Vec]
|
||||
#[derive(Debug)]
|
||||
pub struct Tree<T> {
|
||||
nodes: Vec<Node<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for Tree<T> {
|
||||
fn default() -> Self {
|
||||
Self { nodes: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Getters
|
||||
impl<T> Tree<T> {
|
||||
pub fn get(&self, index: Ref<T>) -> Option<&T> {
|
||||
self.get_node(index).map(|node| &node.value)
|
||||
}
|
||||
pub fn get_mut(&mut self, index: Ref<T>) -> Option<&mut T> {
|
||||
self.get_node_mut(index).map(|node| &mut node.value)
|
||||
}
|
||||
pub fn get_node(&self, index: Ref<T>) -> Option<&Node<T>> {
|
||||
self.nodes.get(usize::from(index))
|
||||
}
|
||||
pub fn get_node_mut(&mut self, index: Ref<T>) -> Option<&mut Node<T>> {
|
||||
self.nodes.get_mut(usize::from(index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tree operations
|
||||
impl<T> Tree<T> {
|
||||
pub fn new() -> Self {
|
||||
Self { nodes: Default::default() }
|
||||
}
|
||||
|
||||
/// Creates a new root for the tree.
|
||||
///
|
||||
/// If the tree already has a root, the value will be returned.
|
||||
pub fn root(&mut self, value: T) -> Result<Ref<T>, T> {
|
||||
if self.is_empty() {
|
||||
// Create an index for the new node
|
||||
let node = Ref::new_unchecked(self.nodes.len());
|
||||
// add child to tree
|
||||
self.nodes.push(Node::from(value));
|
||||
Ok(node)
|
||||
} else {
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_root(&mut self) -> Option<Ref<T>> {
|
||||
match self.nodes.is_empty() {
|
||||
true => None,
|
||||
false => Some(Ref::new_unchecked(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a value into the tree as a child of the parent node
|
||||
///
|
||||
/// # Panics
|
||||
/// May panic if the node [Ref] is from a different tree
|
||||
pub fn insert(&mut self, value: T, parent: Ref<T>) -> Ref<T> {
|
||||
let child = Ref::new_unchecked(self.nodes.len());
|
||||
// add child to tree before parent
|
||||
self.nodes.push(Node::with_parent(value, parent));
|
||||
// add child to parent
|
||||
self[parent].children.push(child);
|
||||
|
||||
child
|
||||
}
|
||||
|
||||
/// Gets the depth of a node
|
||||
///
|
||||
/// # Panics
|
||||
/// May panic if the node [Ref] is from a different tree
|
||||
pub fn depth(&self, node: Ref<T>) -> usize {
|
||||
match self[node].parent {
|
||||
Some(node) => self.depth(node) + 1,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of branches in the tree
|
||||
pub fn branches(&self) -> usize {
|
||||
self.nodes.iter().fold(0, |edges, node| edges + node.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard data structure functions
|
||||
impl<T> Tree<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.nodes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Ref<T>> for Tree<T> {
|
||||
type Output = Node<T>;
|
||||
fn index(&self, index: Ref<T>) -> &Self::Output {
|
||||
self.get_node(index).expect("Ref should be inside Tree")
|
||||
}
|
||||
}
|
||||
impl<T> IndexMut<Ref<T>> for Tree<T> {
|
||||
fn index_mut(&mut self, index: Ref<T>) -> &mut Self::Output {
|
||||
self.get_node_mut(index).expect("Ref should be inside Tree")
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in a [Tree]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Node<T> {
|
||||
value: T,
|
||||
/// The parent
|
||||
parent: Option<Ref<T>>,
|
||||
/// The children
|
||||
children: Vec<Ref<T>>,
|
||||
}
|
||||
|
||||
impl<T> Node<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self { value, parent: None, children: vec![] }
|
||||
}
|
||||
pub const fn with_parent(value: T, parent: Ref<T>) -> Self {
|
||||
Self { value, parent: Some(parent), children: vec![] }
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &T {
|
||||
self.as_ref()
|
||||
}
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
self.as_mut()
|
||||
}
|
||||
pub fn swap(&mut self, value: T) -> T {
|
||||
std::mem::replace(&mut self.value, value)
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<Ref<T>> {
|
||||
self.parent
|
||||
}
|
||||
pub fn children(&self) -> &[Ref<T>] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.children.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.children.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Node<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<T> AsMut<T> for Node<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Node<T> {
|
||||
#[inline]
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[allow(unused)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_children() {
|
||||
let mut tree = Tree::new();
|
||||
let root = tree.root(0).unwrap();
|
||||
let one = tree.insert(1, root);
|
||||
let two = tree.insert(2, root);
|
||||
assert_eq!([one, two].as_slice(), tree[root].children());
|
||||
}
|
||||
#[test]
|
||||
fn nest_children() {
|
||||
let mut tree = Tree::new();
|
||||
let root = tree.root(0).unwrap();
|
||||
let one = tree.insert(1, root);
|
||||
let two = tree.insert(2, one);
|
||||
assert_eq!(&[one], tree[root].children());
|
||||
assert_eq!(&[two], tree[one].children());
|
||||
assert_eq!(tree[two].children(), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compares_equal() {}
|
||||
}
|
||||
70
cl-structures/src/tree/tree_ref.rs
Normal file
70
cl-structures/src/tree/tree_ref.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
//! An element in a [Tree](super::Tree)
|
||||
///
|
||||
/// Contains a niche, and as such, [`Option<TreeRef<T>>`] is free :D
|
||||
use std::{marker::PhantomData, num::NonZeroUsize};
|
||||
|
||||
/// An element of in a [Tree](super::Tree).
|
||||
//? The index of the node is stored as a [NonZeroUsize] for space savings
|
||||
//? Making Refs T-specific helps the user keep track of which Refs belong to which trees.
|
||||
//? This isn't bulletproof, of course, but it'll keep Ref<Foo> from being used on Tree<Bar>
|
||||
pub struct Ref<T: ?Sized>(NonZeroUsize, PhantomData<T>);
|
||||
|
||||
impl<T: ?Sized> Ref<T> {
|
||||
/// Constructs a new [Ref] with the given index
|
||||
pub fn new_unchecked(index: usize) -> Self {
|
||||
// Safety: index cannot be zero because we use saturating addition on unsigned type.
|
||||
Self(
|
||||
unsafe { NonZeroUsize::new_unchecked(index.saturating_add(1)) },
|
||||
PhantomData,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<Ref<T>> for usize {
|
||||
fn from(value: Ref<T>) -> Self {
|
||||
usize::from(value.0) - 1
|
||||
}
|
||||
}
|
||||
|
||||
/* --- implementations of derivable traits, because we don't need bounds here --- */
|
||||
|
||||
impl<T: ?Sized> std::fmt::Debug for Ref<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("TreeRef").field(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> std::hash::Hash for Ref<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
self.1.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PartialEq for Ref<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0 && self.1 == other.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Eq for Ref<T> {}
|
||||
|
||||
impl<T: ?Sized> PartialOrd for Ref<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Ord for Ref<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.0.cmp(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for Ref<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Copy for Ref<T> {}
|
||||
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 [TokenKind], some [TokenData], 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::TokenData;
|
||||
pub use token_type::{Punct, TokenKind};
|
||||
42
cl-token/src/token.rs
Normal file
42
cl-token/src/token.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
//! A [Token] contains a single unit of lexical information, and an optional bit of [TokenData]
|
||||
use super::{TokenData, TokenKind};
|
||||
|
||||
/// Contains a single unit of lexical information,
|
||||
/// and an optional bit of [TokenData]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Token {
|
||||
pub ty: TokenKind,
|
||||
pub data: TokenData,
|
||||
pub line: u32,
|
||||
pub col: u32,
|
||||
}
|
||||
impl Token {
|
||||
/// Creates a new [Token] out of a [TokenKind], [TokenData], line, and column.
|
||||
pub fn new(ty: TokenKind, data: impl Into<TokenData>, line: u32, col: u32) -> Self {
|
||||
Self { ty, data: data.into(), line, col }
|
||||
}
|
||||
/// Casts this token to a new [TokenKind]
|
||||
pub fn cast(self, ty: TokenKind) -> Self {
|
||||
Self { ty, ..self }
|
||||
}
|
||||
/// Returns the [TokenKind] of this token
|
||||
pub fn ty(&self) -> TokenKind {
|
||||
self.ty
|
||||
}
|
||||
/// Returns a reference to this token's [TokenData]
|
||||
pub fn data(&self) -> &TokenData {
|
||||
&self.data
|
||||
}
|
||||
/// Converts this token into its inner [TokenData]
|
||||
pub fn into_data(self) -> TokenData {
|
||||
self.data
|
||||
}
|
||||
/// Returns the line where this token originated
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line
|
||||
}
|
||||
/// Returns the column where this token originated
|
||||
pub fn col(&self) -> u32 {
|
||||
self.col
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
//! Additional data stored within a [Token](super::Token),
|
||||
//! external to its [Type](super::token_type::Type)
|
||||
//! external to its [TokenKind](super::token_type::TokenKind)
|
||||
/// Additional data stored within a [Token](super::Token),
|
||||
/// external to its [Type](super::token_type::Type)
|
||||
/// external to its [TokenKind](super::token_type::TokenKind)
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Data {
|
||||
/// [Token](super::Token) contains an [identifier](str)
|
||||
Identifier(Box<str>),
|
||||
pub enum TokenData {
|
||||
/// [Token](super::Token) contains a [String]
|
||||
String(String),
|
||||
/// [Token](super::Token) contains a [character](char)
|
||||
@@ -18,7 +16,6 @@ pub enum Data {
|
||||
None,
|
||||
}
|
||||
from! {
|
||||
value: &str => Self::Identifier(value.into()),
|
||||
value: String => Self::String(value),
|
||||
value: u128 => Self::Integer(value),
|
||||
value: f64 => Self::Float(value),
|
||||
@@ -27,19 +24,18 @@ from! {
|
||||
}
|
||||
/// Implements [From] for an enum
|
||||
macro from($($value:ident: $src:ty => $dst:expr),*$(,)?) {
|
||||
$(impl From<$src> for Data {
|
||||
$(impl From<$src> for TokenData {
|
||||
fn from($value: $src) -> Self { $dst }
|
||||
})*
|
||||
}
|
||||
impl std::fmt::Display for Data {
|
||||
impl std::fmt::Display for TokenData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Data::Identifier(v) => v.fmt(f),
|
||||
Data::String(v) => write!(f, "\"{v}\""),
|
||||
Data::Character(v) => write!(f, "'{v}'"),
|
||||
Data::Integer(v) => v.fmt(f),
|
||||
Data::Float(v) => v.fmt(f),
|
||||
Data::None => "None".fmt(f),
|
||||
TokenData::String(v) => write!(f, "\"{v}\""),
|
||||
TokenData::Character(v) => write!(f, "'{v}'"),
|
||||
TokenData::Integer(v) => v.fmt(f),
|
||||
TokenData::Float(v) => v.fmt(f),
|
||||
TokenData::None => "None".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
237
cl-token/src/token_type.rs
Normal file
237
cl-token/src/token_type.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
//! Stores a [Token's](super::Token) lexical information
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
/// Stores a [Token's](super::Token) lexical information
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TokenKind {
|
||||
/// Invalid sequence
|
||||
Invalid,
|
||||
/// Any kind of comment
|
||||
Comment,
|
||||
/// Any tokenizable literal (See [TokenData](super::TokenData))
|
||||
Literal,
|
||||
/// A non-keyword identifier
|
||||
Identifier,
|
||||
// A keyword
|
||||
Break,
|
||||
Cl,
|
||||
Const,
|
||||
Continue,
|
||||
Else,
|
||||
Enum,
|
||||
False,
|
||||
For,
|
||||
Fn,
|
||||
If,
|
||||
Impl,
|
||||
In,
|
||||
Let,
|
||||
Mod,
|
||||
Mut,
|
||||
Pub,
|
||||
Return,
|
||||
SelfKw,
|
||||
SelfTy,
|
||||
Static,
|
||||
Struct,
|
||||
Super,
|
||||
True,
|
||||
Type,
|
||||
While,
|
||||
/// Delimiter or punctuation
|
||||
Punct(Punct),
|
||||
}
|
||||
|
||||
/// An operator character (delimiter, punctuation)
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Punct {
|
||||
LCurly, // {
|
||||
RCurly, // }
|
||||
LBrack, // [
|
||||
RBrack, // ]
|
||||
LParen, // (
|
||||
RParen, // )
|
||||
Amp, // &
|
||||
AmpAmp, // &&
|
||||
AmpEq, // &=
|
||||
Arrow, // ->
|
||||
At, // @
|
||||
Backslash, // \
|
||||
Bang, // !
|
||||
BangBang, // !!
|
||||
BangEq, // !=
|
||||
Bar, // |
|
||||
BarBar, // ||
|
||||
BarEq, // |=
|
||||
Colon, // :
|
||||
ColonColon, // ::
|
||||
Comma, // ,
|
||||
Dot, // .
|
||||
DotDot, // ..
|
||||
DotDotEq, // ..=
|
||||
Eq, // =
|
||||
EqEq, // ==
|
||||
FatArrow, // =>
|
||||
Grave, // `
|
||||
Gt, // >
|
||||
GtEq, // >=
|
||||
GtGt, // >>
|
||||
GtGtEq, // >>=
|
||||
Hash, // #
|
||||
HashBang, // #!
|
||||
Lt, // <
|
||||
LtEq, // <=
|
||||
LtLt, // <<
|
||||
LtLtEq, // <<=
|
||||
Minus, // -
|
||||
MinusEq, // -=
|
||||
Plus, // +
|
||||
PlusEq, // +=
|
||||
Question, // ?
|
||||
Rem, // %
|
||||
RemEq, // %=
|
||||
Semi, // ;
|
||||
Slash, // /
|
||||
SlashEq, // /=
|
||||
Star, // *
|
||||
StarEq, // *=
|
||||
Tilde, // ~
|
||||
Xor, // ^
|
||||
XorEq, // ^=
|
||||
XorXor, // ^^
|
||||
}
|
||||
|
||||
impl Display for TokenKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TokenKind::Invalid => "invalid".fmt(f),
|
||||
TokenKind::Comment => "comment".fmt(f),
|
||||
TokenKind::Literal => "literal".fmt(f),
|
||||
TokenKind::Identifier => "identifier".fmt(f),
|
||||
|
||||
TokenKind::Break => "break".fmt(f),
|
||||
TokenKind::Cl => "cl".fmt(f),
|
||||
TokenKind::Const => "const".fmt(f),
|
||||
TokenKind::Continue => "continue".fmt(f),
|
||||
TokenKind::Else => "else".fmt(f),
|
||||
TokenKind::Enum => "enum".fmt(f),
|
||||
TokenKind::False => "false".fmt(f),
|
||||
TokenKind::For => "for".fmt(f),
|
||||
TokenKind::Fn => "fn".fmt(f),
|
||||
TokenKind::If => "if".fmt(f),
|
||||
TokenKind::Impl => "impl".fmt(f),
|
||||
TokenKind::In => "in".fmt(f),
|
||||
TokenKind::Let => "let".fmt(f),
|
||||
TokenKind::Mod => "mod".fmt(f),
|
||||
TokenKind::Mut => "mut".fmt(f),
|
||||
TokenKind::Pub => "pub".fmt(f),
|
||||
TokenKind::Return => "return".fmt(f),
|
||||
TokenKind::SelfKw => "self".fmt(f),
|
||||
TokenKind::SelfTy => "Self".fmt(f),
|
||||
TokenKind::Static => "static".fmt(f),
|
||||
TokenKind::Struct => "struct".fmt(f),
|
||||
TokenKind::Super => "super".fmt(f),
|
||||
TokenKind::True => "true".fmt(f),
|
||||
TokenKind::Type => "type".fmt(f),
|
||||
TokenKind::While => "while".fmt(f),
|
||||
|
||||
TokenKind::Punct(op) => op.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for TokenKind {
|
||||
/// [FromStr] can only fail when an identifier isn't a keyword
|
||||
type Err = ();
|
||||
/// Parses a string s to return a Keyword
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"break" => Self::Break,
|
||||
"cl" => Self::Cl,
|
||||
"const" => Self::Const,
|
||||
"continue" => Self::Continue,
|
||||
"else" => Self::Else,
|
||||
"enum" => Self::Enum,
|
||||
"false" => Self::False,
|
||||
"for" => Self::For,
|
||||
"fn" => Self::Fn,
|
||||
"if" => Self::If,
|
||||
"impl" => Self::Impl,
|
||||
"in" => Self::In,
|
||||
"let" => Self::Let,
|
||||
"mod" => Self::Mod,
|
||||
"mut" => Self::Mut,
|
||||
"pub" => Self::Pub,
|
||||
"return" => Self::Return,
|
||||
"self" => Self::SelfKw,
|
||||
"Self" => Self::SelfTy,
|
||||
"static" => Self::Static,
|
||||
"struct" => Self::Struct,
|
||||
"super" => Self::Super,
|
||||
"true" => Self::True,
|
||||
"type" => Self::Type,
|
||||
"while" => Self::While,
|
||||
_ => Err(())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Punct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Punct::LCurly => "{".fmt(f),
|
||||
Punct::RCurly => "}".fmt(f),
|
||||
Punct::LBrack => "[".fmt(f),
|
||||
Punct::RBrack => "]".fmt(f),
|
||||
Punct::LParen => "(".fmt(f),
|
||||
Punct::RParen => ")".fmt(f),
|
||||
Punct::Amp => "&".fmt(f),
|
||||
Punct::AmpAmp => "&&".fmt(f),
|
||||
Punct::AmpEq => "&=".fmt(f),
|
||||
Punct::Arrow => "->".fmt(f),
|
||||
Punct::At => "@".fmt(f),
|
||||
Punct::Backslash => "\\".fmt(f),
|
||||
Punct::Bang => "!".fmt(f),
|
||||
Punct::BangBang => "!!".fmt(f),
|
||||
Punct::BangEq => "!=".fmt(f),
|
||||
Punct::Bar => "|".fmt(f),
|
||||
Punct::BarBar => "||".fmt(f),
|
||||
Punct::BarEq => "|=".fmt(f),
|
||||
Punct::Colon => ":".fmt(f),
|
||||
Punct::ColonColon => "::".fmt(f),
|
||||
Punct::Comma => ",".fmt(f),
|
||||
Punct::Dot => ".".fmt(f),
|
||||
Punct::DotDot => "..".fmt(f),
|
||||
Punct::DotDotEq => "..=".fmt(f),
|
||||
Punct::Eq => "=".fmt(f),
|
||||
Punct::EqEq => "==".fmt(f),
|
||||
Punct::FatArrow => "=>".fmt(f),
|
||||
Punct::Grave => "`".fmt(f),
|
||||
Punct::Gt => ">".fmt(f),
|
||||
Punct::GtEq => ">=".fmt(f),
|
||||
Punct::GtGt => ">>".fmt(f),
|
||||
Punct::GtGtEq => ">>=".fmt(f),
|
||||
Punct::Hash => "#".fmt(f),
|
||||
Punct::HashBang => "#!".fmt(f),
|
||||
Punct::Lt => "<".fmt(f),
|
||||
Punct::LtEq => "<=".fmt(f),
|
||||
Punct::LtLt => "<<".fmt(f),
|
||||
Punct::LtLtEq => "<<=".fmt(f),
|
||||
Punct::Minus => "-".fmt(f),
|
||||
Punct::MinusEq => "-=".fmt(f),
|
||||
Punct::Plus => "+".fmt(f),
|
||||
Punct::PlusEq => "+=".fmt(f),
|
||||
Punct::Question => "?".fmt(f),
|
||||
Punct::Rem => "%".fmt(f),
|
||||
Punct::RemEq => "%=".fmt(f),
|
||||
Punct::Semi => ";".fmt(f),
|
||||
Punct::Slash => "/".fmt(f),
|
||||
Punct::SlashEq => "/=".fmt(f),
|
||||
Punct::Star => "*".fmt(f),
|
||||
Punct::StarEq => "*=".fmt(f),
|
||||
Punct::Tilde => "~".fmt(f),
|
||||
Punct::Xor => "^".fmt(f),
|
||||
Punct::XorEq => "^=".fmt(f),
|
||||
Punct::XorXor => "^^".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
12
cl-typeck/Cargo.toml
Normal file
12
cl-typeck/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[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" }
|
||||
cl-structures = { path = "../cl-structures" }
|
||||
909
cl-typeck/src/lib.rs
Normal file
909
cl-typeck/src/lib.rs
Normal file
@@ -0,0 +1,909 @@
|
||||
//! # The Conlang Type Checker
|
||||
//!
|
||||
//! As a statically typed language, Conlang requires a robust type checker to enforce correctness.
|
||||
#![feature(debug_closure_helpers)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
/*
|
||||
|
||||
The type checker keeps track of a *global intern pool* for Types and Values
|
||||
References to the intern pool are held by ID, and items cannot be freed from the pool EVER.
|
||||
|
||||
Items are inserted into their respective pools,
|
||||
|
||||
|
||||
*/
|
||||
|
||||
pub mod key {
|
||||
use cl_structures::intern_pool::*;
|
||||
|
||||
// define the index types
|
||||
make_intern_key! {
|
||||
/// Uniquely represents a [Def][1] in the [Def][1] [Pool]
|
||||
///
|
||||
/// [1]: crate::definition::Def
|
||||
DefID,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod definition {
|
||||
use crate::{key::DefID, module::Module, type_kind::TypeKind, value_kind::ValueKind};
|
||||
use cl_ast::{format::FmtPretty, Item, Meta, Visibility};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Def {
|
||||
pub name: String,
|
||||
pub vis: Visibility,
|
||||
pub meta: Vec<Meta>,
|
||||
pub kind: DefKind,
|
||||
pub source: Option<Item>,
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
impl Default for Def {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: Default::default(),
|
||||
vis: Default::default(),
|
||||
meta: Default::default(),
|
||||
kind: DefKind::Type(TypeKind::Module),
|
||||
source: Default::default(),
|
||||
module: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Def {
|
||||
pub fn new_module(
|
||||
name: String,
|
||||
vis: Visibility,
|
||||
meta: Vec<Meta>,
|
||||
parent: Option<DefID>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
vis,
|
||||
meta,
|
||||
kind: DefKind::Type(TypeKind::Module),
|
||||
source: None,
|
||||
module: Module { parent, ..Default::default() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Def {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, vis, meta, kind, source, module } = self;
|
||||
f.debug_struct("Def")
|
||||
.field("name", &name)
|
||||
.field("vis", &vis)
|
||||
.field_with("meta", |f| write!(f, "{meta:?}"))
|
||||
.field("kind", &kind)
|
||||
.field_with("source", |f| match source {
|
||||
Some(item) => write!(f.pretty(), "{{\n{item}\n}}"),
|
||||
None => todo!(),
|
||||
})
|
||||
.field("module", &module)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DefKind {
|
||||
/// A type, such as a ``
|
||||
Type(TypeKind),
|
||||
/// A value, such as a `const`, `static`, or `fn`
|
||||
Value(ValueKind),
|
||||
}
|
||||
|
||||
impl DefKind {
|
||||
pub fn is_type(&self) -> bool {
|
||||
matches!(self, Self::Type(_))
|
||||
}
|
||||
pub fn ty(&self) -> Option<&TypeKind> {
|
||||
match self {
|
||||
DefKind::Type(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn is_value(&self) -> bool {
|
||||
matches!(self, Self::Value(_))
|
||||
}
|
||||
pub fn value(&self) -> Option<&ValueKind> {
|
||||
match self {
|
||||
DefKind::Value(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod type_kind {
|
||||
//! A [TypeKind] represents an item in the Type Namespace
|
||||
//! (a component of a [Project](crate::project::Project)).
|
||||
|
||||
use cl_ast::Visibility;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
use crate::key::DefID;
|
||||
|
||||
/// The kind of a type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TypeKind {
|
||||
/// A type which has not yet been resolved
|
||||
Undecided,
|
||||
/// An alias for an already-defined type
|
||||
Alias(Option<DefID>),
|
||||
/// A primitive type, built-in to the compiler
|
||||
Intrinsic(Intrinsic),
|
||||
/// A user-defined abstract data type
|
||||
Adt(Adt),
|
||||
/// A reference to an already-defined type: &T
|
||||
Ref(DefID),
|
||||
/// A contiguous view of dynamically sized memory
|
||||
Slice(DefID),
|
||||
/// A function pointer which accepts multiple inputs and produces an output
|
||||
FnPtr { args: Vec<DefID>, rety: DefID },
|
||||
/// The unit type
|
||||
Empty,
|
||||
/// The never type
|
||||
Never,
|
||||
/// The Self type
|
||||
SelfTy,
|
||||
/// An untyped module
|
||||
Module,
|
||||
}
|
||||
|
||||
/// A user-defined Abstract Data Type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Adt {
|
||||
/// A union-like enum type
|
||||
Enum(Vec<(String, DefID)>),
|
||||
CLikeEnum(Vec<(String, u128)>),
|
||||
/// An enum with no fields, which can never be constructed
|
||||
FieldlessEnum,
|
||||
|
||||
/// A structural product type with named members
|
||||
Struct(Vec<(String, Visibility, DefID)>),
|
||||
/// A structural product type with unnamed members
|
||||
TupleStruct(Vec<(Visibility, DefID)>),
|
||||
/// A structural product type of neither named nor unnamed members
|
||||
UnitStruct,
|
||||
|
||||
/// A choose your own undefined behavior type
|
||||
/// TODO: should unions be a language feature?
|
||||
Union(Vec<(String, DefID)>),
|
||||
}
|
||||
|
||||
/// The set of compiler-intrinsic types.
|
||||
/// These primitive types have native implementations of the basic operations.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Intrinsic {
|
||||
/// An 8-bit signed integer: `#[intrinsic = "i8"]`
|
||||
I8,
|
||||
/// A 16-bit signed integer: `#[intrinsic = "i16"]`
|
||||
I16,
|
||||
/// A 32-bit signed integer: `#[intrinsic = "i32"]`
|
||||
I32,
|
||||
/// A 64-bit signed integer: `#[intrinsic = "i32"]`
|
||||
I64,
|
||||
// /// A 128-bit signed integer: `#[intrinsic = "i32"]`
|
||||
// I128,
|
||||
/// An 8-bit unsigned integer: `#[intrinsic = "u8"]`
|
||||
U8,
|
||||
/// A 16-bit unsigned integer: `#[intrinsic = "u16"]`
|
||||
U16,
|
||||
/// A 32-bit unsigned integer: `#[intrinsic = "u32"]`
|
||||
U32,
|
||||
/// A 64-bit unsigned integer: `#[intrinsic = "u64"]`
|
||||
U64,
|
||||
// /// A 128-bit unsigned integer: `#[intrinsic = "u128"]`
|
||||
// U128,
|
||||
/// A boolean (`true` or `false`): `#[intrinsic = "bool"]`
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl FromStr for Intrinsic {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"i8" => Intrinsic::I8,
|
||||
"i16" => Intrinsic::I16,
|
||||
"i32" => Intrinsic::I32,
|
||||
"i64" => Intrinsic::I64,
|
||||
"u8" => Intrinsic::U8,
|
||||
"u16" => Intrinsic::U16,
|
||||
"u32" => Intrinsic::U32,
|
||||
"u64" => Intrinsic::U64,
|
||||
"bool" => Intrinsic::Bool,
|
||||
_ => Err(())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Float {
|
||||
F32 = 0x20,
|
||||
F64,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod value_kind {
|
||||
//! A [ValueKind] represents an item in the Value Namespace
|
||||
//! (a component of a [Project](crate::project::Project)).
|
||||
|
||||
use crate::typeref::TypeRef;
|
||||
use cl_ast::Block;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ValueKind {
|
||||
Undecided,
|
||||
Const(TypeRef),
|
||||
Static(TypeRef),
|
||||
Fn {
|
||||
// TODO: Store the variable bindings here!
|
||||
args: Vec<TypeRef>,
|
||||
rety: TypeRef,
|
||||
body: Block,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub mod module {
|
||||
//! A [Module] is a node in the Module Tree (a component of a
|
||||
//! [Project](crate::project::Project))
|
||||
use crate::key::DefID;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A [Module] is a node in the Module Tree (a component of a
|
||||
/// [Project](crate::project::Project)).
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Module {
|
||||
pub parent: Option<DefID>,
|
||||
pub types: HashMap<String, DefID>,
|
||||
pub values: HashMap<String, DefID>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn new(parent: DefID) -> Self {
|
||||
Self { parent: Some(parent), ..Default::default() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod path {
|
||||
use cl_ast::{Path as AstPath, PathPart};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Path<'p> {
|
||||
pub absolute: bool,
|
||||
pub parts: &'p [PathPart],
|
||||
}
|
||||
|
||||
impl<'p> Path<'p> {
|
||||
pub fn new(path: &'p AstPath) -> Self {
|
||||
let AstPath { absolute, parts } = path;
|
||||
Self { absolute: *absolute, parts }
|
||||
}
|
||||
pub fn relative(self) -> Self {
|
||||
Self { absolute: false, ..self }
|
||||
}
|
||||
pub fn pop_front(self) -> Option<Self> {
|
||||
let Self { absolute, parts } = self;
|
||||
Some(Self { absolute, parts: parts.get(1..)? })
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.parts.is_empty()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.parts.len()
|
||||
}
|
||||
pub fn front(&self) -> Option<&PathPart> {
|
||||
self.parts.first()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p> From<&'p AstPath> for Path<'p> {
|
||||
fn from(value: &'p AstPath) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Path<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
const SEPARATOR: &str = "::";
|
||||
let Self { absolute, parts } = self;
|
||||
if *absolute {
|
||||
write!(f, "{SEPARATOR}")?
|
||||
}
|
||||
for (idx, part) in parts.iter().enumerate() {
|
||||
write!(f, "{}{part}", if idx > 0 { SEPARATOR } else { "" })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod project {
|
||||
use crate::{
|
||||
definition::{Def, DefKind},
|
||||
key::DefID,
|
||||
path::Path,
|
||||
type_kind::TypeKind,
|
||||
};
|
||||
use cl_ast::{Identifier, PathPart, Visibility};
|
||||
use cl_structures::intern_pool::Pool;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Project {
|
||||
pub pool: Pool<Def, DefID>,
|
||||
pub module_root: DefID,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
impl Default for Project {
|
||||
fn default() -> Self {
|
||||
let mut pool = Pool::default();
|
||||
let module_root = pool.insert(Def::default());
|
||||
// Insert the Never(!) type
|
||||
let never = pool.insert(Def {
|
||||
name: String::from("!"),
|
||||
vis: Visibility::Public,
|
||||
kind: DefKind::Type(TypeKind::Never),
|
||||
..Default::default()
|
||||
});
|
||||
pool[module_root]
|
||||
.module
|
||||
.types
|
||||
.insert(String::from("!"), never);
|
||||
Self { pool, module_root }
|
||||
}
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn parent_of(&self, module: DefID) -> Option<DefID> {
|
||||
self[module].module.parent
|
||||
}
|
||||
pub fn root_of(&self, module: DefID) -> DefID {
|
||||
match self.parent_of(module) {
|
||||
Some(module) => self.root_of(module),
|
||||
None => module,
|
||||
}
|
||||
}
|
||||
/// Resolves a path within a module tree, finding the innermost module.
|
||||
/// Returns the remaining path parts.
|
||||
pub fn get_type<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
|
||||
// TODO: Cache module lookups
|
||||
if path.absolute {
|
||||
self.get_type(path.relative(), self.root_of(within))
|
||||
} else if let Some(front) = path.front() {
|
||||
let module = &self[within].module;
|
||||
match front {
|
||||
PathPart::SelfKw => self.get_type(path.pop_front()?, within),
|
||||
PathPart::SuperKw => self.get_type(path.pop_front()?, module.parent?),
|
||||
PathPart::Ident(Identifier(name)) => match module.types.get(name) {
|
||||
Some(&submodule) => self.get_type(path.pop_front()?, submodule),
|
||||
None => Some((within, path)),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Some((within, path))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value<'a>(&self, path: Path<'a>, within: DefID) -> Option<(DefID, Path<'a>)> {
|
||||
match path.front()? {
|
||||
PathPart::Ident(Identifier(name)) => Some((
|
||||
self[within].module.values.get(name).copied()?,
|
||||
path.pop_front()?,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn insert_type(&mut self, name: String, value: Def, parent: DefID) -> Option<DefID> {
|
||||
let id = self.pool.insert(value);
|
||||
self[parent].module.types.insert(name, id)
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
pub fn insert_value(&mut self, name: String, value: Def, parent: DefID) -> Option<DefID> {
|
||||
let id = self.pool.insert(value);
|
||||
self[parent].module.values.insert(name, id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [Index] and [IndexMut] for [Project]: `self.table[ID] -> Definition`
|
||||
macro_rules! impl_index {
|
||||
($(self.$table:ident[$idx:ty] -> $out:ty),*$(,)?) => {$(
|
||||
impl Index<$idx> for Project {
|
||||
type Output = $out;
|
||||
fn index(&self, index: $idx) -> &Self::Output {
|
||||
&self.$table[index]
|
||||
}
|
||||
}
|
||||
impl IndexMut<$idx> for Project {
|
||||
fn index_mut(&mut self, index: $idx) -> &mut Self::Output {
|
||||
&mut self.$table[index]
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
impl_index! {
|
||||
self.pool[DefID] -> Def,
|
||||
// self.types[TypeID] -> TypeDef,
|
||||
// self.values[ValueID] -> ValueDef,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod name_collector {
|
||||
//! Performs step 1 of type checking: Collecting all the names of things into [Module] units
|
||||
|
||||
use crate::{
|
||||
definition::{Def, DefKind},
|
||||
key,
|
||||
project::Project,
|
||||
type_kind::{Adt, TypeKind},
|
||||
value_kind::ValueKind,
|
||||
};
|
||||
use cl_ast::*;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Collects types for future use
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct NameCollector<'prj> {
|
||||
/// A stack of the current modules
|
||||
pub mod_stack: Vec<key::DefID>,
|
||||
/// The [Project], the type checker and resolver's central data store
|
||||
pub project: &'prj mut Project,
|
||||
}
|
||||
|
||||
impl<'prj> NameCollector<'prj> {
|
||||
pub fn new(project: &'prj mut Project) -> Self {
|
||||
// create a root module
|
||||
Self { mod_stack: vec![project.module_root], project }
|
||||
}
|
||||
|
||||
/// Gets the currently traversed parent module
|
||||
pub fn parent(&self) -> Option<key::DefID> {
|
||||
self.mod_stack.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NameCollector<'_> {
|
||||
type Target = Project;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.project
|
||||
}
|
||||
}
|
||||
impl DerefMut for NameCollector<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.project
|
||||
}
|
||||
}
|
||||
|
||||
impl NameCollector<'_> {
|
||||
pub fn file(&mut self, f: &File) -> Result<(), &'static str> {
|
||||
let parent = self.parent().ok_or("No parent to add item to")?;
|
||||
|
||||
for item in &f.items {
|
||||
let def = match &item.kind {
|
||||
// modules
|
||||
// types
|
||||
ItemKind::Module(_) => {
|
||||
self.module(item)?;
|
||||
continue;
|
||||
}
|
||||
ItemKind::Enum(_) => Some(self.ty_enum(item)?),
|
||||
ItemKind::Alias(_) => Some(self.ty_alias(item)?),
|
||||
ItemKind::Struct(_) => Some(self.ty_struct(item)?),
|
||||
// values processed by the value collector
|
||||
ItemKind::Const(_) => Some(self.val_const(item)?),
|
||||
ItemKind::Static(_) => Some(self.val_static(item)?),
|
||||
ItemKind::Function(_) => Some(self.val_function(item)?),
|
||||
ItemKind::Impl(_) => None,
|
||||
};
|
||||
let Some(def) = def else { continue };
|
||||
match def.kind {
|
||||
DefKind::Type(_) => {
|
||||
if let Some(v) = self.insert_type(def.name.clone(), def, parent) {
|
||||
panic!("Redefinition of type {} ({v:?})!", self[v].name)
|
||||
}
|
||||
}
|
||||
DefKind::Value(_) => {
|
||||
if let Some(v) = self.insert_value(def.name.clone(), def, parent) {
|
||||
panic!("Redefinition of value {} ({v:?})!", self[v].name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Collects a [Module]
|
||||
pub fn module(&mut self, m: &Item) -> Result<(), &'static str> {
|
||||
let Item { kind: ItemKind::Module(Module { name, kind }), vis, attrs, .. } = m else {
|
||||
Err("module called on Item which was not an ItemKind::Module")?
|
||||
};
|
||||
let ModuleKind::Inline(kind) = kind else {
|
||||
Err("Out-of-line modules not yet supported")?
|
||||
};
|
||||
let parent = self.parent().ok_or("No parent to add module to")?;
|
||||
|
||||
let module = self.pool.insert(Def::new_module(
|
||||
name.0.clone(),
|
||||
*vis,
|
||||
attrs.meta.clone(),
|
||||
Some(parent),
|
||||
));
|
||||
|
||||
self[parent]
|
||||
.module
|
||||
.types
|
||||
.insert(name.0.clone(), module)
|
||||
.is_some()
|
||||
.then(|| panic!("Error: redefinition of module {name}"));
|
||||
|
||||
self.mod_stack.push(module);
|
||||
let out = self.file(kind);
|
||||
self.mod_stack.pop();
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
/// Type collection
|
||||
impl NameCollector<'_> {
|
||||
/// Collects an [Item] of type [ItemKind::Enum]
|
||||
pub fn ty_enum(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
let Item { kind: ItemKind::Enum(Enum { name, kind }), vis, attrs, .. } = item else {
|
||||
Err("Enum called on item which was not ItemKind::Enum")?
|
||||
};
|
||||
let kind = match kind {
|
||||
EnumKind::NoVariants => DefKind::Type(TypeKind::Adt(Adt::FieldlessEnum)),
|
||||
EnumKind::Variants(_) => DefKind::Type(TypeKind::Undecided),
|
||||
};
|
||||
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind,
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Collects an [Item] of type [ItemKind::Alias]
|
||||
pub fn ty_alias(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
let Item { kind: ItemKind::Alias(Alias { to: name, from }), vis, attrs, .. } = item
|
||||
else {
|
||||
Err("Alias called on Item which was not ItemKind::Alias")?
|
||||
};
|
||||
|
||||
let mut kind = match from {
|
||||
Some(_) => DefKind::Type(TypeKind::Undecided),
|
||||
None => DefKind::Type(TypeKind::Alias(None)),
|
||||
};
|
||||
|
||||
for meta in &attrs.meta {
|
||||
let Meta { name: meta_name, kind: meta_kind } = meta;
|
||||
match (meta_name.0.as_str(), meta_kind) {
|
||||
("intrinsic", MetaKind::Equals(Literal::String(intrinsic))) => {
|
||||
kind = DefKind::Type(TypeKind::Intrinsic(
|
||||
intrinsic.parse().map_err(|_| "unknown intrinsic type")?,
|
||||
));
|
||||
}
|
||||
("intrinsic", MetaKind::Plain) => {
|
||||
kind = DefKind::Type(TypeKind::Intrinsic(
|
||||
name.0.parse().map_err(|_| "Unknown intrinsic type")?,
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind,
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Collects an [Item] of type [ItemKind::Struct]
|
||||
pub fn ty_struct(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
let Item { kind: ItemKind::Struct(Struct { name, kind }), vis, attrs, .. } = item
|
||||
else {
|
||||
Err("Struct called on item which was not ItemKind::Struct")?
|
||||
};
|
||||
let kind = match kind {
|
||||
StructKind::Empty => DefKind::Type(TypeKind::Adt(Adt::UnitStruct)),
|
||||
StructKind::Tuple(_) => DefKind::Type(TypeKind::Undecided),
|
||||
StructKind::Struct(_) => DefKind::Type(TypeKind::Undecided),
|
||||
};
|
||||
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind,
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Value collection
|
||||
impl NameCollector<'_> {
|
||||
pub fn val_const(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
let Item { kind: ItemKind::Const(Const { name, .. }), vis, attrs, .. } = item else {
|
||||
Err("Const called on Item which was not ItemKind::Const")?
|
||||
};
|
||||
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind: DefKind::Value(ValueKind::Undecided),
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn val_static(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
let Item { kind: ItemKind::Static(Static { name, .. }), vis, attrs, .. } = item else {
|
||||
Err("Static called on Item which was not ItemKind::Static")?
|
||||
};
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind: DefKind::Type(TypeKind::Undecided),
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn val_function(&mut self, item: &Item) -> Result<Def, &'static str> {
|
||||
// TODO: treat function bodies like modules with internal items
|
||||
let Item { kind: ItemKind::Function(Function { name, .. }), vis, attrs, .. } = item
|
||||
else {
|
||||
Err("val_function called on Item which was not ItemKind::Function")?
|
||||
};
|
||||
Ok(Def {
|
||||
name: name.0.clone(),
|
||||
vis: *vis,
|
||||
meta: attrs.meta.clone(),
|
||||
kind: DefKind::Value(ValueKind::Undecided),
|
||||
source: Some(item.clone()),
|
||||
module: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod type_resolver {
|
||||
//! Performs step 2 of type checking: Evaluating type definitions
|
||||
#![allow(unused)]
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use cl_ast::*;
|
||||
|
||||
use crate::{definition::Def, key::DefID, project::Project};
|
||||
|
||||
pub struct TypeResolver<'prj> {
|
||||
pub project: &'prj mut Project,
|
||||
}
|
||||
|
||||
impl Deref for TypeResolver<'_> {
|
||||
type Target = Project;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.project
|
||||
}
|
||||
}
|
||||
impl DerefMut for TypeResolver<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.project
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeResolver<'_> {
|
||||
pub fn resolve(&mut self) -> Result<bool, &str> {
|
||||
#![allow(unused)]
|
||||
for typedef in self.pool.iter_mut().filter(|v| v.kind.is_type()) {
|
||||
let Def { name, vis, meta: attr, kind, source: Some(ref definition), module: _ } =
|
||||
typedef
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match &definition.kind {
|
||||
ItemKind::Alias(Alias { to: _, from: Some(from) }) => match &from.kind {
|
||||
TyKind::Never => todo!(),
|
||||
TyKind::Empty => todo!(),
|
||||
TyKind::SelfTy => todo!(),
|
||||
TyKind::Path(_) => todo!(),
|
||||
TyKind::Tuple(_) => todo!(),
|
||||
TyKind::Ref(_) => todo!(),
|
||||
TyKind::Fn(_) => todo!(),
|
||||
},
|
||||
ItemKind::Alias(_) => {}
|
||||
ItemKind::Const(_) => todo!(),
|
||||
ItemKind::Static(_) => todo!(),
|
||||
ItemKind::Module(_) => todo!(),
|
||||
ItemKind::Function(_) => {}
|
||||
ItemKind::Struct(_) => {}
|
||||
ItemKind::Enum(_) => {}
|
||||
ItemKind::Impl(_) => {}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn get_type(&self, kind: &TyKind) -> Option<DefID> {
|
||||
match kind {
|
||||
TyKind::Never => todo!(),
|
||||
TyKind::Empty => todo!(),
|
||||
TyKind::SelfTy => todo!(),
|
||||
TyKind::Path(_) => todo!(),
|
||||
TyKind::Tuple(_) => todo!(),
|
||||
TyKind::Ref(_) => todo!(),
|
||||
TyKind::Fn(_) => todo!(),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod typeref {
|
||||
//! Stores type and reference info
|
||||
|
||||
use crate::key::DefID;
|
||||
|
||||
/// The Type struct represents all valid types, and can be trivially equality-compared
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TypeRef {
|
||||
/// You can only have a pointer chain 65535 pointers long.
|
||||
ref_depth: u16,
|
||||
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
|
||||
kind: RefKind,
|
||||
}
|
||||
|
||||
/// Types can be [Generic](RefKind::Generic) or [Concrete](RefKind::Concrete)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RefKind {
|
||||
/// A Concrete type has an associated [Def](super::definition::Def)
|
||||
Concrete(DefID),
|
||||
/// A Generic type is a *locally unique* comparable value,
|
||||
/// valid only until the end of its typing context.
|
||||
/// This is usually the surrounding function.
|
||||
Generic(usize),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// 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>> {
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
pub mod rule {
|
||||
use crate::{key::DefID, typeref::TypeRef};
|
||||
|
||||
pub struct Rule {
|
||||
/// What is this Rule for?
|
||||
pub operation: (),
|
||||
/// What inputs does it take?
|
||||
pub inputs: Vec<TypeRef>,
|
||||
/// What output does it produce?
|
||||
pub output: TypeRef,
|
||||
/// Where did this rule come from?
|
||||
pub through: Origin,
|
||||
}
|
||||
|
||||
// TODO: Genericize
|
||||
pub enum Operation {
|
||||
Mul,
|
||||
Div,
|
||||
Rem,
|
||||
Add,
|
||||
Sub,
|
||||
|
||||
Deref,
|
||||
Neg,
|
||||
Not,
|
||||
At,
|
||||
Tilde,
|
||||
|
||||
Index,
|
||||
|
||||
If,
|
||||
While,
|
||||
For,
|
||||
}
|
||||
|
||||
pub enum Origin {
|
||||
/// This rule is built into the compiler
|
||||
Intrinsic,
|
||||
/// This rule is derived from an implementation on a type
|
||||
Extrinsic(DefID),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod typeck {
|
||||
#![allow(unused)]
|
||||
use cl_ast::*;
|
||||
|
||||
pub struct Context {
|
||||
rules: (),
|
||||
}
|
||||
|
||||
trait TypeCheck {}
|
||||
}
|
||||
|
||||
//
|
||||
44
grammar.ebnf
44
grammar.ebnf
@@ -1,38 +1,38 @@
|
||||
(* Conlang Expression Grammar *)
|
||||
Start = File ;
|
||||
Start = File EOI ;
|
||||
|
||||
Mutability = "mut"? ;
|
||||
Visibility = "pub"? ;
|
||||
|
||||
|
||||
File = Item* EOI ;
|
||||
File = Item* ;
|
||||
|
||||
|
||||
Attrs = ('#' '[' (Meta ',') Meta? ']')* ;
|
||||
Attrs = ('#' '[' (Meta ',')* Meta? ']')* ;
|
||||
Meta = Identifier ('=' Literal | '(' (Literal ',')* Literal? ')')? ;
|
||||
|
||||
|
||||
Item = Attrs* Visibility ItemKind ;
|
||||
Item = Attrs Visibility ItemKind ;
|
||||
ItemKind = Const | Static | Module
|
||||
| Function | Struct | Enum
|
||||
| Alias | Impl ;
|
||||
|
||||
|
||||
(* item *)
|
||||
Const = "const" Identifier ':' Type = Expr ';' ;
|
||||
Const = "const" Identifier ':' Ty '=' Expr ';' ;
|
||||
|
||||
Static = "static" Mutability Identifier ':' Type = Expr ';' ;
|
||||
Static = "static" Mutability Identifier ':' Ty '=' Expr ';' ;
|
||||
|
||||
Module = "mod" Identifier ModuleKind ;
|
||||
ModuleKind = '{' Item* '}' | ';' ;
|
||||
|
||||
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Type)? Block? ;
|
||||
Param = Mutability Identifier ':' Type ;
|
||||
Function = "fn" Identifier '(' (Param ',')* Param? ')' ('->' Ty)? Block? ;
|
||||
Param = Mutability Identifier ':' Ty ;
|
||||
|
||||
Struct = "struct" Identifier (StructTuple | StructBody)?;
|
||||
StructBody = '{' (StructMember ',')* StructMember? '}' ;
|
||||
StructTuple = TyTuple ;
|
||||
StructMember = Visibility Identifier ':' Type ;
|
||||
StructMember = Visibility Identifier ':' Ty ;
|
||||
|
||||
Enum = "enum" Identifier '{' (Variant ',')* Variant? '}' ;
|
||||
Variant = Identifier (VarStruct | VarTuple | VarCLike)? ;
|
||||
@@ -40,7 +40,7 @@ VarStruct = '{' (StructMember ',')* StructMember? '}' ;
|
||||
VarTuple = TyTuple ;
|
||||
VarCLike = '=' INTEGER ;
|
||||
|
||||
Alias = "type" Ty ('=' Ty)? ';' ;
|
||||
Alias = "type" Identifier ('=' Ty)? ';' ;
|
||||
|
||||
Impl = "impl" Path '{' Item* '}' ;
|
||||
(* TODO: Impl Trait for Target*)
|
||||
@@ -51,8 +51,9 @@ Ty = Never | Empty | Path | TyTuple | TyRef | TyFn ;
|
||||
Never = '!' ;
|
||||
Empty = '(' ')' ;
|
||||
TyTuple = '(' (Ty ',')* Ty? ')' ;
|
||||
TyRef = ('&' | '&&')* Path ;
|
||||
TyFn = "fn" TyTuple (-> Ty)? ;
|
||||
TyRef = Amps* Path ;
|
||||
Amps = '&' | '&&' ;
|
||||
TyFn = "fn" TyTuple ('->' Ty)? ;
|
||||
|
||||
|
||||
(* path *)
|
||||
@@ -74,24 +75,19 @@ Bool = "true" | "false" ;
|
||||
|
||||
|
||||
(* expr *)
|
||||
ExprKind = Assign | Compare | Range | Logic | Bitwise | Shift
|
||||
| Factor | Term | Unary | Member | Call | Index
|
||||
| Path | Literal | Array | ArrayRep | AddrOf
|
||||
| Block | Group
|
||||
| While | If | For | Break | Return | Continue ;
|
||||
|
||||
Expr = Assign ;
|
||||
|
||||
Assign = Path (AssignKind Assign ) | Compare ;
|
||||
|
||||
Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ;
|
||||
(* Binary = Compare | Range | Logic | Bitwise | Shift | Factor | Term ; *)
|
||||
Compare = Range (CompareOp Range )* ;
|
||||
Range = Logic (RangeOp Logic )* ;
|
||||
Logic = Bitwise (LogicOp Bitwise)* ;
|
||||
Bitwise = Shift (BitwiseOp Shift )* ;
|
||||
Shift = Factor (ShiftOp Factor )* ;
|
||||
Factor = Term (FactorOp Term )* ;
|
||||
Term = Unary (FactorOp Unary )* ;
|
||||
Term = Unary (TermOp Unary )* ;
|
||||
|
||||
Unary = (UnaryKind)* Member ;
|
||||
|
||||
@@ -111,13 +107,12 @@ Literal = STRING | CHARACTER | FLOAT | INTEGER | Bool ;
|
||||
Array = '[' (Expr ',')* Expr? ']' ;
|
||||
ArrayRep = '[' Expr ';' Expr ']' ;
|
||||
|
||||
AddrOf = ('&' | '&&')* Mutability? Expr ;
|
||||
AddrOf = Amps Amps* Mutability Expr ;
|
||||
|
||||
Block = '{' Stmt* '}';
|
||||
|
||||
Group = '(' (Empty | Expr | Tuple) ')' ;
|
||||
Group = Empty | '(' (Expr | Tuple) ')' ;
|
||||
Tuple = (Expr ',')* Expr? ;
|
||||
Empty = ;
|
||||
|
||||
While = "while" Expr Block Else ;
|
||||
If = "if" Expr Block Else ;
|
||||
@@ -127,11 +122,8 @@ Break = "break" Expr ;
|
||||
Return = "return" Expr ;
|
||||
Continue = "continue" ;
|
||||
|
||||
AssignKind = '=' | '+=' | '-=' | '*=' | '/=' |
|
||||
'&=' | '|=' | '^=' |'<<=' |'>>=' ;
|
||||
AssignKind = '=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' |'<<=' |'>>=' ;
|
||||
|
||||
BinaryKind = CompareOp | RangeOp | LogicOp | BitwiseOp
|
||||
| ShiftOp | TermOp | FactorOp ;
|
||||
CompareOp = '<' | '<=' | '==' | '!=' | '>=' | '>' ;
|
||||
RangeOp = '..' | '..=' ;
|
||||
LogicOp = '&&' | '||' | '^^' ;
|
||||
|
||||
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"
|
||||
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;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,57 +0,0 @@
|
||||
//! # Token
|
||||
//!
|
||||
//! 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,
|
||||
/// and an optional bit of [Data]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Token {
|
||||
ty: Type,
|
||||
data: Data,
|
||||
line: u32,
|
||||
col: u32,
|
||||
}
|
||||
impl Token {
|
||||
/// Creates a new [Token] out of a [Type], [Data], line, and column.
|
||||
pub fn new(ty: Type, data: impl Into<Data>, line: u32, col: u32) -> Self {
|
||||
Self { ty, data: data.into(), line, col }
|
||||
}
|
||||
/// Casts this token to a new [Type]
|
||||
pub fn cast(self, ty: Type) -> Self {
|
||||
Self { ty, ..self }
|
||||
}
|
||||
/// Returns the [Type] of this token
|
||||
pub fn ty(&self) -> Type {
|
||||
self.ty
|
||||
}
|
||||
/// Returns a reference to this token's [Data]
|
||||
pub fn data(&self) -> &Data {
|
||||
&self.data
|
||||
}
|
||||
/// Converts this token into its inner [Data]
|
||||
pub fn into_data(self) -> Data {
|
||||
self.data
|
||||
}
|
||||
/// Returns the line where this token originated
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line
|
||||
}
|
||||
/// Returns the column where this token originated
|
||||
pub fn col(&self) -> u32 {
|
||||
self.col
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
//! Stores a [Token's](super::Token) lexical information
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
/// Stores a [Token's](super::Token) lexical information
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Type {
|
||||
// Invalid syntax
|
||||
Invalid,
|
||||
// Any kind of comment
|
||||
Comment,
|
||||
// Any identifier
|
||||
Identifier,
|
||||
Keyword(Keyword),
|
||||
// Literals
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Character,
|
||||
// Delimiters and punctuation
|
||||
LCurly, // {
|
||||
RCurly, // }
|
||||
LBrack, // [
|
||||
RBrack, // ]
|
||||
LParen, // (
|
||||
RParen, // )
|
||||
Amp, // &
|
||||
AmpAmp, // &&
|
||||
AmpEq, // &=
|
||||
Arrow, // ->
|
||||
At, // @
|
||||
Backslash, // \
|
||||
Bang, // !
|
||||
BangBang, // !!
|
||||
BangEq, // !=
|
||||
Bar, // |
|
||||
BarBar, // ||
|
||||
BarEq, // |=
|
||||
Colon, // :
|
||||
ColonColon, // ::
|
||||
Comma, // ,
|
||||
Dot, // .
|
||||
DotDot, // ..
|
||||
DotDotEq, // ..=
|
||||
Eq, // =
|
||||
EqEq, // ==
|
||||
FatArrow, // =>
|
||||
Grave, // `
|
||||
Gt, // >
|
||||
GtEq, // >=
|
||||
GtGt, // >>
|
||||
GtGtEq, // >>=
|
||||
Hash, // #
|
||||
HashBang, // #!
|
||||
Lt, // <
|
||||
LtEq, // <=
|
||||
LtLt, // <<
|
||||
LtLtEq, // <<=
|
||||
Minus, // -
|
||||
MinusEq, // -=
|
||||
Plus, // +
|
||||
PlusEq, // +=
|
||||
Question, // ?
|
||||
Rem, // %
|
||||
RemEq, // %=
|
||||
Semi, // ;
|
||||
Slash, // /
|
||||
SlashEq, // /=
|
||||
Star, // *
|
||||
StarEq, // *=
|
||||
Tilde, // ~
|
||||
Xor, // ^
|
||||
XorEq, // ^=
|
||||
XorXor, // ^^
|
||||
}
|
||||
|
||||
/// Represents a reserved word.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Keyword {
|
||||
Break,
|
||||
Cl,
|
||||
Const,
|
||||
Continue,
|
||||
Else,
|
||||
Enum,
|
||||
False,
|
||||
For,
|
||||
Fn,
|
||||
If,
|
||||
Impl,
|
||||
In,
|
||||
Let,
|
||||
Mod,
|
||||
Mut,
|
||||
Pub,
|
||||
Return,
|
||||
SelfKw,
|
||||
SelfTy,
|
||||
Static,
|
||||
Struct,
|
||||
Super,
|
||||
True,
|
||||
Type,
|
||||
While,
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Type::Invalid => "invalid".fmt(f),
|
||||
Type::Comment => "comment".fmt(f),
|
||||
Type::Identifier => "identifier".fmt(f),
|
||||
Type::Keyword(k) => k.fmt(f),
|
||||
Type::Integer => "integer literal".fmt(f),
|
||||
Type::Float => "float literal".fmt(f),
|
||||
Type::String => "string literal".fmt(f),
|
||||
Type::Character => "char literal".fmt(f),
|
||||
Type::LCurly => "left curly".fmt(f),
|
||||
Type::RCurly => "right curly".fmt(f),
|
||||
Type::LBrack => "left brack".fmt(f),
|
||||
Type::RBrack => "right brack".fmt(f),
|
||||
Type::LParen => "left paren".fmt(f),
|
||||
Type::RParen => "right paren".fmt(f),
|
||||
Type::Amp => "and".fmt(f),
|
||||
Type::AmpAmp => "and-and".fmt(f),
|
||||
Type::AmpEq => "and-assign".fmt(f),
|
||||
Type::Arrow => "arrow".fmt(f),
|
||||
Type::At => "at".fmt(f),
|
||||
Type::Backslash => "backslash".fmt(f),
|
||||
Type::Bang => "bang".fmt(f),
|
||||
Type::BangBang => "not-not".fmt(f),
|
||||
Type::BangEq => "not equal to".fmt(f),
|
||||
Type::Bar => "or".fmt(f),
|
||||
Type::BarBar => "or-or".fmt(f),
|
||||
Type::BarEq => "or-assign".fmt(f),
|
||||
Type::Colon => "colon".fmt(f),
|
||||
Type::ColonColon => "path separator".fmt(f),
|
||||
Type::Comma => "comma".fmt(f),
|
||||
Type::Dot => "dot".fmt(f),
|
||||
Type::DotDot => "exclusive range".fmt(f),
|
||||
Type::DotDotEq => "inclusive range".fmt(f),
|
||||
Type::Eq => "assign".fmt(f),
|
||||
Type::EqEq => "equal to".fmt(f),
|
||||
Type::FatArrow => "fat arrow".fmt(f),
|
||||
Type::Grave => "grave".fmt(f),
|
||||
Type::Gt => "greater than".fmt(f),
|
||||
Type::GtEq => "greater than or equal to".fmt(f),
|
||||
Type::GtGt => "shift right".fmt(f),
|
||||
Type::GtGtEq => "shift right-assign".fmt(f),
|
||||
Type::Hash => "hash".fmt(f),
|
||||
Type::HashBang => "shebang".fmt(f),
|
||||
Type::Lt => "less than".fmt(f),
|
||||
Type::LtEq => "less than or equal to".fmt(f),
|
||||
Type::LtLt => "shift left".fmt(f),
|
||||
Type::LtLtEq => "shift left-assign".fmt(f),
|
||||
Type::Minus => "sub".fmt(f),
|
||||
Type::MinusEq => "sub-assign".fmt(f),
|
||||
Type::Plus => "add".fmt(f),
|
||||
Type::PlusEq => "add-assign".fmt(f),
|
||||
Type::Question => "huh?".fmt(f),
|
||||
Type::Rem => "rem".fmt(f),
|
||||
Type::RemEq => "rem-assign".fmt(f),
|
||||
Type::Semi => "ignore".fmt(f),
|
||||
Type::Slash => "div".fmt(f),
|
||||
Type::SlashEq => "div-assign".fmt(f),
|
||||
Type::Star => "star".fmt(f),
|
||||
Type::StarEq => "star-assign".fmt(f),
|
||||
Type::Tilde => "tilde".fmt(f),
|
||||
Type::Xor => "xor".fmt(f),
|
||||
Type::XorEq => "xor-assign".fmt(f),
|
||||
Type::XorXor => "cat-ears".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Keyword {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Break => "break".fmt(f),
|
||||
Self::Cl => "cl".fmt(f),
|
||||
Self::Const => "const".fmt(f),
|
||||
Self::Continue => "continue".fmt(f),
|
||||
Self::Else => "else".fmt(f),
|
||||
Self::Enum => "enum".fmt(f),
|
||||
Self::False => "false".fmt(f),
|
||||
Self::For => "for".fmt(f),
|
||||
Self::Fn => "fn".fmt(f),
|
||||
Self::If => "if".fmt(f),
|
||||
Self::Impl => "impl".fmt(f),
|
||||
Self::In => "in".fmt(f),
|
||||
Self::Let => "let".fmt(f),
|
||||
Self::Mod => "mod".fmt(f),
|
||||
Self::Mut => "mut".fmt(f),
|
||||
Self::Pub => "pub".fmt(f),
|
||||
Self::Return => "return".fmt(f),
|
||||
Self::SelfKw => "self".fmt(f),
|
||||
Self::SelfTy => "Self".fmt(f),
|
||||
Self::Static => "static".fmt(f),
|
||||
Self::Struct => "struct".fmt(f),
|
||||
Self::Super => "super".fmt(f),
|
||||
Self::True => "true".fmt(f),
|
||||
Self::Type => "type".fmt(f),
|
||||
Self::While => "while".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for Keyword {
|
||||
/// [FromStr] can only fail when an identifier isn't a keyword
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"break" => Self::Break,
|
||||
"cl" => Self::Cl,
|
||||
"const" => Self::Const,
|
||||
"continue" => Self::Continue,
|
||||
"else" => Self::Else,
|
||||
"enum" => Self::Enum,
|
||||
"false" => Self::False,
|
||||
"for" => Self::For,
|
||||
"fn" => Self::Fn,
|
||||
"if" => Self::If,
|
||||
"impl" => Self::Impl,
|
||||
"in" => Self::In,
|
||||
"let" => Self::Let,
|
||||
"mod" => Self::Mod,
|
||||
"mut" => Self::Mut,
|
||||
"pub" => Self::Pub,
|
||||
"return" => Self::Return,
|
||||
"self" => Self::SelfKw,
|
||||
"Self" => Self::SelfTy,
|
||||
"static" => Self::Static,
|
||||
"struct" => Self::Struct,
|
||||
"super" => Self::Super,
|
||||
"true" => Self::True,
|
||||
"type" => Self::Type,
|
||||
"while" => Self::While,
|
||||
_ => Err(())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
// Calculate Fibonacci numbers
|
||||
|
||||
fn main() -> i128 {
|
||||
let num = 10;
|
||||
print("fib(", num, "): ", fib(num));
|
||||
fn main() {
|
||||
for num in 0..=30 {
|
||||
print("fib(", num, ") = ", fib(num))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the classic recursive definition of fib()
|
||||
fn fib(a: i128) -> i128 {
|
||||
fn fib(a: i64) -> i64 {
|
||||
if a > 1 {
|
||||
fib(a - 1) + fib(a - 2)
|
||||
} 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