Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cb85c7f42 | |||
| 3b14186b70 | |||
| a6ad20911d | |||
| 01cf9d93e2 | |||
| edabbe1655 | |||
| af9c293907 | |||
| 0e3ba342c4 | |||
| d95d35268e | |||
| 0c2b0002ce | |||
| 3534be5fbc | |||
| 026681787a | |||
| 80e1219808 | |||
| 6ee9bbd72e | |||
| 6e94b702c9 | |||
| d21683ad61 | |||
| 518fbe74a1 | |||
| bc955c6409 | |||
| 678c0f952c | |||
| 86c4da0689 | |||
| 5db77db6b8 | |||
| 145a24c5ff | |||
| 485afb7843 | |||
| 01871bf455 | |||
| fd361f2bea | |||
| 0eef6b910c | |||
| c50940a44c | |||
| 3cda3d83d9 | |||
| e5a51ba6c2 | |||
| 883fd31d38 | |||
| d71276b477 | |||
| d8e32ee263 | |||
| e419c23769 | |||
| 1bd9c021dd | |||
| df68d6f2e6 | |||
| ae11d87d68 | |||
| 96be5aba6c | |||
| b9f4994930 | |||
| f4fe07a08b | |||
| 94be5d787f | |||
| 5deb585054 | |||
| 56e71d6782 | |||
| c62df3d8b3 | |||
| fad28beb05 | |||
| 0f8b0824ac | |||
| 99a00875a8 | |||
| 8675f91aca | |||
| de63a8c123 | |||
| 533436afc1 | |||
| 1eb0516baf | |||
| 97808fd855 | |||
| 388a69948e | |||
| 5e7ba6de24 | |||
| adb0fd229c | |||
| 0e545077c6 | |||
| b64cc232f9 | |||
| b0341f06fd | |||
| a3e383b53f | |||
| 1b217b2e75 | |||
| 5662bd8524 | |||
| 28f9048087 | |||
| b17164b68b | |||
| ecebefe218 | |||
| fc374e0108 | |||
| 4295982876 | |||
| 729155d3a4 | |||
| 8c0ae02a71 | |||
| 7f7836877e | |||
| b2733aa171 | |||
| a233bb18bc | |||
| e06a27a5b1 | |||
| 3f5c5480ae | |||
| 53cf71608a | |||
| 883c2677d9 | |||
| 7d98ef87d5 | |||
| a188c5b65e | |||
| 872818fe7c | |||
| 3aef055739 | |||
| 38a5d31b08 | |||
| e43847bbd4 | |||
| a8b8a91c79 | |||
| 695c812bf5 | |||
| 524c84be9e | |||
| 4096442f75 | |||
| 03a4e76292 | |||
| 46a1639990 | |||
| 5ea8039a8a | |||
| 479efbad73 | |||
| a462dd2be3 |
@@ -8,14 +8,13 @@ members = [
|
||||
"compiler/cl-ast",
|
||||
"compiler/cl-parser",
|
||||
"compiler/cl-lexer",
|
||||
"compiler/cl-arena",
|
||||
"repline",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
repository = "https://git.soft.fish/j/Conlang"
|
||||
version = "0.0.6"
|
||||
version = "0.0.9"
|
||||
authors = ["John Breaux <j@soft.fish>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "cl-arena"
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -1,42 +0,0 @@
|
||||
use super::DroplessArena;
|
||||
extern crate std;
|
||||
use core::alloc::Layout;
|
||||
use std::{prelude::rust_2021::*, vec};
|
||||
|
||||
#[test]
|
||||
fn alloc_raw() {
|
||||
let arena = DroplessArena::new();
|
||||
let bytes = arena.alloc_raw(Layout::for_value(&0u128));
|
||||
let byte2 = arena.alloc_raw(Layout::for_value(&0u128));
|
||||
|
||||
assert_ne!(bytes, byte2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc() {
|
||||
let arena = DroplessArena::new();
|
||||
let mut allocations = vec![];
|
||||
for i in 0..0x400 {
|
||||
allocations.push(arena.alloc(i));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_strings() {
|
||||
const KW: &[&str] = &["pub", "mut", "fn", "mod", "conlang", "sidon", "🦈"];
|
||||
let arena = DroplessArena::new();
|
||||
let mut allocations = vec![];
|
||||
for _ in 0..100 {
|
||||
for kw in KW {
|
||||
allocations.push(arena.alloc_str(kw));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn alloc_zsts() {
|
||||
struct Zst;
|
||||
let arena = DroplessArena::new();
|
||||
arena.alloc(Zst);
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
//! Typed and dropless arena allocation, paraphrased from [the Rust Compiler's `rustc_arena`](https://github.com/rust-lang/rust/blob/master/compiler/rustc_arena/src/lib.rs). See [LICENSE][1].
|
||||
//!
|
||||
//! An Arena Allocator is a type of allocator which provides stable locations for allocations within
|
||||
//! itself for the entire duration of its lifetime.
|
||||
//!
|
||||
//! [1]: https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT
|
||||
|
||||
#![feature(dropck_eyepatch, new_uninit, strict_provenance)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) mod constants {
|
||||
//! Size constants for arena chunk growth
|
||||
pub(crate) const MIN_CHUNK: usize = 4096;
|
||||
pub(crate) const MAX_CHUNK: usize = 2 * 1024 * 1024;
|
||||
}
|
||||
|
||||
mod chunk {
|
||||
//! An [ArenaChunk] contains a block of raw memory for use in arena allocators.
|
||||
use alloc::boxed::Box;
|
||||
use core::{
|
||||
mem::{self, MaybeUninit},
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
pub struct ArenaChunk<T> {
|
||||
pub(crate) mem: NonNull<[MaybeUninit<T>]>,
|
||||
pub(crate) filled: usize,
|
||||
}
|
||||
|
||||
impl<T: Sized> ArenaChunk<T> {
|
||||
pub fn new(cap: usize) -> Self {
|
||||
let slice = Box::new_uninit_slice(cap);
|
||||
Self { mem: NonNull::from(Box::leak(slice)), filled: 0 }
|
||||
}
|
||||
|
||||
/// Drops all elements inside self, and resets the filled count to 0
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `self.filled` elements of self are currently initialized
|
||||
pub unsafe fn drop_elements(&mut self) {
|
||||
if mem::needs_drop::<T>() {
|
||||
// Safety: the caller has ensured that `filled` elements are initialized
|
||||
unsafe {
|
||||
let slice = self.mem.as_mut();
|
||||
for t in slice[..self.filled].iter_mut() {
|
||||
t.assume_init_drop();
|
||||
}
|
||||
}
|
||||
self.filled = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a pointer to the start of the arena
|
||||
pub fn start(&mut self) -> *mut T {
|
||||
self.mem.as_ptr() as _
|
||||
}
|
||||
|
||||
/// Gets a pointer to the end of the arena
|
||||
pub fn end(&mut self) -> *mut T {
|
||||
if mem::size_of::<T>() == 0 {
|
||||
ptr::without_provenance_mut(usize::MAX) // pointers to ZSTs must be unique
|
||||
} else {
|
||||
unsafe { self.start().add(self.mem.len()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ArenaChunk<T> {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { Box::from_raw(self.mem.as_ptr()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod typed_arena {
|
||||
//! A [TypedArena] can hold many instances of a single type, and will properly [Drop] them.
|
||||
#![allow(clippy::mut_from_ref)]
|
||||
|
||||
use crate::{chunk::ArenaChunk, constants::*};
|
||||
use alloc::vec::Vec;
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
marker::PhantomData,
|
||||
mem, ptr, slice,
|
||||
};
|
||||
|
||||
/// A [TypedArena] can hold many instances of a single type, and will properly [Drop] them when
|
||||
/// it falls out of scope.
|
||||
pub struct TypedArena<'arena, T> {
|
||||
_lives: PhantomData<&'arena T>,
|
||||
_drops: PhantomData<T>,
|
||||
chunks: RefCell<Vec<ArenaChunk<T>>>,
|
||||
head: Cell<*mut T>,
|
||||
tail: Cell<*mut T>,
|
||||
}
|
||||
|
||||
impl<'arena, T> Default for TypedArena<'arena, T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena, T> TypedArena<'arena, T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
_lives: PhantomData,
|
||||
_drops: PhantomData,
|
||||
chunks: RefCell::new(Vec::new()),
|
||||
head: Cell::new(ptr::null_mut()),
|
||||
tail: Cell::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc(&'arena self, value: T) -> &'arena mut T {
|
||||
if self.head == self.tail {
|
||||
self.grow(1);
|
||||
}
|
||||
|
||||
let out = if mem::size_of::<T>() == 0 {
|
||||
self.head
|
||||
.set(ptr::without_provenance_mut(self.head.get().addr() + 1));
|
||||
ptr::NonNull::<T>::dangling().as_ptr()
|
||||
} else {
|
||||
let out = self.head.get();
|
||||
self.head.set(unsafe { out.add(1) });
|
||||
out
|
||||
};
|
||||
|
||||
unsafe {
|
||||
ptr::write(out, value);
|
||||
&mut *out
|
||||
}
|
||||
}
|
||||
|
||||
fn can_allocate(&self, len: usize) -> bool {
|
||||
len <= unsafe { self.tail.get().offset_from(self.head.get()) as usize }
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Panics if size_of::<T> == 0 || len == 0
|
||||
#[inline]
|
||||
fn alloc_raw_slice(&self, len: usize) -> *mut T {
|
||||
assert!(mem::size_of::<T>() != 0);
|
||||
assert!(len != 0);
|
||||
|
||||
if !self.can_allocate(len) {
|
||||
self.grow(len)
|
||||
}
|
||||
|
||||
let out = self.head.get();
|
||||
|
||||
unsafe { self.head.set(out.add(len)) };
|
||||
out
|
||||
}
|
||||
|
||||
pub fn alloc_from_iter<I>(&'arena self, iter: I) -> &'arena mut [T]
|
||||
where I: IntoIterator<Item = T> {
|
||||
// Collect them all into a buffer so they're allocated contiguously
|
||||
let mut buf = iter.into_iter().collect::<Vec<_>>();
|
||||
if buf.is_empty() {
|
||||
return &mut [];
|
||||
}
|
||||
|
||||
let len = buf.len();
|
||||
// If T is a ZST, calling alloc_raw_slice will panic
|
||||
let slice = if mem::size_of::<T>() == 0 {
|
||||
self.head
|
||||
.set(ptr::without_provenance_mut(self.head.get().addr() + len));
|
||||
ptr::NonNull::dangling().as_ptr()
|
||||
} else {
|
||||
self.alloc_raw_slice(len)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
buf.as_ptr().copy_to_nonoverlapping(slice, len);
|
||||
buf.set_len(0);
|
||||
slice::from_raw_parts_mut(slice, len)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn grow(&self, len: usize) {
|
||||
let size = mem::size_of::<T>().max(1);
|
||||
|
||||
let mut chunks = self.chunks.borrow_mut();
|
||||
|
||||
let capacity = if let Some(last) = chunks.last_mut() {
|
||||
last.filled = self.get_filled_of_chunk(last);
|
||||
last.mem.len().min(MAX_CHUNK / size) * 2
|
||||
} else {
|
||||
MIN_CHUNK / size
|
||||
}
|
||||
.max(len);
|
||||
|
||||
let mut chunk = ArenaChunk::<T>::new(capacity);
|
||||
|
||||
self.head.set(chunk.start());
|
||||
self.tail.set(chunk.end());
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
fn get_filled_of_chunk(&self, chunk: &mut ArenaChunk<T>) -> usize {
|
||||
let Self { head: tail, .. } = self;
|
||||
let head = chunk.start();
|
||||
if mem::size_of::<T>() == 0 {
|
||||
tail.get().addr() - head.addr()
|
||||
} else {
|
||||
unsafe { tail.get().offset_from(head) as usize }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'arena, T: Send> Send for TypedArena<'arena, T> {}
|
||||
|
||||
unsafe impl<'arena, #[may_dangle] T> Drop for TypedArena<'arena, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut chunks = self.chunks.borrow_mut();
|
||||
|
||||
if let Some(last) = chunks.last_mut() {
|
||||
last.filled = self.get_filled_of_chunk(last);
|
||||
self.tail.set(self.head.get());
|
||||
}
|
||||
|
||||
for chunk in chunks.iter_mut() {
|
||||
unsafe { chunk.drop_elements() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
}
|
||||
|
||||
pub mod dropless_arena {
|
||||
//! A [DroplessArena] can hold *any* combination of types as long as they don't implement
|
||||
//! [Drop].
|
||||
use crate::{chunk::ArenaChunk, constants::*};
|
||||
use alloc::vec::Vec;
|
||||
use core::{
|
||||
alloc::Layout,
|
||||
cell::{Cell, RefCell},
|
||||
marker::PhantomData,
|
||||
mem, ptr, slice,
|
||||
};
|
||||
|
||||
pub struct DroplessArena<'arena> {
|
||||
_lives: PhantomData<&'arena u8>,
|
||||
chunks: RefCell<Vec<ArenaChunk<u8>>>,
|
||||
head: Cell<*mut u8>,
|
||||
tail: Cell<*mut u8>,
|
||||
}
|
||||
|
||||
impl Default for DroplessArena<'_> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'arena> DroplessArena<'arena> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
_lives: PhantomData,
|
||||
chunks: RefCell::new(Vec::new()),
|
||||
head: Cell::new(ptr::null_mut()),
|
||||
tail: Cell::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a `T` in the [DroplessArena], and returns a mutable reference to it.
|
||||
///
|
||||
/// # Panics
|
||||
/// - Panics if T implements [Drop]
|
||||
/// - Panics if T is zero-sized
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn alloc<T>(&'arena self, value: T) -> &'arena mut T {
|
||||
assert!(!mem::needs_drop::<T>());
|
||||
assert!(mem::size_of::<T>() != 0);
|
||||
|
||||
let out = self.alloc_raw(Layout::new::<T>()) as *mut T;
|
||||
|
||||
unsafe {
|
||||
ptr::write(out, value);
|
||||
&mut *out
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a slice of `T`s`, copied from the given slice, returning a mutable reference
|
||||
/// to it.
|
||||
///
|
||||
/// # Panics
|
||||
/// - Panics if T implements [Drop]
|
||||
/// - Panics if T is zero-sized
|
||||
/// - Panics if the slice is empty
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn alloc_slice<T: Copy>(&'arena self, slice: &[T]) -> &'arena mut [T] {
|
||||
assert!(!mem::needs_drop::<T>());
|
||||
assert!(mem::size_of::<T>() != 0);
|
||||
assert!(!slice.is_empty());
|
||||
|
||||
let mem = self.alloc_raw(Layout::for_value::<[T]>(slice)) as *mut T;
|
||||
|
||||
unsafe {
|
||||
mem.copy_from_nonoverlapping(slice.as_ptr(), slice.len());
|
||||
slice::from_raw_parts_mut(mem, slice.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a copy of the given [`&str`](str), returning a reference to the allocation.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the string is empty.
|
||||
pub fn alloc_str(&'arena self, string: &str) -> &'arena str {
|
||||
let slice = self.alloc_slice(string.as_bytes());
|
||||
|
||||
// Safety: This is a clone of the input string, which was valid
|
||||
unsafe { core::str::from_utf8_unchecked(slice) }
|
||||
}
|
||||
|
||||
/// Allocates some [bytes](u8) based on the given [Layout].
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the provided [Layout] has size 0
|
||||
pub fn alloc_raw(&'arena self, layout: Layout) -> *mut u8 {
|
||||
/// Rounds the given size (or pointer value) *up* to the given alignment
|
||||
fn align_up(size: usize, align: usize) -> usize {
|
||||
(size + align - 1) & !(align - 1)
|
||||
}
|
||||
/// Rounds the given size (or pointer value) *down* to the given alignment
|
||||
fn align_down(size: usize, align: usize) -> usize {
|
||||
size & !(align - 1)
|
||||
}
|
||||
|
||||
assert!(layout.size() != 0);
|
||||
loop {
|
||||
let Self { head, tail, .. } = self;
|
||||
let start = head.get().addr();
|
||||
let end = tail.get().addr();
|
||||
|
||||
let align = 8.max(layout.align());
|
||||
|
||||
let bytes = align_up(layout.size(), align);
|
||||
|
||||
if let Some(end) = end.checked_sub(bytes) {
|
||||
let end = align_down(end, layout.align());
|
||||
|
||||
if start <= end {
|
||||
tail.set(tail.get().with_addr(end));
|
||||
return tail.get();
|
||||
}
|
||||
}
|
||||
|
||||
self.grow(layout.size());
|
||||
}
|
||||
}
|
||||
|
||||
/// Grows the allocator, doubling the chunk size until it reaches [MAX_CHUNK].
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn grow(&self, len: usize) {
|
||||
let mut chunks = self.chunks.borrow_mut();
|
||||
|
||||
let capacity = if let Some(last) = chunks.last_mut() {
|
||||
last.mem.len().min(MAX_CHUNK / 2) * 2
|
||||
} else {
|
||||
MIN_CHUNK
|
||||
}
|
||||
.max(len);
|
||||
|
||||
let mut chunk = ArenaChunk::<u8>::new(capacity);
|
||||
|
||||
self.head.set(chunk.start());
|
||||
self.tail.set(chunk.end());
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
/// Checks whether the given slice is allocated in this arena
|
||||
pub fn contains_slice<T>(&self, slice: &[T]) -> bool {
|
||||
let ptr = slice.as_ptr().cast::<u8>().cast_mut();
|
||||
for chunk in self.chunks.borrow_mut().iter_mut() {
|
||||
if chunk.start() <= ptr && ptr <= chunk.end() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'arena> Send for DroplessArena<'arena> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use super::TypedArena;
|
||||
extern crate std;
|
||||
use std::{prelude::rust_2021::*, print, vec};
|
||||
#[test]
|
||||
fn pushing_to_arena() {
|
||||
let arena = TypedArena::new();
|
||||
let foo = arena.alloc("foo");
|
||||
let bar = arena.alloc("bar");
|
||||
let baz = arena.alloc("baz");
|
||||
|
||||
assert_eq!("foo", *foo);
|
||||
assert_eq!("bar", *bar);
|
||||
assert_eq!("baz", *baz);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushing_vecs_to_arena() {
|
||||
let arena = TypedArena::new();
|
||||
|
||||
let foo = arena.alloc(vec!["foo"]);
|
||||
let bar = arena.alloc(vec!["bar"]);
|
||||
let baz = arena.alloc(vec!["baz"]);
|
||||
|
||||
assert_eq!("foo", foo[0]);
|
||||
assert_eq!("bar", bar[0]);
|
||||
assert_eq!("baz", baz[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushing_zsts() {
|
||||
struct ZeroSized;
|
||||
impl Drop for ZeroSized {
|
||||
fn drop(&mut self) {
|
||||
print!("")
|
||||
}
|
||||
}
|
||||
|
||||
let arena = TypedArena::new();
|
||||
|
||||
for _ in 0..0x100 {
|
||||
arena.alloc(ZeroSized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pushing_nodrop_zsts() {
|
||||
struct ZeroSized;
|
||||
let arena = TypedArena::new();
|
||||
|
||||
for _ in 0..0x1000 {
|
||||
arena.alloc(ZeroSized);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn resize() {
|
||||
let arena = TypedArena::new();
|
||||
|
||||
for _ in 0..0x780 {
|
||||
arena.alloc(0u128);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
//! - [Assign], [Modify], [Binary], and [Unary] expressions
|
||||
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
|
||||
//! - [Ty] and [TyKind]: Type qualifiers
|
||||
//! - [Pattern]: Pattern matching operators
|
||||
//! - [Path]: Path expressions
|
||||
use cl_structures::{intern::interned::Interned, span::*};
|
||||
|
||||
@@ -36,6 +37,7 @@ pub enum Literal {
|
||||
Bool(bool),
|
||||
Char(char),
|
||||
Int(u128),
|
||||
Float(u64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
@@ -79,7 +81,6 @@ pub struct Item {
|
||||
/// What kind of [Item] is this?
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ItemKind {
|
||||
// TODO: Import declaration ("use") item
|
||||
// TODO: Trait declaration ("trait") item?
|
||||
/// A [module](Module)
|
||||
Module(Module),
|
||||
@@ -258,7 +259,6 @@ pub enum TyKind {
|
||||
Tuple(TyTuple),
|
||||
Ref(TyRef),
|
||||
Fn(TyFn),
|
||||
// TODO: slice, array types
|
||||
}
|
||||
|
||||
/// An array of [`T`](Ty)
|
||||
@@ -323,7 +323,6 @@ pub struct Stmt {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum StmtKind {
|
||||
Empty,
|
||||
Local(Let),
|
||||
Item(Box<Item>),
|
||||
Expr(Box<Expr>),
|
||||
}
|
||||
@@ -335,15 +334,6 @@ pub enum Semi {
|
||||
Unterminated,
|
||||
}
|
||||
|
||||
/// A local variable declaration [Stmt]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Let {
|
||||
pub mutable: Mutability,
|
||||
pub name: Sym,
|
||||
pub ty: Option<Box<Ty>>,
|
||||
pub init: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// An expression, the beating heart of the language
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Expr {
|
||||
@@ -357,6 +347,12 @@ pub enum ExprKind {
|
||||
/// An empty expression: `(` `)`
|
||||
#[default]
|
||||
Empty,
|
||||
/// A backtick-quoted expression
|
||||
Quote(Quote),
|
||||
/// A local bind instruction, `let` [`Sym`] `=` [`Expr`]
|
||||
Let(Let),
|
||||
/// A [Match] expression: `match` [Expr] `{` ([MatchArm] `,`)* [MatchArm]? `}`
|
||||
Match(Match),
|
||||
/// An [Assign]ment expression: [`Expr`] (`=` [`Expr`])\+
|
||||
Assign(Assign),
|
||||
/// A [Modify]-assignment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
|
||||
@@ -365,6 +361,8 @@ pub enum ExprKind {
|
||||
Binary(Binary),
|
||||
/// A [Unary] expression: [`UnaryKind`]\* [`Expr`]
|
||||
Unary(Unary),
|
||||
/// A [Cast] expression: [`Expr`] `as` [`Ty`]
|
||||
Cast(Cast),
|
||||
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
|
||||
Member(Member),
|
||||
/// An Array [Index] expression: a[10, 20, 30]
|
||||
@@ -388,8 +386,6 @@ pub enum ExprKind {
|
||||
Group(Group),
|
||||
/// A [Tuple] expression: `(` [`Expr`] (`,` [`Expr`])+ `)`
|
||||
Tuple(Tuple),
|
||||
/// A [Loop] expression: `loop` [`Block`]
|
||||
Loop(Loop),
|
||||
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
||||
While(While),
|
||||
/// An [If] expression: `if` [`Expr`] [`Block`] [`Else`]?
|
||||
@@ -401,9 +397,46 @@ pub enum ExprKind {
|
||||
/// A [Return] expression `return` [`Expr`]?
|
||||
Return(Return),
|
||||
/// A continue expression: `continue`
|
||||
Continue(Continue),
|
||||
Continue,
|
||||
}
|
||||
|
||||
/// A backtick-quoted subexpression-literal
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Quote {
|
||||
pub quote: Box<ExprKind>,
|
||||
}
|
||||
|
||||
/// A local variable declaration [Stmt]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Let {
|
||||
pub mutable: Mutability,
|
||||
pub name: Pattern,
|
||||
pub ty: Option<Box<Ty>>,
|
||||
pub init: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// A [Pattern] meta-expression (any [`ExprKind`] that fits pattern rules)
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Pattern {
|
||||
Path(Path),
|
||||
Literal(Literal),
|
||||
Ref(Mutability, Box<Pattern>),
|
||||
Tuple(Vec<Pattern>),
|
||||
Array(Vec<Pattern>),
|
||||
Struct(Path, Vec<(Path, Option<Pattern>)>),
|
||||
}
|
||||
|
||||
/// A `match` expression: `match` `{` ([MatchArm] `,`)* [MatchArm]? `}`
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Match {
|
||||
pub scrutinee: Box<Expr>,
|
||||
pub arms: Vec<MatchArm>,
|
||||
}
|
||||
|
||||
/// A single arm of a [Match] expression: [`Pattern`] `=>` [`Expr`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct MatchArm(pub Pattern, pub Expr);
|
||||
|
||||
/// An [Assign]ment expression: [`Expr`] ([`ModifyKind`] [`Expr`])\+
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Assign {
|
||||
@@ -478,12 +511,21 @@ pub enum UnaryKind {
|
||||
Deref,
|
||||
Neg,
|
||||
Not,
|
||||
/// A Loop expression: `loop` [`Block`]
|
||||
Loop,
|
||||
/// Unused
|
||||
At,
|
||||
/// Unused
|
||||
Tilde,
|
||||
}
|
||||
|
||||
/// A cast expression: [`Expr`] `as` [`Ty`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Cast {
|
||||
pub head: Box<ExprKind>,
|
||||
pub ty: Ty,
|
||||
}
|
||||
|
||||
/// A [Member] access expression: [`Expr`] [`MemberKind`]\*
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Member {
|
||||
@@ -537,7 +579,6 @@ pub struct ArrayRep {
|
||||
/// An address-of expression: `&` `mut`? [`Expr`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct AddrOf {
|
||||
pub count: usize,
|
||||
pub mutable: Mutability,
|
||||
pub expr: Box<ExprKind>,
|
||||
}
|
||||
@@ -560,12 +601,6 @@ pub struct Tuple {
|
||||
pub exprs: Vec<Expr>,
|
||||
}
|
||||
|
||||
/// A [Loop] expression: `loop` [`Block`]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Loop {
|
||||
pub body: Box<Expr>,
|
||||
}
|
||||
|
||||
/// A [While] expression: `while` [`Expr`] [`Block`] [`Else`]?
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct While {
|
||||
@@ -608,7 +643,3 @@ pub struct Break {
|
||||
pub struct Return {
|
||||
pub body: Option<Box<Expr>>,
|
||||
}
|
||||
|
||||
/// A continue expression: `continue`
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Continue;
|
||||
|
||||
@@ -9,8 +9,24 @@ mod display {
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Display, Write},
|
||||
io::IsTerminal,
|
||||
};
|
||||
|
||||
fn keyword(d: impl Display, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if std::io::stdout().is_terminal() {
|
||||
write!(f, "\x1b[31m{d}\x1b[0m")
|
||||
} else {
|
||||
d.fmt(f)
|
||||
}
|
||||
}
|
||||
fn ident(d: impl Display, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if std::io::stdout().is_terminal() {
|
||||
write!(f, "\x1b[95m{d}\x1b[0m")
|
||||
} else {
|
||||
d.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn separate<I: Display, W: Write>(
|
||||
iterable: impl IntoIterator<Item = I>,
|
||||
sep: &'static str,
|
||||
@@ -30,7 +46,7 @@ mod display {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Mutability::Not => Ok(()),
|
||||
Mutability::Mut => "mut ".fmt(f),
|
||||
Mutability::Mut => keyword("ante ", f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,18 +55,28 @@ mod display {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Visibility::Private => Ok(()),
|
||||
Visibility::Public => "pub ".fmt(f),
|
||||
Visibility::Public => keyword("lukin ", f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Literal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
fn fmt(this: &Literal, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match this {
|
||||
Literal::Bool(v) => v.fmt(f),
|
||||
Literal::Char(v) => write!(f, "'{v}'"),
|
||||
Literal::Char(v) => write!(f, "'{}'", v.escape_debug()),
|
||||
Literal::Int(v) => v.fmt(f),
|
||||
Literal::String(v) => write!(f, "\"{v}\""),
|
||||
Literal::Float(v) => write!(f, "{:?}", f64::from_bits(*v)),
|
||||
Literal::String(v) => write!(f, "\"{}\"", v.escape_debug()),
|
||||
}
|
||||
}
|
||||
if std::io::stdout().is_terminal() {
|
||||
write!(f, "\x1b[94m")?;
|
||||
fmt(self, f)?;
|
||||
write!(f, "\x1b[0m")
|
||||
} else {
|
||||
fmt(self, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +102,8 @@ mod display {
|
||||
impl Display for Meta {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, kind } = self;
|
||||
write!(f, "{name}{kind}")
|
||||
ident(name, f)?;
|
||||
write!(f, "{kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +145,10 @@ mod display {
|
||||
impl Display for Alias {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { to, from } = self;
|
||||
keyword("ijo", f)?;
|
||||
match from {
|
||||
Some(from) => write!(f, "type {to} = {from};"),
|
||||
None => write!(f, "type {to};"),
|
||||
Some(from) => write!(f, " {to} = {from};"),
|
||||
None => write!(f, " {to};"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,21 +156,28 @@ mod display {
|
||||
impl Display for Const {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, ty, init } = self;
|
||||
write!(f, "const {name}: {ty} = {init}")
|
||||
keyword("kiwen ", f)?;
|
||||
ident(name, f)?;
|
||||
write!(f, ": {ty} = {init}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Static {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
write!(f, "static {mutable}{name}: {ty} = {init}")
|
||||
keyword("mute", f)?;
|
||||
write!(f, " {mutable}")?;
|
||||
ident(name, f)?;
|
||||
write!(f, ": {ty} = {init}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Module {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, kind } = self;
|
||||
write!(f, "mod {name}{kind}")
|
||||
keyword("selo ", f)?;
|
||||
ident(name, f)?;
|
||||
write!(f, "{kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +206,10 @@ mod display {
|
||||
};
|
||||
|
||||
debug_assert_eq!(bind.len(), types.len());
|
||||
write!(f, "fn {name} ")?;
|
||||
keyword("nasin", f)?;
|
||||
" ".fmt(f)?;
|
||||
ident(name, f)?;
|
||||
" ".fmt(f)?;
|
||||
{
|
||||
let mut f = f.delimit(INLINE_PARENS);
|
||||
for (idx, (arg, ty)) in bind.iter().zip(types.iter()).enumerate() {
|
||||
@@ -194,14 +232,17 @@ mod display {
|
||||
impl Display for Param {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { mutability, name } = self;
|
||||
write!(f, "{mutability}{name}")
|
||||
write!(f, "{mutability}")?;
|
||||
ident(name, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Struct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, kind } = self;
|
||||
write!(f, "struct {name}{kind}")
|
||||
keyword("lipu ", f)?;
|
||||
ident(name, f)?;
|
||||
write!(f, "{kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,14 +259,18 @@ mod display {
|
||||
impl Display for StructMember {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { vis, name, ty } = self;
|
||||
write!(f, "{vis}{name}: {ty}")
|
||||
write!(f, "{vis}")?;
|
||||
ident(name, f)?;
|
||||
write!(f, ": {ty}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Enum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, kind } = self;
|
||||
write!(f, "enum {name}{kind}")
|
||||
keyword("kulupu ", f)?;
|
||||
ident(name, f)?;
|
||||
write!(f, "{kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +286,8 @@ mod display {
|
||||
impl Display for Variant {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, kind } = self;
|
||||
write!(f, "{name}{kind}")
|
||||
ident(name, f)?;
|
||||
write!(f, "{kind}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +305,8 @@ mod display {
|
||||
impl Display for Impl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { target, body } = self;
|
||||
write!(f, "impl {target} ")?;
|
||||
keyword("insa", f)?;
|
||||
write!(f, " {target} ")?;
|
||||
write!(f.delimit(BRACES), "{body}")
|
||||
}
|
||||
}
|
||||
@@ -269,7 +316,9 @@ mod display {
|
||||
match self {
|
||||
ImplKind::Type(t) => t.fmt(f),
|
||||
ImplKind::Trait { impl_trait, for_type } => {
|
||||
write!(f, "{impl_trait} for {for_type}")
|
||||
write!(f, "{impl_trait} ")?;
|
||||
keyword("ale", f)?;
|
||||
write!(f, " {for_type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,7 +327,8 @@ mod display {
|
||||
impl Display for Use {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { absolute, tree } = self;
|
||||
f.write_str(if *absolute { "use ::" } else { "use " })?;
|
||||
keyword("jo", f)?;
|
||||
f.write_str(if *absolute { " ::" } else { " " })?;
|
||||
write!(f, "{tree};")
|
||||
}
|
||||
}
|
||||
@@ -288,8 +338,12 @@ mod display {
|
||||
match self {
|
||||
UseTree::Tree(tree) => separate(tree, ", ")(f.delimit(INLINE_BRACES)),
|
||||
UseTree::Path(path, rest) => write!(f, "{path}::{rest}"),
|
||||
UseTree::Alias(path, name) => write!(f, "{path} as {name}"),
|
||||
UseTree::Name(name) => write!(f, "{name}"),
|
||||
UseTree::Alias(path, name) => {
|
||||
write!(f, "{path} ")?;
|
||||
keyword("sama ", f)?;
|
||||
ident(name, f)
|
||||
}
|
||||
UseTree::Name(name) => ident(name, f),
|
||||
UseTree::Glob => write!(f, "*"),
|
||||
}
|
||||
}
|
||||
@@ -349,7 +403,8 @@ mod display {
|
||||
impl Display for TyFn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { args, rety } = self;
|
||||
write!(f, "fn {args}")?;
|
||||
keyword("nasin", f)?;
|
||||
write!(f, " {args}")?;
|
||||
match rety {
|
||||
Some(v) => write!(f, " -> {v}"),
|
||||
None => Ok(()),
|
||||
@@ -370,10 +425,10 @@ mod display {
|
||||
impl Display for PathPart {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PathPart::SuperKw => "super".fmt(f),
|
||||
PathPart::SelfKw => "self".fmt(f),
|
||||
PathPart::SelfTy => "Self".fmt(f),
|
||||
PathPart::Ident(id) => id.fmt(f),
|
||||
PathPart::SuperKw => keyword("mama", f),
|
||||
PathPart::SelfKw => keyword("mi", f),
|
||||
PathPart::SelfTy => keyword("Mi", f),
|
||||
PathPart::Ident(id) => ident(id, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,7 +444,6 @@ mod display {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
StmtKind::Empty => Ok(()),
|
||||
StmtKind::Local(v) => v.fmt(f),
|
||||
StmtKind::Item(v) => v.fmt(f),
|
||||
StmtKind::Expr(v) => v.fmt(f),
|
||||
}
|
||||
@@ -405,20 +459,6 @@ mod display {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Let {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
write!(f, "let {mutable}{name}")?;
|
||||
if let Some(value) = ty {
|
||||
write!(f, ": {value}")?;
|
||||
}
|
||||
if let Some(value) = init {
|
||||
write!(f, " = {value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.kind.fmt(f)
|
||||
@@ -429,10 +469,14 @@ mod display {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExprKind::Empty => "()".fmt(f),
|
||||
ExprKind::Quote(v) => v.fmt(f),
|
||||
ExprKind::Let(v) => v.fmt(f),
|
||||
ExprKind::Match(v) => v.fmt(f),
|
||||
ExprKind::Assign(v) => v.fmt(f),
|
||||
ExprKind::Modify(v) => v.fmt(f),
|
||||
ExprKind::Binary(v) => v.fmt(f),
|
||||
ExprKind::Unary(v) => v.fmt(f),
|
||||
ExprKind::Cast(v) => v.fmt(f),
|
||||
ExprKind::Member(v) => v.fmt(f),
|
||||
ExprKind::Index(v) => v.fmt(f),
|
||||
ExprKind::Structor(v) => v.fmt(f),
|
||||
@@ -444,17 +488,82 @@ mod display {
|
||||
ExprKind::Block(v) => v.fmt(f),
|
||||
ExprKind::Group(v) => v.fmt(f),
|
||||
ExprKind::Tuple(v) => v.fmt(f),
|
||||
ExprKind::Loop(v) => v.fmt(f),
|
||||
ExprKind::While(v) => v.fmt(f),
|
||||
ExprKind::If(v) => v.fmt(f),
|
||||
ExprKind::For(v) => v.fmt(f),
|
||||
ExprKind::Break(v) => v.fmt(f),
|
||||
ExprKind::Return(v) => v.fmt(f),
|
||||
ExprKind::Continue(_) => "continue".fmt(f),
|
||||
ExprKind::Continue => keyword("tama", f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Quote {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { quote } = self;
|
||||
write!(f, "`{quote}`")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Let {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
keyword("poki", f)?;
|
||||
write!(f, " {mutable}")?;
|
||||
ident(name, f)?;
|
||||
|
||||
if let Some(value) = ty {
|
||||
write!(f, ": {value}")?;
|
||||
}
|
||||
if let Some(value) = init {
|
||||
write!(f, " = {value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Pattern {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Pattern::Path(path) => path.fmt(f),
|
||||
Pattern::Literal(literal) => literal.fmt(f),
|
||||
Pattern::Ref(mutability, pattern) => write!(f, "&{mutability}{pattern}"),
|
||||
Pattern::Tuple(patterns) => separate(patterns, ", ")(f.delimit(INLINE_PARENS)),
|
||||
Pattern::Array(patterns) => separate(patterns, ", ")(f.delimit(INLINE_SQUARE)),
|
||||
Pattern::Struct(path, items) => {
|
||||
write!(f, "{path}: ")?;
|
||||
let f = &mut f.delimit(BRACES);
|
||||
for (idx, (name, item)) in items.iter().enumerate() {
|
||||
if idx != 0 {
|
||||
f.write_str(",\n")?;
|
||||
}
|
||||
write!(f, "{name}")?;
|
||||
if let Some(pattern) = item {
|
||||
write!(f, ": {pattern}")?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Match {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { scrutinee, arms } = self;
|
||||
keyword("seme", f)?;
|
||||
write!(f, " {scrutinee} ")?;
|
||||
separate(arms, ",\n")(f.delimit(BRACES))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MatchArm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self(pat, expr) = self;
|
||||
write!(f, "{pat} => {expr}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Assign {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { parts } = self;
|
||||
@@ -538,6 +647,7 @@ mod display {
|
||||
impl Display for UnaryKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UnaryKind::Loop => return keyword("awen ", f),
|
||||
UnaryKind::Deref => "*",
|
||||
UnaryKind::Neg => "-",
|
||||
UnaryKind::Not => "!",
|
||||
@@ -548,6 +658,15 @@ mod display {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Cast {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head, ty } = self;
|
||||
write!(f, "{head} ")?;
|
||||
keyword("sama", f)?;
|
||||
write!(f, " {ty}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Member {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { head, kind } = self;
|
||||
@@ -558,9 +677,12 @@ mod display {
|
||||
impl Display for MemberKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MemberKind::Call(name, args) => write!(f, "{name}{args}"),
|
||||
MemberKind::Struct(name) => write!(f, "{name}"),
|
||||
MemberKind::Tuple(name) => write!(f, "{name}"),
|
||||
MemberKind::Call(name, args) => {
|
||||
ident(name, f)?;
|
||||
separate(&args.exprs, ", ")(f.delimit(INLINE_PARENS))
|
||||
}
|
||||
MemberKind::Struct(name) => ident(name, f),
|
||||
MemberKind::Tuple(name) => ident(name, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,7 +706,7 @@ mod display {
|
||||
impl Display for Fielder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { name, init } = self;
|
||||
write!(f, "{name}")?;
|
||||
ident(name, f)?;
|
||||
if let Some(init) = init {
|
||||
write!(f, ": {init}")?;
|
||||
}
|
||||
@@ -607,17 +729,19 @@ mod display {
|
||||
|
||||
impl Display for AddrOf {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { count, mutable, expr } = self;
|
||||
for _ in 0..*count {
|
||||
f.write_char('&')?;
|
||||
}
|
||||
write!(f, "{mutable}{expr}")
|
||||
let Self { mutable, expr } = self;
|
||||
write!(f, "&{mutable}{expr}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Block {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
separate(&self.stmts, "\n")(f.delimit(BRACES))
|
||||
let Self { stmts } = self;
|
||||
|
||||
match stmts.as_slice() {
|
||||
[] => "{}".fmt(f),
|
||||
stmts => separate(stmts, "\n")(f.delimit(BRACES)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,42 +753,48 @@ mod display {
|
||||
|
||||
impl Display for Tuple {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
separate(&self.exprs, ", ")(f.delimit(INLINE_PARENS))
|
||||
}
|
||||
}
|
||||
let Self { exprs } = self;
|
||||
|
||||
impl Display for Loop {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { body } = self;
|
||||
write!(f, "loop {body}")
|
||||
match exprs.as_slice() {
|
||||
[] => write!(f, "()"),
|
||||
[expr] => write!(f, "({expr},)"),
|
||||
exprs => separate(exprs, ", ")(f.delimit(INLINE_PARENS)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for While {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { cond, pass, fail } = self;
|
||||
write!(f, "while {cond} {pass}{fail}")
|
||||
write!(f, "lawa {cond} {pass}{fail}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for If {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { cond, pass, fail } = self;
|
||||
write!(f, "if {cond} {pass}{fail}")
|
||||
keyword("tan", f)?;
|
||||
write!(f, " {cond} {pass}{fail}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for For {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { bind, cond, pass, fail } = self;
|
||||
write!(f, "for {bind} in {cond} {pass}{fail}")
|
||||
keyword("ale", f)?;
|
||||
write!(f, " {bind} ")?;
|
||||
keyword("lon", f)?;
|
||||
write!(f, " {cond} {pass}{fail}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Else {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.body {
|
||||
Some(body) => write!(f, " else {body}"),
|
||||
Some(body) => {
|
||||
keyword(" taso", f)?;
|
||||
write!(f, " {body}")
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -672,7 +802,7 @@ mod display {
|
||||
|
||||
impl Display for Break {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "break")?;
|
||||
keyword("pana", f)?;
|
||||
match &self.body {
|
||||
Some(body) => write!(f, " {body}"),
|
||||
_ => Ok(()),
|
||||
@@ -682,19 +812,13 @@ mod display {
|
||||
|
||||
impl Display for Return {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "return")?;
|
||||
keyword("pini", f)?;
|
||||
match &self.body {
|
||||
Some(body) => write!(f, " {body}"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Continue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
"continue".fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod convert {
|
||||
@@ -755,15 +879,18 @@ mod convert {
|
||||
TyFn => TyKind::Fn,
|
||||
}
|
||||
impl From for StmtKind {
|
||||
Let => StmtKind::Local,
|
||||
Item => StmtKind::Item,
|
||||
Expr => StmtKind::Expr,
|
||||
}
|
||||
impl From for ExprKind {
|
||||
Let => ExprKind::Let,
|
||||
Quote => ExprKind::Quote,
|
||||
Match => ExprKind::Match,
|
||||
Assign => ExprKind::Assign,
|
||||
Modify => ExprKind::Modify,
|
||||
Binary => ExprKind::Binary,
|
||||
Unary => ExprKind::Unary,
|
||||
Cast => ExprKind::Cast,
|
||||
Member => ExprKind::Member,
|
||||
Index => ExprKind::Index,
|
||||
Path => ExprKind::Path,
|
||||
@@ -774,13 +901,11 @@ mod convert {
|
||||
Block => ExprKind::Block,
|
||||
Group => ExprKind::Group,
|
||||
Tuple => ExprKind::Tuple,
|
||||
Loop => ExprKind::Loop,
|
||||
While => ExprKind::While,
|
||||
If => ExprKind::If,
|
||||
For => ExprKind::For,
|
||||
Break => ExprKind::Break,
|
||||
Return => ExprKind::Return,
|
||||
Continue => ExprKind::Continue,
|
||||
}
|
||||
impl From for Literal {
|
||||
bool => Literal::Bool,
|
||||
@@ -800,6 +925,50 @@ mod convert {
|
||||
Self { body: Some(value.into()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExprKind> for Pattern {
|
||||
type Error = ExprKind;
|
||||
|
||||
/// Performs the conversion. On failure, returns the *first* non-pattern subexpression.
|
||||
fn try_from(value: ExprKind) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
ExprKind::Literal(literal) => Pattern::Literal(literal),
|
||||
ExprKind::Path(path) => Pattern::Path(path),
|
||||
ExprKind::Empty => Pattern::Tuple(vec![]),
|
||||
ExprKind::Group(Group { expr }) => Pattern::Tuple(vec![Pattern::try_from(*expr)?]),
|
||||
ExprKind::Tuple(Tuple { exprs }) => Pattern::Tuple(
|
||||
exprs
|
||||
.into_iter()
|
||||
.map(|e| Pattern::try_from(e.kind))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
ExprKind::AddrOf(AddrOf { mutable, expr }) => {
|
||||
Pattern::Ref(mutable, Box::new(Pattern::try_from(*expr)?))
|
||||
}
|
||||
ExprKind::Array(Array { values }) => Pattern::Array(
|
||||
values
|
||||
.into_iter()
|
||||
.map(|e| Pattern::try_from(e.kind))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
// ExprKind::Index(index) => todo!(),
|
||||
// ExprKind::Member(member) => todo!(),
|
||||
ExprKind::Structor(Structor { to, init }) => {
|
||||
let fields = init
|
||||
.into_iter()
|
||||
.map(|Fielder { name, init }| {
|
||||
Ok((
|
||||
name.into(),
|
||||
init.map(|i| Pattern::try_from(i.kind)).transpose()?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, Self::Error>>()?;
|
||||
Pattern::Struct(to, fields)
|
||||
}
|
||||
err => Err(err)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod path {
|
||||
@@ -825,10 +994,26 @@ mod path {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether this path refers to the sinkhole identifier, `_`
|
||||
pub fn is_sinkhole(&self) -> bool {
|
||||
if let [PathPart::Ident(id)] = self.parts.as_slice() {
|
||||
if let "_" = Sym::to_ref(id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
impl PathPart {
|
||||
pub fn from_sym(ident: Sym) -> Self {
|
||||
Self::Ident(ident)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Sym> for Path {
|
||||
fn from(value: Sym) -> Self {
|
||||
Self { parts: vec![PathPart::Ident(value)], absolute: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ pub trait Fold {
|
||||
fn fold_int(&mut self, i: u128) -> u128 {
|
||||
i
|
||||
}
|
||||
fn fold_smuggled_float(&mut self, f: u64) -> u64 {
|
||||
f
|
||||
}
|
||||
fn fold_string(&mut self, s: String) -> String {
|
||||
s
|
||||
}
|
||||
@@ -226,15 +229,6 @@ pub trait Fold {
|
||||
fn fold_semi(&mut self, s: Semi) -> Semi {
|
||||
s
|
||||
}
|
||||
fn fold_let(&mut self, l: Let) -> Let {
|
||||
let Let { mutable, name, ty, init } = l;
|
||||
Let {
|
||||
mutable: self.fold_mutability(mutable),
|
||||
name: self.fold_sym(name),
|
||||
ty: ty.map(|t| Box::new(self.fold_ty(*t))),
|
||||
init: init.map(|e| Box::new(self.fold_expr(*e))),
|
||||
}
|
||||
}
|
||||
fn fold_expr(&mut self, e: Expr) -> Expr {
|
||||
let Expr { extents, kind } = e;
|
||||
Expr { extents: self.fold_span(extents), kind: self.fold_expr_kind(kind) }
|
||||
@@ -242,6 +236,56 @@ pub trait Fold {
|
||||
fn fold_expr_kind(&mut self, kind: ExprKind) -> ExprKind {
|
||||
or_fold_expr_kind(self, kind)
|
||||
}
|
||||
fn fold_let(&mut self, l: Let) -> Let {
|
||||
let Let { mutable, name, ty, init } = l;
|
||||
Let {
|
||||
mutable: self.fold_mutability(mutable),
|
||||
name: self.fold_pattern(name),
|
||||
ty: ty.map(|t| Box::new(self.fold_ty(*t))),
|
||||
init: init.map(|e| Box::new(self.fold_expr(*e))),
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_pattern(&mut self, p: Pattern) -> Pattern {
|
||||
match p {
|
||||
Pattern::Path(path) => Pattern::Path(self.fold_path(path)),
|
||||
Pattern::Literal(literal) => Pattern::Literal(self.fold_literal(literal)),
|
||||
Pattern::Ref(mutability, pattern) => Pattern::Ref(
|
||||
self.fold_mutability(mutability),
|
||||
Box::new(self.fold_pattern(*pattern)),
|
||||
),
|
||||
Pattern::Tuple(patterns) => {
|
||||
Pattern::Tuple(patterns.into_iter().map(|p| self.fold_pattern(p)).collect())
|
||||
}
|
||||
Pattern::Array(patterns) => {
|
||||
Pattern::Array(patterns.into_iter().map(|p| self.fold_pattern(p)).collect())
|
||||
}
|
||||
Pattern::Struct(path, items) => Pattern::Struct(
|
||||
self.fold_path(path),
|
||||
items
|
||||
.into_iter()
|
||||
.map(|(name, bind)| (name, bind.map(|p| self.fold_pattern(p))))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_match(&mut self, m: Match) -> Match {
|
||||
let Match { scrutinee, arms } = m;
|
||||
Match {
|
||||
scrutinee: self.fold_expr(*scrutinee).into(),
|
||||
arms: arms
|
||||
.into_iter()
|
||||
.map(|arm| self.fold_match_arm(arm))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_match_arm(&mut self, a: MatchArm) -> MatchArm {
|
||||
let MatchArm(pat, expr) = a;
|
||||
MatchArm(self.fold_pattern(pat), self.fold_expr(expr))
|
||||
}
|
||||
|
||||
fn fold_assign(&mut self, a: Assign) -> Assign {
|
||||
let Assign { parts } = a;
|
||||
let (head, tail) = *parts;
|
||||
@@ -276,6 +320,10 @@ pub trait Fold {
|
||||
fn fold_unary_kind(&mut self, kind: UnaryKind) -> UnaryKind {
|
||||
kind
|
||||
}
|
||||
fn fold_cast(&mut self, cast: Cast) -> Cast {
|
||||
let Cast { head, ty } = cast;
|
||||
Cast { head: Box::new(self.fold_expr_kind(*head)), ty: self.fold_ty(ty) }
|
||||
}
|
||||
fn fold_member(&mut self, m: Member) -> Member {
|
||||
let Member { head, kind } = m;
|
||||
Member { head: Box::new(self.fold_expr_kind(*head)), kind: self.fold_member_kind(kind) }
|
||||
@@ -315,9 +363,8 @@ pub trait Fold {
|
||||
}
|
||||
}
|
||||
fn fold_addrof(&mut self, a: AddrOf) -> AddrOf {
|
||||
let AddrOf { count, mutable, expr } = a;
|
||||
let AddrOf { mutable, expr } = a;
|
||||
AddrOf {
|
||||
count,
|
||||
mutable: self.fold_mutability(mutable),
|
||||
expr: Box::new(self.fold_expr_kind(*expr)),
|
||||
}
|
||||
@@ -334,10 +381,6 @@ pub trait Fold {
|
||||
let Tuple { exprs } = t;
|
||||
Tuple { exprs: exprs.into_iter().map(|e| self.fold_expr(e)).collect() }
|
||||
}
|
||||
fn fold_loop(&mut self, l: Loop) -> Loop {
|
||||
let Loop { body } = l;
|
||||
Loop { body: Box::new(self.fold_expr(*body)) }
|
||||
}
|
||||
fn fold_while(&mut self, w: While) -> While {
|
||||
let While { cond, pass, fail } = w;
|
||||
While {
|
||||
@@ -375,10 +418,6 @@ pub trait Fold {
|
||||
let Return { body } = r;
|
||||
Return { body: body.map(|e| Box::new(self.fold_expr(*e))) }
|
||||
}
|
||||
fn fold_continue(&mut self, c: Continue) -> Continue {
|
||||
let Continue = c;
|
||||
Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -388,6 +427,7 @@ pub fn or_fold_literal<F: Fold + ?Sized>(folder: &mut F, lit: Literal) -> Litera
|
||||
Literal::Bool(b) => Literal::Bool(folder.fold_bool(b)),
|
||||
Literal::Char(c) => Literal::Char(folder.fold_char(c)),
|
||||
Literal::Int(i) => Literal::Int(folder.fold_int(i)),
|
||||
Literal::Float(f) => Literal::Float(folder.fold_smuggled_float(f)),
|
||||
Literal::String(s) => Literal::String(folder.fold_string(s)),
|
||||
}
|
||||
}
|
||||
@@ -521,7 +561,6 @@ pub fn or_fold_ty_kind<F: Fold + ?Sized>(folder: &mut F, kind: TyKind) -> TyKind
|
||||
pub fn or_fold_stmt_kind<F: Fold + ?Sized>(folder: &mut F, kind: StmtKind) -> StmtKind {
|
||||
match kind {
|
||||
StmtKind::Empty => StmtKind::Empty,
|
||||
StmtKind::Local(l) => StmtKind::Local(folder.fold_let(l)),
|
||||
StmtKind::Item(i) => StmtKind::Item(Box::new(folder.fold_item(*i))),
|
||||
StmtKind::Expr(e) => StmtKind::Expr(Box::new(folder.fold_expr(*e))),
|
||||
}
|
||||
@@ -531,10 +570,14 @@ pub fn or_fold_stmt_kind<F: Fold + ?Sized>(folder: &mut F, kind: StmtKind) -> St
|
||||
pub fn or_fold_expr_kind<F: Fold + ?Sized>(folder: &mut F, kind: ExprKind) -> ExprKind {
|
||||
match kind {
|
||||
ExprKind::Empty => ExprKind::Empty,
|
||||
ExprKind::Quote(q) => ExprKind::Quote(q), // quoted expressions are left unmodified
|
||||
ExprKind::Let(l) => ExprKind::Let(folder.fold_let(l)),
|
||||
ExprKind::Match(m) => ExprKind::Match(folder.fold_match(m)),
|
||||
ExprKind::Assign(a) => ExprKind::Assign(folder.fold_assign(a)),
|
||||
ExprKind::Modify(m) => ExprKind::Modify(folder.fold_modify(m)),
|
||||
ExprKind::Binary(b) => ExprKind::Binary(folder.fold_binary(b)),
|
||||
ExprKind::Unary(u) => ExprKind::Unary(folder.fold_unary(u)),
|
||||
ExprKind::Cast(c) => ExprKind::Cast(folder.fold_cast(c)),
|
||||
ExprKind::Member(m) => ExprKind::Member(folder.fold_member(m)),
|
||||
ExprKind::Index(i) => ExprKind::Index(folder.fold_index(i)),
|
||||
ExprKind::Structor(s) => ExprKind::Structor(folder.fold_structor(s)),
|
||||
@@ -546,13 +589,12 @@ pub fn or_fold_expr_kind<F: Fold + ?Sized>(folder: &mut F, kind: ExprKind) -> Ex
|
||||
ExprKind::Block(b) => ExprKind::Block(folder.fold_block(b)),
|
||||
ExprKind::Group(g) => ExprKind::Group(folder.fold_group(g)),
|
||||
ExprKind::Tuple(t) => ExprKind::Tuple(folder.fold_tuple(t)),
|
||||
ExprKind::Loop(l) => ExprKind::Loop(folder.fold_loop(l)),
|
||||
ExprKind::While(w) => ExprKind::While(folder.fold_while(w)),
|
||||
ExprKind::If(i) => ExprKind::If(folder.fold_if(i)),
|
||||
ExprKind::For(f) => ExprKind::For(folder.fold_for(f)),
|
||||
ExprKind::Break(b) => ExprKind::Break(folder.fold_break(b)),
|
||||
ExprKind::Return(r) => ExprKind::Return(folder.fold_return(r)),
|
||||
ExprKind::Continue(c) => ExprKind::Continue(folder.fold_continue(c)),
|
||||
ExprKind::Continue => ExprKind::Continue,
|
||||
}
|
||||
}
|
||||
pub fn or_fold_member_kind<F: Fold + ?Sized>(folder: &mut F, kind: MemberKind) -> MemberKind {
|
||||
|
||||
@@ -23,6 +23,7 @@ pub trait Visit<'a>: Sized {
|
||||
fn visit_bool(&mut self, _b: &'a bool) {}
|
||||
fn visit_char(&mut self, _c: &'a char) {}
|
||||
fn visit_int(&mut self, _i: &'a u128) {}
|
||||
fn visit_smuggled_float(&mut self, _f: &'a u64) {}
|
||||
fn visit_string(&mut self, _s: &'a str) {}
|
||||
fn visit_file(&mut self, f: &'a File) {
|
||||
let File { items } = f;
|
||||
@@ -191,17 +192,6 @@ pub trait Visit<'a>: Sized {
|
||||
or_visit_stmt_kind(self, kind)
|
||||
}
|
||||
fn visit_semi(&mut self, _s: &'a Semi) {}
|
||||
fn visit_let(&mut self, l: &'a Let) {
|
||||
let Let { mutable, name, ty, init } = l;
|
||||
self.visit_mutability(mutable);
|
||||
self.visit_sym(name);
|
||||
if let Some(ty) = ty {
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
if let Some(init) = init {
|
||||
self.visit_expr(init)
|
||||
}
|
||||
}
|
||||
fn visit_expr(&mut self, e: &'a Expr) {
|
||||
let Expr { extents, kind } = e;
|
||||
self.visit_span(extents);
|
||||
@@ -210,6 +200,55 @@ pub trait Visit<'a>: Sized {
|
||||
fn visit_expr_kind(&mut self, e: &'a ExprKind) {
|
||||
or_visit_expr_kind(self, e)
|
||||
}
|
||||
fn visit_let(&mut self, l: &'a Let) {
|
||||
let Let { mutable, name, ty, init } = l;
|
||||
self.visit_mutability(mutable);
|
||||
self.visit_pattern(name);
|
||||
if let Some(ty) = ty {
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
if let Some(init) = init {
|
||||
self.visit_expr(init)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, p: &'a Pattern) {
|
||||
match p {
|
||||
Pattern::Path(path) => self.visit_path(path),
|
||||
Pattern::Literal(literal) => self.visit_literal(literal),
|
||||
Pattern::Ref(mutability, pattern) => {
|
||||
self.visit_mutability(mutability);
|
||||
self.visit_pattern(pattern);
|
||||
}
|
||||
Pattern::Tuple(patterns) => {
|
||||
patterns.iter().for_each(|p| self.visit_pattern(p));
|
||||
}
|
||||
Pattern::Array(patterns) => {
|
||||
patterns.iter().for_each(|p| self.visit_pattern(p));
|
||||
}
|
||||
Pattern::Struct(path, items) => {
|
||||
self.visit_path(path);
|
||||
items.iter().for_each(|(_name, bind)| {
|
||||
bind.as_ref().inspect(|bind| {
|
||||
self.visit_pattern(bind);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_match(&mut self, m: &'a Match) {
|
||||
let Match { scrutinee, arms } = m;
|
||||
self.visit_expr(scrutinee);
|
||||
arms.iter().for_each(|arm| self.visit_match_arm(arm));
|
||||
}
|
||||
|
||||
fn visit_match_arm(&mut self, a: &'a MatchArm) {
|
||||
let MatchArm(pat, expr) = a;
|
||||
self.visit_pattern(pat);
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
|
||||
fn visit_assign(&mut self, a: &'a Assign) {
|
||||
let Assign { parts } = a;
|
||||
let (head, tail) = parts.as_ref();
|
||||
@@ -238,6 +277,11 @@ pub trait Visit<'a>: Sized {
|
||||
self.visit_expr_kind(tail);
|
||||
}
|
||||
fn visit_unary_kind(&mut self, _kind: &'a UnaryKind) {}
|
||||
fn visit_cast(&mut self, cast: &'a Cast) {
|
||||
let Cast { head, ty } = cast;
|
||||
self.visit_expr_kind(head);
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
fn visit_member(&mut self, m: &'a Member) {
|
||||
let Member { head, kind } = m;
|
||||
self.visit_expr_kind(head);
|
||||
@@ -273,7 +317,7 @@ pub trait Visit<'a>: Sized {
|
||||
self.visit_expr_kind(repeat);
|
||||
}
|
||||
fn visit_addrof(&mut self, a: &'a AddrOf) {
|
||||
let AddrOf { count: _, mutable, expr } = a;
|
||||
let AddrOf { mutable, expr } = a;
|
||||
self.visit_mutability(mutable);
|
||||
self.visit_expr_kind(expr);
|
||||
}
|
||||
@@ -289,10 +333,6 @@ pub trait Visit<'a>: Sized {
|
||||
let Tuple { exprs } = t;
|
||||
exprs.iter().for_each(|e| self.visit_expr(e))
|
||||
}
|
||||
fn visit_loop(&mut self, l: &'a Loop) {
|
||||
let Loop { body } = l;
|
||||
self.visit_expr(body)
|
||||
}
|
||||
fn visit_while(&mut self, w: &'a While) {
|
||||
let While { cond, pass, fail } = w;
|
||||
self.visit_expr(cond);
|
||||
@@ -330,9 +370,7 @@ pub trait Visit<'a>: Sized {
|
||||
self.visit_expr(body)
|
||||
}
|
||||
}
|
||||
fn visit_continue(&mut self, c: &'a Continue) {
|
||||
let Continue = c;
|
||||
}
|
||||
fn visit_continue(&mut self) {}
|
||||
}
|
||||
|
||||
pub fn or_visit_literal<'a, V: Visit<'a>>(visitor: &mut V, l: &'a Literal) {
|
||||
@@ -340,6 +378,7 @@ pub fn or_visit_literal<'a, V: Visit<'a>>(visitor: &mut V, l: &'a Literal) {
|
||||
Literal::Bool(b) => visitor.visit_bool(b),
|
||||
Literal::Char(c) => visitor.visit_char(c),
|
||||
Literal::Int(i) => visitor.visit_int(i),
|
||||
Literal::Float(f) => visitor.visit_smuggled_float(f),
|
||||
Literal::String(s) => visitor.visit_string(s),
|
||||
}
|
||||
}
|
||||
@@ -443,7 +482,6 @@ pub fn or_visit_ty_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a TyKind) {
|
||||
pub fn or_visit_stmt_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StmtKind) {
|
||||
match kind {
|
||||
StmtKind::Empty => {}
|
||||
StmtKind::Local(l) => visitor.visit_let(l),
|
||||
StmtKind::Item(i) => visitor.visit_item(i),
|
||||
StmtKind::Expr(e) => visitor.visit_expr(e),
|
||||
}
|
||||
@@ -452,10 +490,14 @@ pub fn or_visit_stmt_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a StmtKind)
|
||||
pub fn or_visit_expr_kind<'a, V: Visit<'a>>(visitor: &mut V, e: &'a ExprKind) {
|
||||
match e {
|
||||
ExprKind::Empty => {}
|
||||
ExprKind::Quote(_q) => {} // Quoted expressions are left unvisited
|
||||
ExprKind::Let(l) => visitor.visit_let(l),
|
||||
ExprKind::Match(m) => visitor.visit_match(m),
|
||||
ExprKind::Assign(a) => visitor.visit_assign(a),
|
||||
ExprKind::Modify(m) => visitor.visit_modify(m),
|
||||
ExprKind::Binary(b) => visitor.visit_binary(b),
|
||||
ExprKind::Unary(u) => visitor.visit_unary(u),
|
||||
ExprKind::Cast(c) => visitor.visit_cast(c),
|
||||
ExprKind::Member(m) => visitor.visit_member(m),
|
||||
ExprKind::Index(i) => visitor.visit_index(i),
|
||||
ExprKind::Structor(s) => visitor.visit_structor(s),
|
||||
@@ -467,13 +509,12 @@ pub fn or_visit_expr_kind<'a, V: Visit<'a>>(visitor: &mut V, e: &'a ExprKind) {
|
||||
ExprKind::Block(b) => visitor.visit_block(b),
|
||||
ExprKind::Group(g) => visitor.visit_group(g),
|
||||
ExprKind::Tuple(t) => visitor.visit_tuple(t),
|
||||
ExprKind::Loop(l) => visitor.visit_loop(l),
|
||||
ExprKind::While(w) => visitor.visit_while(w),
|
||||
ExprKind::If(i) => visitor.visit_if(i),
|
||||
ExprKind::For(f) => visitor.visit_for(f),
|
||||
ExprKind::Break(b) => visitor.visit_break(b),
|
||||
ExprKind::Return(r) => visitor.visit_return(r),
|
||||
ExprKind::Continue(c) => visitor.visit_continue(c),
|
||||
ExprKind::Continue => visitor.visit_continue(),
|
||||
}
|
||||
}
|
||||
pub fn or_visit_member_kind<'a, V: Visit<'a>>(visitor: &mut V, kind: &'a MemberKind) {
|
||||
|
||||
@@ -26,8 +26,8 @@ fn desugar_while(extents: Span, kind: ExprKind) -> ExprKind {
|
||||
let break_expr = Expr { extents: fail_span, kind: ExprKind::Break(Break { body }) };
|
||||
|
||||
let loop_body = If { cond, pass, fail: Else { body: Some(Box::new(break_expr)) } };
|
||||
let loop_body = Expr { extents, kind: ExprKind::If(loop_body) };
|
||||
ExprKind::Loop(Loop { body: Box::new(loop_body) })
|
||||
let loop_body = ExprKind::If(loop_body);
|
||||
ExprKind::Unary(Unary { kind: UnaryKind::Loop, tail: Box::new(loop_body) })
|
||||
}
|
||||
_ => kind,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct Indent<'f, F: Write + ?Sized> {
|
||||
f: &'f mut F,
|
||||
}
|
||||
|
||||
impl<'f, F: Write + ?Sized> Write for Indent<'f, F> {
|
||||
impl<F: Write + ?Sized> Write for Indent<'_, F> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
for s in s.split_inclusive('\n') {
|
||||
self.f.write_str(s)?;
|
||||
@@ -45,14 +45,14 @@ impl<'f, F: Write + ?Sized> Delimit<'f, F> {
|
||||
Self { f, delim }
|
||||
}
|
||||
}
|
||||
impl<'f, F: Write + ?Sized> Drop for Delimit<'f, F> {
|
||||
impl<F: Write + ?Sized> Drop for Delimit<'_, F> {
|
||||
fn drop(&mut self) {
|
||||
let Self { f: Indent { f, .. }, delim } = self;
|
||||
let _ = f.write_str(delim.close);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f, F: Write + ?Sized> Write for Delimit<'f, F> {
|
||||
impl<F: Write + ?Sized> Write for Delimit<'_, F> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.f.write_str(s)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
//! - [Assign], [Binary], and [Unary] expressions
|
||||
//! - [ModifyKind], [BinaryKind], and [UnaryKind] operators
|
||||
//! - [Ty] and [TyKind]: Type qualifiers
|
||||
//! - [Pattern]: Pattern matching operators
|
||||
//! - [Path]: Path expressions
|
||||
#![warn(clippy::all)]
|
||||
#![feature(decl_macro)]
|
||||
|
||||
56
compiler/cl-interpret/examples/conlang-run.rs
Normal file
56
compiler/cl-interpret/examples/conlang-run.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
//! A bare-minimum harness to evaluate a Conlang program
|
||||
|
||||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use cl_ast::Expr;
|
||||
use cl_interpret::{convalue::ConValue, env::Environment};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::{inliner::ModuleInliner, Parser};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut args = std::env::args();
|
||||
|
||||
let prog = args.next().unwrap();
|
||||
let Some(path) = args.next().map(PathBuf::from) else {
|
||||
println!("Usage: {prog} `file.cl` [ args... ]");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let parent = path.parent().unwrap_or("".as_ref());
|
||||
|
||||
let code = std::fs::read_to_string(&path)?;
|
||||
let code = Parser::new(Lexer::new(&code)).parse()?;
|
||||
let code = match ModuleInliner::new(parent).inline(code) {
|
||||
Ok(code) => code,
|
||||
Err((code, ioerrs, perrs)) => {
|
||||
for (p, err) in ioerrs {
|
||||
eprintln!("{}:{err}", p.display());
|
||||
}
|
||||
for (p, err) in perrs {
|
||||
eprintln!("{}:{err}", p.display());
|
||||
}
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
let mut env = Environment::new();
|
||||
env.eval(&code)?;
|
||||
|
||||
let main = "main".into();
|
||||
if env.get(main).is_ok() {
|
||||
let args = args
|
||||
.flat_map(|arg| {
|
||||
Parser::new(Lexer::new(&arg))
|
||||
.parse::<Expr>()
|
||||
.map(|arg| env.eval(&arg))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
match env.call(main, &args)? {
|
||||
ConValue::Empty => {}
|
||||
retval => println!("{retval}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,67 +1,192 @@
|
||||
//! Implementations of built-in functions
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use super::{
|
||||
use crate::{
|
||||
convalue::ConValue,
|
||||
env::Environment,
|
||||
error::{Error, IResult},
|
||||
BuiltIn, Callable,
|
||||
};
|
||||
use cl_ast::Sym;
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
rc::Rc,
|
||||
slice,
|
||||
};
|
||||
|
||||
builtins! {
|
||||
const MISC;
|
||||
/// A function built into the interpreter.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Builtin {
|
||||
/// An identifier to be used during registration
|
||||
pub name: &'static str,
|
||||
/// The signature, displayed when the builtin is printed
|
||||
pub desc: &'static str,
|
||||
/// The function to be run when called
|
||||
pub func: &'static dyn Fn(&mut Environment, &[ConValue]) -> IResult<ConValue>,
|
||||
}
|
||||
|
||||
impl Builtin {
|
||||
/// Constructs a new Builtin
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
desc: &'static str,
|
||||
func: &'static impl Fn(&mut Environment, &[ConValue]) -> IResult<ConValue>,
|
||||
) -> Builtin {
|
||||
Builtin { name, desc, func }
|
||||
}
|
||||
|
||||
pub const fn description(&self) -> &'static str {
|
||||
self.desc
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Builtin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Builtin")
|
||||
.field("description", &self.desc)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Callable for Builtin {
|
||||
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
(self.func)(interpreter, args)
|
||||
}
|
||||
|
||||
fn name(&self) -> cl_ast::Sym {
|
||||
self.name.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a function definition into a [Builtin].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cl_interpret::{builtin2::builtin, convalue::ConValue};
|
||||
/// let my_builtin = builtin! {
|
||||
/// /// Use the `@env` suffix to bind the environment!
|
||||
/// /// (needed for recursive calls)
|
||||
/// fn my_builtin(ConValue::Bool(b), rest @ ..) @env {
|
||||
/// // This is all Rust code!
|
||||
/// eprintln!("my_builtin({b}, ..)");
|
||||
/// match rest {
|
||||
/// [] => Ok(ConValue::Empty),
|
||||
/// _ => my_builtin(env, rest), // Can be called as a normal function!
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub macro builtin(
|
||||
$(#[$($meta:tt)*])*
|
||||
fn $name:ident ($($arg:pat),*$(,)?) $(@$env:tt)? $body:block
|
||||
) {{
|
||||
$(#[$($meta)*])*
|
||||
fn $name(_env: &mut Environment, _args: &[ConValue]) -> IResult<ConValue> {
|
||||
// Set up the builtin! environment
|
||||
$(let $env = _env;)?
|
||||
// Allow for single argument `fn foo(args @ ..)` pattern
|
||||
#[allow(clippy::redundant_at_rest_pattern, irrefutable_let_patterns)]
|
||||
let [$($arg),*] = _args else {
|
||||
Err($crate::error::Error::TypeError)?
|
||||
};
|
||||
$body.map(Into::into)
|
||||
}
|
||||
Builtin {
|
||||
name: stringify!($name),
|
||||
desc: stringify![builtin fn $name($($arg),*)],
|
||||
func: &$name,
|
||||
}
|
||||
}}
|
||||
|
||||
/// Constructs an array of [Builtin]s from pseudo-function definitions
|
||||
pub macro builtins($(
|
||||
$(#[$($meta:tt)*])*
|
||||
fn $name:ident ($($args:tt)*) $(@$env:tt)? $body:block
|
||||
)*) {
|
||||
[$(builtin!($(#[$($meta)*])* fn $name ($($args)*) $(@$env)? $body)),*]
|
||||
}
|
||||
|
||||
/// Creates an [Error::BuiltinDebug] using interpolation of runtime expressions.
|
||||
/// See [std::format].
|
||||
pub macro error_format ($($t:tt)*) {
|
||||
$crate::error::Error::BuiltinDebug(format!($($t)*))
|
||||
}
|
||||
|
||||
pub const Builtins: &[Builtin] = &builtins version of the input values
|
||||
pub fn dbg<_, args> () -> IResult<ConValue> {
|
||||
/// Debug-prints the argument, returning a copy
|
||||
fn dbg(arg) {
|
||||
println!("{arg:?}");
|
||||
Ok(arg.clone())
|
||||
}
|
||||
|
||||
/// Debug-prints the argument
|
||||
fn dbgp(args @ ..) {
|
||||
let mut out = stdout().lock();
|
||||
for arg in args {
|
||||
writeln!(out, "{arg:?}").ok();
|
||||
}
|
||||
Ok(args.into())
|
||||
args.iter().try_for_each(|arg| writeln!(out, "{arg:#?}") ).ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dumps info from the environment
|
||||
pub fn dump<env, _>() -> IResult<ConValue> {
|
||||
println!("{}", *env);
|
||||
/// Dumps the environment
|
||||
fn dump() @env {
|
||||
println!("{env}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn builtins() @env {
|
||||
for builtin in env.builtins().values().flatten() {
|
||||
println!("{builtin}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the length of the input list as a [ConValue::Int]
|
||||
fn len(list) @env {
|
||||
Ok(match list {
|
||||
ConValue::Empty => 0,
|
||||
ConValue::String(s) => s.chars().count() as _,
|
||||
ConValue::Ref(r) => return len(env, slice::from_ref(r.as_ref())),
|
||||
ConValue::Array(t) => t.len() as _,
|
||||
ConValue::Tuple(t) => t.len() as _,
|
||||
ConValue::RangeExc(start, end) => (end - start) as _,
|
||||
ConValue::RangeInc(start, end) => (end - start + 1) as _,
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn dump_symbols() {
|
||||
println!("{}", cl_structures::intern::string_interner::StringInterner::global());
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
|
||||
/// Returns a shark
|
||||
fn shark() {
|
||||
Ok('\u{1f988}')
|
||||
}
|
||||
builtins! {
|
||||
const BINARY;
|
||||
];
|
||||
|
||||
pub const Math: &[Builtin] = &builtins![
|
||||
/// Multiplication `a * b`
|
||||
pub fn mul(lhs, rhs) -> IResult<ConValue> {
|
||||
fn mul(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b),
|
||||
@@ -70,7 +195,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Division `a / b`
|
||||
pub fn div(lhs, rhs) -> IResult<ConValue> {
|
||||
fn div(lhs, rhs) {
|
||||
Ok(match (lhs, rhs){
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b),
|
||||
@@ -79,7 +204,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Remainder `a % b`
|
||||
pub fn rem(lhs, rhs) -> IResult<ConValue> {
|
||||
fn rem(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b),
|
||||
@@ -88,7 +213,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Addition `a + b`
|
||||
pub fn add(lhs, rhs) -> IResult<ConValue> {
|
||||
fn add(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||
@@ -98,7 +223,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Subtraction `a - b`
|
||||
pub fn sub(lhs, rhs) -> IResult<ConValue> {
|
||||
fn sub(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b),
|
||||
@@ -107,7 +232,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Shift Left `a << b`
|
||||
pub fn shl(lhs, rhs) -> IResult<ConValue> {
|
||||
fn shl(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b),
|
||||
@@ -116,7 +241,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Shift Right `a >> b`
|
||||
pub fn shr(lhs, rhs) -> IResult<ConValue> {
|
||||
fn shr(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b),
|
||||
@@ -125,7 +250,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Bitwise And `a & b`
|
||||
pub fn and(lhs, rhs) -> IResult<ConValue> {
|
||||
fn and(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b),
|
||||
@@ -135,7 +260,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Bitwise Or `a | b`
|
||||
pub fn or(lhs, rhs) -> IResult<ConValue> {
|
||||
fn or(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b),
|
||||
@@ -145,7 +270,7 @@ builtins! {
|
||||
}
|
||||
|
||||
/// Bitwise Exclusive Or `a ^ b`
|
||||
pub fn xor(lhs, rhs) -> IResult<ConValue> {
|
||||
fn xor(lhs, rhs) {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b),
|
||||
@@ -154,67 +279,34 @@ builtins! {
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
fn range_exc(from, to) {
|
||||
let (&ConValue::Int(from), &ConValue::Int(to)) = (from, to) else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1)))
|
||||
Ok(ConValue::RangeExc(from, to))
|
||||
}
|
||||
|
||||
/// Inclusive Range `a..=b`
|
||||
pub fn range_inc(lhs, rhs) -> IResult<ConValue> {
|
||||
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||
fn range_inc(from, to) {
|
||||
let (&ConValue::Int(from), &ConValue::Int(to)) = (from, to) else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
Ok(ConValue::RangeInc(lhs, rhs))
|
||||
Ok(ConValue::RangeInc(from, to))
|
||||
}
|
||||
}
|
||||
builtins! {
|
||||
const UNARY;
|
||||
|
||||
/// Negates the ConValue
|
||||
pub fn neg(tail) -> IResult<ConValue> {
|
||||
fn neg(tail) {
|
||||
Ok(match tail {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(v.wrapping_neg()),
|
||||
ConValue::Float(v) => ConValue::Float(-v),
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inverts the ConValue
|
||||
pub fn not(tail) -> IResult<ConValue> {
|
||||
fn not(tail) {
|
||||
Ok(match tail {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(!v),
|
||||
@@ -223,62 +315,23 @@ builtins! {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deref(tail) -> IResult<ConValue> {
|
||||
/// Compares two values
|
||||
fn cmp(head, tail) {
|
||||
Ok(ConValue::Int(match (head, tail) {
|
||||
(ConValue::Int(a), ConValue::Int(b)) => a.cmp(b) as _,
|
||||
(ConValue::Bool(a), ConValue::Bool(b)) => a.cmp(b) as _,
|
||||
(ConValue::Char(a), ConValue::Char(b)) => a.cmp(b) as _,
|
||||
(ConValue::String(a), ConValue::String(b)) => a.cmp(b) as _,
|
||||
_ => Err(error_format!("Incomparable values: {head}, {tail}"))?
|
||||
}))
|
||||
}
|
||||
|
||||
/// Does the opposite of `&`
|
||||
fn deref(tail) {
|
||||
use std::rc::Rc;
|
||||
Ok(match tail {
|
||||
ConValue::Ref(v) => Rc::as_ref(v).clone(),
|
||||
_ => tail.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> Sym { stringify!($name).into() }
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
336
compiler/cl-interpret/src/convalue.rs
Normal file
336
compiler/cl-interpret/src/convalue.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
//! Values in the dynamically typed AST interpreter.
|
||||
//!
|
||||
//! The most permanent fix is a temporary one.
|
||||
use cl_ast::{format::FmtAdapter, ExprKind, Sym};
|
||||
|
||||
use super::{
|
||||
builtin::Builtin,
|
||||
error::{Error, IResult},
|
||||
function::Function, Callable, Environment,
|
||||
};
|
||||
use std::{collections::HashMap, ops::*, rc::Rc};
|
||||
|
||||
type Integer = isize;
|
||||
|
||||
/// A Conlang value stores data in the interpreter
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum ConValue {
|
||||
/// The empty/unit `()` type
|
||||
#[default]
|
||||
Empty,
|
||||
/// An integer
|
||||
Int(Integer),
|
||||
/// A floating point number
|
||||
Float(f64),
|
||||
/// A boolean
|
||||
Bool(bool),
|
||||
/// A unicode character
|
||||
Char(char),
|
||||
/// A string
|
||||
String(Sym),
|
||||
/// A reference
|
||||
Ref(Rc<ConValue>),
|
||||
/// An Array
|
||||
Array(Box<[ConValue]>),
|
||||
/// A tuple
|
||||
Tuple(Box<[ConValue]>),
|
||||
/// An exclusive range
|
||||
RangeExc(Integer, Integer),
|
||||
/// An inclusive range
|
||||
RangeInc(Integer, Integer),
|
||||
/// A value of a product type
|
||||
Struct(Box<(Sym, HashMap<Sym, ConValue>)>),
|
||||
/// An entire namespace
|
||||
Module(Box<HashMap<Sym, Option<ConValue>>>),
|
||||
/// A quoted expression
|
||||
Quote(Box<ExprKind>),
|
||||
/// A callable thing
|
||||
Function(Rc<Function>),
|
||||
/// A built-in function
|
||||
Builtin(&'static 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))
|
||||
}
|
||||
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)?
|
||||
};
|
||||
match self {
|
||||
ConValue::String(string) => string
|
||||
.chars()
|
||||
.nth(*index as _)
|
||||
.map(ConValue::Char)
|
||||
.ok_or(Error::OobIndex(*index as usize, string.chars().count())),
|
||||
ConValue::Array(arr) => arr
|
||||
.get(*index as usize)
|
||||
.cloned()
|
||||
.ok_or(Error::OobIndex(*index as usize, arr.len())),
|
||||
_ => Err(Error::TypeError),
|
||||
}
|
||||
}
|
||||
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) -> Sym {
|
||||
match self {
|
||||
ConValue::Function(func) => func.name(),
|
||||
ConValue::Builtin(func) => func.name(),
|
||||
_ => "".into(),
|
||||
}
|
||||
}
|
||||
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::Float(a), Self::Float(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()) }
|
||||
})*
|
||||
}
|
||||
impl From<&Sym> for ConValue {
|
||||
fn from(value: &Sym) -> Self {
|
||||
ConValue::String(*value)
|
||||
}
|
||||
}
|
||||
from! {
|
||||
Integer => ConValue::Int,
|
||||
f64 => ConValue::Float,
|
||||
bool => ConValue::Bool,
|
||||
char => ConValue::Char,
|
||||
Sym => ConValue::String,
|
||||
&str => ConValue::String,
|
||||
String => ConValue::String,
|
||||
Rc<str> => ConValue::String,
|
||||
ExprKind => ConValue::Quote,
|
||||
Function => ConValue::Function,
|
||||
Vec<ConValue> => ConValue::Tuple,
|
||||
&'static 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.wrapping_add(b)),
|
||||
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a + b),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &*b).into(),
|
||||
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
|
||||
(ConValue::Char(a), ConValue::Char(b)) => {
|
||||
ConValue::String([a, b].into_iter().collect::<String>().into())
|
||||
}
|
||||
_ => 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.checked_div(b).unwrap_or_else(|| {
|
||||
eprintln!("Warning: Divide by zero in {a} / {b}"); a
|
||||
})),
|
||||
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a / b),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Mul: mul = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)),
|
||||
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a * b),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Rem: rem = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| {
|
||||
println!("Warning: Divide by zero in {a} % {b}"); a
|
||||
})),
|
||||
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(a % b),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Shl: shl = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shl(b as _)),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Shr: shr = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shr(b as _)),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Sub: sub = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(b)),
|
||||
(ConValue::Float(a), ConValue::Float(b)) => ConValue::Float(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::Float(v) => v.fmt(f),
|
||||
ConValue::Bool(v) => v.fmt(f),
|
||||
ConValue::Char(v) => v.fmt(f),
|
||||
ConValue::String(v) => v.fmt(f),
|
||||
ConValue::Ref(v) => write!(f, "&{v}"),
|
||||
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::Struct(parts) => {
|
||||
let (name, map) = parts.as_ref();
|
||||
use std::fmt::Write;
|
||||
if !name.is_empty() {
|
||||
write!(f, "{name}: ")?;
|
||||
}
|
||||
let mut f = f.delimit_with("{", "\n}");
|
||||
for (k, v) in map.iter() {
|
||||
write!(f, "\n{k}: {v},")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ConValue::Module(module) => {
|
||||
use std::fmt::Write;
|
||||
let mut f = f.delimit_with("{", "\n}");
|
||||
for (k, v) in module.iter() {
|
||||
write!(f, "\n{k}: ")?;
|
||||
match v {
|
||||
Some(v) => write!(f, "{v},"),
|
||||
None => write!(f, "_,"),
|
||||
}?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ConValue::Quote(q) => {
|
||||
write!(f, "`{q}`")
|
||||
}
|
||||
ConValue::Function(func) => {
|
||||
write!(f, "{}", func.decl())
|
||||
}
|
||||
ConValue::Builtin(func) => {
|
||||
write!(f, "{}", func.description())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
compiler/cl-interpret/src/env.rs
Normal file
236
compiler/cl-interpret/src/env.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
//! Lexical and non-lexical scoping for variables
|
||||
|
||||
use crate::builtin::Builtin;
|
||||
|
||||
use super::{
|
||||
builtin::{Builtins, Math},
|
||||
convalue::ConValue,
|
||||
error::{Error, IResult},
|
||||
function::Function,
|
||||
Callable, Interpret,
|
||||
};
|
||||
use cl_ast::{Function as FnDecl, Sym};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
type StackFrame = HashMap<Sym, Option<ConValue>>;
|
||||
|
||||
/// Implements a nested lexical scope
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Environment {
|
||||
builtin: StackFrame,
|
||||
global: Vec<(StackFrame, &'static str)>,
|
||||
frames: Vec<(StackFrame, &'static str)>,
|
||||
}
|
||||
|
||||
impl Display for Environment {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (frame, name) in self
|
||||
.global
|
||||
.iter()
|
||||
.rev()
|
||||
.take(2)
|
||||
.rev()
|
||||
.chain(self.frames.iter())
|
||||
{
|
||||
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 {
|
||||
builtin: to_hashmap(Builtins.iter().chain(Math.iter())),
|
||||
global: vec![(HashMap::new(), "globals")],
|
||||
frames: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_hashmap(from: impl IntoIterator<Item = &'static Builtin>) -> HashMap<Sym, Option<ConValue>> {
|
||||
from.into_iter()
|
||||
.map(|v| (v.name(), Some(v.into())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
/// Creates an [Environment] with no [builtins](super::builtin)
|
||||
pub fn no_builtins() -> Self {
|
||||
Self {
|
||||
builtin: HashMap::new(),
|
||||
global: vec![(Default::default(), "globals")],
|
||||
frames: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtins(&self) -> &StackFrame {
|
||||
&self.builtin
|
||||
}
|
||||
|
||||
pub fn add_builtin(&mut self, builtin: &'static Builtin) -> &mut Self {
|
||||
self.builtin.insert(builtin.name(), Some(builtin.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_builtins(&mut self, builtins: &'static [Builtin]) {
|
||||
for builtin in builtins {
|
||||
self.add_builtin(builtin);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_frame(&mut self, name: &'static str, frame: StackFrame) {
|
||||
self.frames.push((frame, name));
|
||||
}
|
||||
|
||||
pub fn pop_frame(&mut self) -> Option<(StackFrame, &'static str)> {
|
||||
self.frames.pop()
|
||||
}
|
||||
|
||||
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: Sym, 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: Sym) -> IResult<&mut Option<ConValue>> {
|
||||
for (frame, _) in self.frames.iter_mut().rev() {
|
||||
if let Some(var) = frame.get_mut(&id) {
|
||||
return Ok(var);
|
||||
}
|
||||
}
|
||||
for (frame, _) in self.global.iter_mut().rev() {
|
||||
if let Some(var) = frame.get_mut(&id) {
|
||||
return Ok(var);
|
||||
}
|
||||
}
|
||||
self.builtin.get_mut(&id).ok_or(Error::NotDefined(id))
|
||||
}
|
||||
/// Resolves a variable immutably.
|
||||
///
|
||||
/// Returns a reference to the variable's contents, if it is defined and initialized.
|
||||
pub fn get(&self, id: Sym) -> IResult<ConValue> {
|
||||
for (frame, _) in self.frames.iter().rev() {
|
||||
match frame.get(&id) {
|
||||
Some(Some(var)) => return Ok(var.clone()),
|
||||
Some(None) => return Err(Error::NotInitialized(id)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
for (frame, _) in self.global.iter().rev() {
|
||||
match frame.get(&id) {
|
||||
Some(Some(var)) => return Ok(var.clone()),
|
||||
Some(None) => return Err(Error::NotInitialized(id)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.builtin
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.flatten()
|
||||
.ok_or(Error::NotDefined(id))
|
||||
}
|
||||
|
||||
pub(crate) fn get_local(&self, id: Sym) -> IResult<ConValue> {
|
||||
for (frame, _) in self.frames.iter().rev() {
|
||||
match frame.get(&id) {
|
||||
Some(Some(var)) => return Ok(var.clone()),
|
||||
Some(None) => return Err(Error::NotInitialized(id)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Err(Error::NotInitialized(id))
|
||||
}
|
||||
|
||||
/// Inserts a new [ConValue] into this [Environment]
|
||||
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
|
||||
if let Some((frame, _)) = self.frames.last_mut() {
|
||||
frame.insert(id, value);
|
||||
} else if let Some((frame, _)) = self.global.last_mut() {
|
||||
frame.insert(id, value);
|
||||
}
|
||||
}
|
||||
/// A convenience function for registering a [FnDecl] as a [Function]
|
||||
pub fn insert_fn(&mut self, decl: &FnDecl) {
|
||||
let FnDecl { name, .. } = decl;
|
||||
let (name, function) = (name, Rc::new(Function::new(decl)));
|
||||
if let Some((frame, _)) = self.frames.last_mut() {
|
||||
frame.insert(*name, Some(ConValue::Function(function.clone())));
|
||||
} else if let Some((frame, _)) = self.global.last_mut() {
|
||||
frame.insert(*name, Some(ConValue::Function(function.clone())));
|
||||
}
|
||||
// Tell the function to lift its upvars now, after it's been declared
|
||||
function.lift_upvars(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 Deref for Frame<'_> {
|
||||
type Target = Environment;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.scope
|
||||
}
|
||||
}
|
||||
impl DerefMut for Frame<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.scope
|
||||
}
|
||||
}
|
||||
impl Drop for Frame<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.scope.exit();
|
||||
}
|
||||
}
|
||||
97
compiler/cl-interpret/src/error.rs
Normal file
97
compiler/cl-interpret/src/error.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
|
||||
|
||||
use cl_ast::{Pattern, Sym};
|
||||
|
||||
use super::convalue::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(Sym),
|
||||
/// A name was defined but not initialized
|
||||
NotInitialized(Sym),
|
||||
/// 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 },
|
||||
/// A pattern failed to match
|
||||
PatFailed(Box<Pattern>),
|
||||
/// Fell through a non-exhaustive match
|
||||
MatchNonexhaustive,
|
||||
/// Error produced by a Builtin
|
||||
BuiltinDebug(String),
|
||||
}
|
||||
|
||||
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::PatFailed(pattern) => {
|
||||
write!(f, "Failed to match pattern {pattern}")
|
||||
}
|
||||
Error::MatchNonexhaustive => {
|
||||
write!(f, "Fell through a non-exhaustive match expression!")
|
||||
}
|
||||
Error::BuiltinDebug(s) => write!(f, "DEBUG: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
80
compiler/cl-interpret/src/function.rs
Normal file
80
compiler/cl-interpret/src/function.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! Represents a block of code which lives inside the Interpreter
|
||||
|
||||
use collect_upvars::collect_upvars;
|
||||
|
||||
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
|
||||
use cl_ast::{Function as FnDecl, Param, Sym};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub mod collect_upvars;
|
||||
|
||||
type Upvars = HashMap<Sym, Option<ConValue>>;
|
||||
|
||||
/// Represents a block of code which persists inside the Interpreter
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Function {
|
||||
/// Stores the contents of the function declaration
|
||||
decl: Rc<FnDecl>,
|
||||
/// Stores data from the enclosing scopes
|
||||
upvars: RefCell<Upvars>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new(decl: &FnDecl) -> Self {
|
||||
// let upvars = collect_upvars(decl, env);
|
||||
Self { decl: decl.clone().into(), upvars: Default::default() }
|
||||
}
|
||||
pub fn decl(&self) -> &FnDecl {
|
||||
&self.decl
|
||||
}
|
||||
pub fn upvars(&self) -> Ref<Upvars> {
|
||||
self.upvars.borrow()
|
||||
}
|
||||
pub fn lift_upvars(&self, env: &Environment) {
|
||||
let upvars = collect_upvars(&self.decl, env);
|
||||
if let Ok(mut self_upvars) = self.upvars.try_borrow_mut() {
|
||||
*self_upvars = upvars;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Callable for Function {
|
||||
fn name(&self) -> Sym {
|
||||
let FnDecl { name, .. } = *self.decl;
|
||||
name
|
||||
}
|
||||
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
|
||||
|
||||
// Check arg mapping
|
||||
if args.len() != bind.len() {
|
||||
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
|
||||
}
|
||||
let Some(body) = body else {
|
||||
return Err(Error::NotDefined(*name));
|
||||
};
|
||||
|
||||
let upvars = self.upvars.take();
|
||||
env.push_frame("upvars", upvars);
|
||||
|
||||
// TODO: completely refactor data storage
|
||||
let mut frame = env.frame("fn args");
|
||||
for (Param { mutability: _, name }, value) in bind.iter().zip(args) {
|
||||
frame.insert(*name, Some(value.clone()));
|
||||
}
|
||||
let res = body.interpret(&mut frame);
|
||||
drop(frame);
|
||||
if let Some((upvars, _)) = env.pop_frame() {
|
||||
self.upvars.replace(upvars);
|
||||
}
|
||||
match res {
|
||||
Err(Error::Return(value)) => Ok(value),
|
||||
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
|
||||
result => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
134
compiler/cl-interpret/src/function/collect_upvars.rs
Normal file
134
compiler/cl-interpret/src/function/collect_upvars.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Collects the "Upvars" of a function at the point of its creation, allowing variable capture
|
||||
use crate::{convalue::ConValue, env::Environment};
|
||||
use cl_ast::{ast_visitor::visit::*, Function, Let, Param, Path, PathPart, Pattern, Sym};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub fn collect_upvars(f: &Function, env: &Environment) -> super::Upvars {
|
||||
CollectUpvars::new(env).get_upvars(f)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CollectUpvars<'env> {
|
||||
env: &'env Environment,
|
||||
upvars: HashMap<Sym, Option<ConValue>>,
|
||||
blacklist: HashSet<Sym>,
|
||||
}
|
||||
|
||||
impl<'env> CollectUpvars<'env> {
|
||||
pub fn new(env: &'env Environment) -> Self {
|
||||
Self { upvars: HashMap::new(), blacklist: HashSet::new(), env }
|
||||
}
|
||||
pub fn get_upvars(mut self, f: &cl_ast::Function) -> HashMap<Sym, Option<ConValue>> {
|
||||
self.visit_function(f);
|
||||
self.upvars
|
||||
}
|
||||
|
||||
pub fn add_upvar(&mut self, name: &Sym) {
|
||||
let Self { env, upvars, blacklist } = self;
|
||||
if blacklist.contains(name) || upvars.contains_key(name) {
|
||||
return;
|
||||
}
|
||||
if let Ok(upvar) = env.get_local(*name) {
|
||||
upvars.insert(*name, Some(upvar));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_name(&mut self, name: &Sym) {
|
||||
self.blacklist.insert(*name);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit<'a> for CollectUpvars<'_> {
|
||||
fn visit_block(&mut self, b: &'a cl_ast::Block) {
|
||||
let blacklist = self.blacklist.clone();
|
||||
|
||||
// visit the block
|
||||
let cl_ast::Block { stmts } = b;
|
||||
stmts.iter().for_each(|s| self.visit_stmt(s));
|
||||
|
||||
// restore the blacklist
|
||||
self.blacklist = blacklist;
|
||||
}
|
||||
|
||||
fn visit_let(&mut self, l: &'a cl_ast::Let) {
|
||||
let Let { mutable, name, ty, init } = l;
|
||||
self.visit_mutability(mutable);
|
||||
if let Some(ty) = ty {
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
// visit the initializer, which may use the bound name
|
||||
if let Some(init) = init {
|
||||
self.visit_expr(init)
|
||||
}
|
||||
// a bound name can never be an upvar
|
||||
self.visit_pattern(name);
|
||||
}
|
||||
|
||||
fn visit_function(&mut self, f: &'a cl_ast::Function) {
|
||||
let Function { name: _, sign: _, bind, body } = f;
|
||||
// parameters can never be upvars
|
||||
for Param { mutability: _, name } in bind {
|
||||
self.bind_name(name);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
self.visit_block(body);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_for(&mut self, f: &'a cl_ast::For) {
|
||||
let cl_ast::For { bind, cond, pass, fail } = f;
|
||||
self.visit_expr(cond);
|
||||
self.visit_else(fail);
|
||||
self.bind_name(bind); // TODO: is bind only bound in the pass block?
|
||||
self.visit_block(pass);
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, p: &'a cl_ast::Path) {
|
||||
// TODO: path resolution in environments
|
||||
let Path { absolute: false, parts } = p else {
|
||||
return;
|
||||
};
|
||||
let [PathPart::Ident(name)] = parts.as_slice() else {
|
||||
return;
|
||||
};
|
||||
self.add_upvar(name);
|
||||
}
|
||||
|
||||
fn visit_fielder(&mut self, f: &'a cl_ast::Fielder) {
|
||||
let cl_ast::Fielder { name, init } = f;
|
||||
if let Some(init) = init {
|
||||
self.visit_expr(init);
|
||||
} else {
|
||||
self.add_upvar(name); // fielder without init grabs from env
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, p: &'a cl_ast::Pattern) {
|
||||
match p {
|
||||
Pattern::Path(path) => {
|
||||
if let [PathPart::Ident(name)] = path.parts.as_slice() {
|
||||
self.bind_name(name)
|
||||
}
|
||||
}
|
||||
Pattern::Literal(literal) => self.visit_literal(literal),
|
||||
Pattern::Ref(mutability, pattern) => {
|
||||
self.visit_mutability(mutability);
|
||||
self.visit_pattern(pattern);
|
||||
}
|
||||
Pattern::Tuple(patterns) => {
|
||||
patterns.iter().for_each(|p| self.visit_pattern(p));
|
||||
}
|
||||
Pattern::Array(patterns) => {
|
||||
patterns.iter().for_each(|p| self.visit_pattern(p));
|
||||
}
|
||||
Pattern::Struct(path, items) => {
|
||||
self.visit_path(path);
|
||||
items.iter().for_each(|(_name, bind)| {
|
||||
bind.as_ref().inspect(|bind| {
|
||||
self.visit_pattern(bind);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use std::{borrow::Borrow, rc::Rc};
|
||||
|
||||
use super::*;
|
||||
use cl_ast::*;
|
||||
use cl_structures::intern::interned::Interned;
|
||||
/// A work-in-progress tree walk interpreter for Conlang
|
||||
pub trait Interpret {
|
||||
/// Interprets this thing in the given [`Environment`].
|
||||
@@ -36,7 +37,7 @@ impl Interpret for Item {
|
||||
ItemKind::Struct(item) => item.interpret(env),
|
||||
ItemKind::Enum(item) => item.interpret(env),
|
||||
ItemKind::Impl(item) => item.interpret(env),
|
||||
ItemKind::Use(_) => todo!("namespaces and imports in the interpreter"),
|
||||
ItemKind::Use(item) => item.interpret(env),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,11 +68,21 @@ impl Interpret for Static {
|
||||
impl Interpret for Module {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { name, kind } = self;
|
||||
// TODO: Enter this module's namespace
|
||||
match kind {
|
||||
env.push_frame(Interned::to_ref(name), Default::default());
|
||||
let out = match kind {
|
||||
ModuleKind::Inline(file) => file.interpret(env),
|
||||
ModuleKind::Outline => Err(Error::Outlined(*name)),
|
||||
ModuleKind::Outline => {
|
||||
eprintln!("Module {name} specified, but not imported.");
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
};
|
||||
|
||||
let (frame, _) = env
|
||||
.pop_frame()
|
||||
.expect("Environment frames must be balanced");
|
||||
env.insert(*name, Some(ConValue::Module(frame.into())));
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
impl Interpret for Function {
|
||||
@@ -100,12 +111,78 @@ impl Interpret for Impl {
|
||||
body.interpret(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for Use {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { absolute: _, tree } = self;
|
||||
tree.interpret(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for UseTree {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
type Bindings = HashMap<Sym, ConValue>;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn get_bindings(
|
||||
tree: &UseTree,
|
||||
env: &mut Environment,
|
||||
bindings: &mut Bindings,
|
||||
) -> IResult<()> {
|
||||
match tree {
|
||||
UseTree::Tree(use_trees) => {
|
||||
for tree in use_trees {
|
||||
get_bindings(tree, env, bindings)?;
|
||||
}
|
||||
}
|
||||
UseTree::Path(PathPart::Ident(name), tree) => {
|
||||
let Ok(ConValue::Module(m)) = env.get(*name) else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
env.push_frame(Interned::to_ref(name), *m);
|
||||
let out = get_bindings(tree, env, bindings);
|
||||
env.pop_frame();
|
||||
return out;
|
||||
}
|
||||
UseTree::Alias(name, alias) => {
|
||||
bindings.insert(*alias, env.get(*name)?);
|
||||
}
|
||||
UseTree::Name(name) => {
|
||||
bindings.insert(*name, env.get(*name)?);
|
||||
}
|
||||
UseTree::Glob => {
|
||||
if let Some((frame, name)) = env.pop_frame() {
|
||||
for (k, v) in &frame {
|
||||
if let Some(v) = v {
|
||||
bindings.insert(*k, v.clone());
|
||||
}
|
||||
}
|
||||
env.push_frame(name, frame);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
eprintln!("ERROR: Cannot use {other}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mut bindings = Bindings::new();
|
||||
get_bindings(self, env, &mut bindings)?;
|
||||
|
||||
for (name, value) in bindings {
|
||||
env.insert(name, Some(value));
|
||||
}
|
||||
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
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)?,
|
||||
};
|
||||
@@ -115,14 +192,7 @@ impl Interpret for Stmt {
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Interpret for Let {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Let { mutable: _, 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> {
|
||||
@@ -130,14 +200,19 @@ impl Interpret for Expr {
|
||||
kind.interpret(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for ExprKind {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
match self {
|
||||
ExprKind::Empty => Ok(ConValue::Empty),
|
||||
ExprKind::Quote(q) => q.interpret(env),
|
||||
ExprKind::Let(v) => v.interpret(env),
|
||||
ExprKind::Match(v) => v.interpret(env),
|
||||
ExprKind::Assign(v) => v.interpret(env),
|
||||
ExprKind::Modify(v) => v.interpret(env),
|
||||
ExprKind::Binary(v) => v.interpret(env),
|
||||
ExprKind::Unary(v) => v.interpret(env),
|
||||
ExprKind::Cast(v) => v.interpret(env),
|
||||
ExprKind::Member(v) => v.interpret(env),
|
||||
ExprKind::Index(v) => v.interpret(env),
|
||||
ExprKind::Structor(v) => v.interpret(env),
|
||||
@@ -149,36 +224,306 @@ impl Interpret for ExprKind {
|
||||
ExprKind::Block(v) => v.interpret(env),
|
||||
ExprKind::Group(v) => v.interpret(env),
|
||||
ExprKind::Tuple(v) => v.interpret(env),
|
||||
ExprKind::Loop(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),
|
||||
ExprKind::Continue => Err(Error::Continue),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_place_expr<'e>(
|
||||
env: &'e mut Environment,
|
||||
expr: &ExprKind,
|
||||
) -> IResult<(&'e mut Option<ConValue>, Sym)> {
|
||||
match expr {
|
||||
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::SelfTy => todo!("What does it mean to assign to capital-S Self?"),
|
||||
PathPart::Ident(s) => env.get_mut(*s).map(|v| (v, *s)),
|
||||
impl Interpret for Quote {
|
||||
fn interpret(&self, _env: &mut Environment) -> IResult<ConValue> {
|
||||
// TODO: squoosh down into a ConValue?
|
||||
Ok(ConValue::Quote(self.quote.clone()))
|
||||
}
|
||||
}
|
||||
ExprKind::Index(_) => todo!("Assignment to an index operation"),
|
||||
ExprKind::Path(_) => todo!("Path expression resolution (IMPORTANT)"),
|
||||
ExprKind::Empty | ExprKind::Group(_) | ExprKind::Tuple(_) => {
|
||||
todo!("Pattern Destructuring?")
|
||||
|
||||
impl Interpret for Let {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Let { mutable: _, name, ty: _, init } = self;
|
||||
match init.as_ref().map(|i| i.interpret(env)).transpose()? {
|
||||
Some(value) => {
|
||||
for (path, value) in assignment::pattern_substitution(name, value)? {
|
||||
match path.parts.as_slice() {
|
||||
[PathPart::Ident(name)] => env.insert(*name, Some(value)),
|
||||
_ => eprintln!("Bad assignment: {path} = {value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for path in assignment::pattern_variables(name) {
|
||||
match path.parts.as_slice() {
|
||||
[PathPart::Ident(name)] => env.insert(*name, None),
|
||||
_ => eprintln!("Bad assignment: {path}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for Match {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { scrutinee, arms } = self;
|
||||
let scrutinee = scrutinee.interpret(env)?;
|
||||
'arm: for MatchArm(pat, expr) in arms {
|
||||
if let Ok(substitution) = assignment::pattern_substitution(pat, scrutinee.clone()) {
|
||||
let mut env = env.frame("match");
|
||||
for (path, value) in substitution {
|
||||
let [PathPart::Ident(name)] = path.parts.as_slice() else {
|
||||
continue 'arm;
|
||||
};
|
||||
env.insert(*name, Some(value));
|
||||
}
|
||||
return expr.interpret(&mut env);
|
||||
}
|
||||
}
|
||||
Err(Error::MatchNonexhaustive)
|
||||
}
|
||||
}
|
||||
|
||||
mod assignment {
|
||||
/// Pattern matching engine for assignment
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
type Namespace = HashMap<Sym, Option<ConValue>>;
|
||||
|
||||
/// Gets the path variables in the given Pattern
|
||||
pub fn pattern_variables(pat: &Pattern) -> Vec<&Path> {
|
||||
fn patvars<'p>(set: &mut Vec<&'p Path>, pat: &'p Pattern) {
|
||||
match pat {
|
||||
Pattern::Path(path) if path.is_sinkhole() => {}
|
||||
Pattern::Path(path) => set.push(path),
|
||||
Pattern::Literal(_) => {}
|
||||
Pattern::Ref(_, pattern) => patvars(set, pattern),
|
||||
Pattern::Tuple(patterns) | Pattern::Array(patterns) => {
|
||||
patterns.iter().for_each(|pat| patvars(set, pat))
|
||||
}
|
||||
Pattern::Struct(_path, items) => {
|
||||
items.iter().for_each(|(name, pat)| match pat {
|
||||
Some(pat) => patvars(set, pat),
|
||||
None => set.push(name),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut set = Vec::new();
|
||||
patvars(&mut set, pat);
|
||||
set
|
||||
}
|
||||
|
||||
/// Appends a substitution to the provided table
|
||||
pub fn append_sub<'pat>(
|
||||
env: &mut HashMap<&'pat Path, ConValue>,
|
||||
pat: &'pat Pattern,
|
||||
value: ConValue,
|
||||
) -> IResult<()> {
|
||||
match pat {
|
||||
Pattern::Path(path) if path.is_sinkhole() => Ok(()),
|
||||
Pattern::Path(path) => {
|
||||
env.insert(path, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Pattern::Literal(literal) => match (literal, value) {
|
||||
(Literal::Bool(a), ConValue::Bool(b)) => *a == b,
|
||||
(Literal::Char(a), ConValue::Char(b)) => *a == b,
|
||||
(Literal::Int(a), ConValue::Int(b)) => *a as isize == b,
|
||||
(Literal::Float(a), ConValue::Float(b)) => f64::from_bits(*a) == b,
|
||||
(Literal::String(a), ConValue::String(b)) => *a == *b,
|
||||
_ => false,
|
||||
}
|
||||
.then_some(())
|
||||
.ok_or(Error::NotAssignable),
|
||||
|
||||
Pattern::Ref(_, pattern) => match value {
|
||||
ConValue::Ref(value) => append_sub(env, pattern, Rc::unwrap_or_clone(value)),
|
||||
_ => Err(Error::NotAssignable),
|
||||
},
|
||||
|
||||
Pattern::Tuple(patterns) => match value {
|
||||
ConValue::Tuple(values) => {
|
||||
if patterns.len() != values.len() {
|
||||
return Err(Error::OobIndex(patterns.len(), values.len()));
|
||||
};
|
||||
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
|
||||
append_sub(env, pat, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::NotAssignable),
|
||||
},
|
||||
|
||||
Pattern::Array(patterns) => match value {
|
||||
ConValue::Array(values) => {
|
||||
if patterns.len() != values.len() {
|
||||
return Err(Error::OobIndex(patterns.len(), values.len()));
|
||||
};
|
||||
for (pat, value) in patterns.iter().zip(Vec::from(values).into_iter()) {
|
||||
append_sub(env, pat, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::NotAssignable),
|
||||
},
|
||||
|
||||
Pattern::Struct(_path, patterns) => {
|
||||
let ConValue::Struct(parts) = value else {
|
||||
return Err(Error::TypeError);
|
||||
};
|
||||
let (_, mut values) = *parts;
|
||||
if values.len() != patterns.len() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
for (name, pat) in patterns {
|
||||
let [.., PathPart::Ident(index)] = name.parts.as_slice() else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
let value = values.remove(index).ok_or(Error::TypeError)?;
|
||||
match pat {
|
||||
Some(pat) => append_sub(env, pat, value)?,
|
||||
None => {
|
||||
env.insert(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a substitution from a pattern and a value
|
||||
pub fn pattern_substitution(
|
||||
pat: &Pattern,
|
||||
value: ConValue,
|
||||
) -> IResult<HashMap<&Path, ConValue>> {
|
||||
let mut substitution = HashMap::new();
|
||||
append_sub(&mut substitution, pat, value)?;
|
||||
Ok(substitution)
|
||||
}
|
||||
|
||||
pub(super) fn pat_assign(env: &mut Environment, pat: &Pattern, value: ConValue) -> IResult<()> {
|
||||
let mut substitution = HashMap::new();
|
||||
append_sub(&mut substitution, pat, value)
|
||||
.map_err(|_| Error::PatFailed(pat.clone().into()))?;
|
||||
for (path, value) in substitution {
|
||||
assign_path(env, path, value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn assign(env: &mut Environment, pat: &ExprKind, value: ConValue) -> IResult<()> {
|
||||
if let Ok(pat) = Pattern::try_from(pat.clone()) {
|
||||
return pat_assign(env, &pat, value);
|
||||
}
|
||||
match pat {
|
||||
ExprKind::Member(member) => *addrof_member(env, member)? = value,
|
||||
ExprKind::Index(index) => *addrof_index(env, index)? = value,
|
||||
_ => Err(Error::NotAssignable)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assign_path(env: &mut Environment, path: &Path, value: ConValue) -> IResult<()> {
|
||||
let Ok(addr) = addrof_path(env, &path.parts) else {
|
||||
eprintln!("Cannot assign {value} to path {path}");
|
||||
return Err(Error::NotAssignable);
|
||||
};
|
||||
*addr = Some(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn addrof<'e>(
|
||||
env: &'e mut Environment,
|
||||
pat: &ExprKind,
|
||||
) -> IResult<&'e mut ConValue> {
|
||||
match pat {
|
||||
ExprKind::Path(path) => addrof_path(env, &path.parts)?
|
||||
.as_mut()
|
||||
.ok_or(Error::NotInitialized("".into())),
|
||||
ExprKind::Member(member) => addrof_member(env, member),
|
||||
ExprKind::Index(index) => addrof_index(env, index),
|
||||
ExprKind::Group(Group { expr }) => addrof(env, expr),
|
||||
ExprKind::AddrOf(AddrOf { mutable: Mutability::Mut, expr }) => addrof(env, expr),
|
||||
_ => Err(Error::TypeError),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addrof_path<'e>(
|
||||
env: &'e mut Environment,
|
||||
path: &[PathPart],
|
||||
) -> IResult<&'e mut Option<ConValue>> {
|
||||
match path {
|
||||
[PathPart::Ident(name)] => env.get_mut(*name),
|
||||
[PathPart::Ident(name), rest @ ..] => match env.get_mut(*name)? {
|
||||
Some(ConValue::Module(env)) => addrof_path_within_namespace(env, rest),
|
||||
_ => Err(Error::NotIndexable),
|
||||
},
|
||||
_ => Err(Error::NotAssignable),
|
||||
}
|
||||
}
|
||||
|
||||
fn addrof_member<'e>(env: &'e mut Environment, member: &Member) -> IResult<&'e mut ConValue> {
|
||||
let Member { head, kind } = member;
|
||||
let ExprKind::Path(path) = head.as_ref() else {
|
||||
return Err(Error::TypeError);
|
||||
};
|
||||
let slot = addrof_path(env, &path.parts)?
|
||||
.as_mut()
|
||||
.ok_or(Error::NotAssignable)?;
|
||||
Ok(match (slot, kind) {
|
||||
(ConValue::Struct(s), MemberKind::Struct(id)) => {
|
||||
s.1.get_mut(id).ok_or(Error::NotDefined(*id))?
|
||||
}
|
||||
(ConValue::Tuple(t), MemberKind::Tuple(Literal::Int(id))) => t
|
||||
.get_mut(*id as usize)
|
||||
.ok_or_else(|| Error::NotDefined(id.to_string().into()))?,
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn addrof_index<'e>(env: &'e mut Environment, index: &Index) -> IResult<&'e mut ConValue> {
|
||||
let Index { head, indices } = index;
|
||||
let indices = indices
|
||||
.iter()
|
||||
.map(|index| index.interpret(env))
|
||||
.collect::<IResult<Vec<_>>>()?;
|
||||
let mut head = addrof(env, head)?;
|
||||
for index in indices {
|
||||
head = match (head, index) {
|
||||
(ConValue::Array(a), ConValue::Int(i)) => {
|
||||
let a_len = a.len();
|
||||
a.get_mut(i as usize)
|
||||
.ok_or(Error::OobIndex(i as usize, a_len))?
|
||||
}
|
||||
_ => Err(Error::NotIndexable)?,
|
||||
}
|
||||
}
|
||||
Ok(head)
|
||||
}
|
||||
|
||||
pub fn addrof_path_within_namespace<'e>(
|
||||
env: &'e mut Namespace,
|
||||
path: &[PathPart],
|
||||
) -> IResult<&'e mut Option<ConValue>> {
|
||||
match path {
|
||||
[] => Err(Error::NotAssignable),
|
||||
[PathPart::Ident(name)] => env.get_mut(name).ok_or(Error::NotDefined(*name)),
|
||||
[PathPart::Ident(name), rest @ ..] => {
|
||||
match env.get_mut(name).ok_or(Error::NotDefined(*name))? {
|
||||
Some(ConValue::Module(env)) => addrof_path_within_namespace(env, rest),
|
||||
_ => Err(Error::NotIndexable),
|
||||
}
|
||||
}
|
||||
[PathPart::SelfKw, rest @ ..] => addrof_path_within_namespace(env, rest),
|
||||
[PathPart::SelfTy, ..] => todo!("calc_address for `Self`"),
|
||||
[PathPart::SuperKw, ..] => todo!("calc_address for `super`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,17 +533,7 @@ impl Interpret for Assign {
|
||||
let (head, tail) = parts.borrow();
|
||||
let init = tail.interpret(env)?;
|
||||
// Resolve the head pattern
|
||||
let target = evaluate_place_expr(env, head)?;
|
||||
use std::mem::discriminant as variant;
|
||||
// runtime typecheck
|
||||
match target.0 {
|
||||
Some(value) if variant(value) == variant(&init) => {
|
||||
*value = init;
|
||||
}
|
||||
value @ None => *value = Some(init),
|
||||
_ => Err(Error::TypeError)?,
|
||||
}
|
||||
Ok(ConValue::Empty)
|
||||
assignment::assign(env, head, init).map(|_| ConValue::Empty)
|
||||
}
|
||||
}
|
||||
impl Interpret for Modify {
|
||||
@@ -208,10 +543,7 @@ impl Interpret for Modify {
|
||||
// Get the initializer and the tail
|
||||
let init = tail.interpret(env)?;
|
||||
// Resolve the head pattern
|
||||
let target = evaluate_place_expr(env, head)?;
|
||||
let (Some(target), _) = target else {
|
||||
return Err(Error::NotInitialized(target.1));
|
||||
};
|
||||
let target = assignment::addrof(env, head)?;
|
||||
|
||||
match op {
|
||||
ModifyKind::Add => target.add_assign(init),
|
||||
@@ -321,12 +653,28 @@ impl Interpret for Binary {
|
||||
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".into(), &[operand]),
|
||||
UnaryKind::Neg => env.call("neg".into(), &[operand]),
|
||||
UnaryKind::Not => env.call("not".into(), &[operand]),
|
||||
UnaryKind::Loop => loop {
|
||||
match tail.interpret(env) {
|
||||
Err(Error::Break(value)) => return Ok(value),
|
||||
Err(Error::Continue) => continue,
|
||||
e => e?,
|
||||
};
|
||||
},
|
||||
UnaryKind::Deref => {
|
||||
let operand = tail.interpret(env)?;
|
||||
env.call("deref".into(), &[operand])
|
||||
}
|
||||
UnaryKind::Neg => {
|
||||
let operand = tail.interpret(env)?;
|
||||
env.call("neg".into(), &[operand])
|
||||
}
|
||||
UnaryKind::Not => {
|
||||
let operand = tail.interpret(env)?;
|
||||
env.call("not".into(), &[operand])
|
||||
}
|
||||
UnaryKind::At => {
|
||||
let operand = tail.interpret(env)?;
|
||||
println!("{operand}");
|
||||
Ok(operand)
|
||||
}
|
||||
@@ -334,6 +682,53 @@ impl Interpret for Unary {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cast(value: ConValue, ty: Sym) -> IResult<ConValue> {
|
||||
let value = match value {
|
||||
ConValue::Empty => 0,
|
||||
ConValue::Int(i) => i as _,
|
||||
ConValue::Bool(b) => b as _,
|
||||
ConValue::Char(c) => c as _,
|
||||
ConValue::Ref(v) => return cast((*v).clone(), ty),
|
||||
// TODO: This, better
|
||||
ConValue::Float(_) if ty.starts_with('f') => return Ok(value),
|
||||
ConValue::Float(f) => f as _,
|
||||
_ => Err(Error::TypeError)?,
|
||||
};
|
||||
Ok(match &*ty {
|
||||
"u8" => ConValue::Int(value as u8 as _),
|
||||
"i8" => ConValue::Int(value as i8 as _),
|
||||
"u16" => ConValue::Int(value as u16 as _),
|
||||
"i16" => ConValue::Int(value as i16 as _),
|
||||
"u32" => ConValue::Int(value as u32 as _),
|
||||
"i32" => ConValue::Int(value as i32 as _),
|
||||
"u64" => ConValue::Int(value),
|
||||
"i64" => ConValue::Int(value),
|
||||
"f32" => ConValue::Float(value as f32 as _),
|
||||
"f64" => ConValue::Float(value as f64 as _),
|
||||
"char" => ConValue::Char(char::from_u32(value as _).unwrap_or('\u{fffd}')),
|
||||
"bool" => ConValue::Bool(value < 0),
|
||||
_ => Err(Error::NotDefined(ty))?,
|
||||
})
|
||||
}
|
||||
|
||||
impl Interpret for Cast {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Cast { head, ty } = self;
|
||||
let value = head.interpret(env)?;
|
||||
if TyKind::Empty == ty.kind {
|
||||
return Ok(ConValue::Empty);
|
||||
};
|
||||
let TyKind::Path(Path { absolute: false, parts }) = &ty.kind else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
match parts.as_slice() {
|
||||
[PathPart::Ident(ty)] => cast(value, *ty),
|
||||
_ => Err(Error::TypeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret for Member {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Member { head, kind } = self;
|
||||
@@ -343,6 +738,20 @@ impl Interpret for Member {
|
||||
.get(*id as usize)
|
||||
.cloned()
|
||||
.ok_or(Error::OobIndex(*id as usize, v.len())),
|
||||
(ConValue::Struct(parts), MemberKind::Struct(name)) => {
|
||||
parts.1.get(name).cloned().ok_or(Error::NotDefined(*name))
|
||||
}
|
||||
(ConValue::Struct(parts), MemberKind::Call(name, args)) => {
|
||||
let mut values = vec![];
|
||||
for arg in &args.exprs {
|
||||
values.push(arg.interpret(env)?);
|
||||
}
|
||||
(parts.1)
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or(Error::NotDefined(*name))?
|
||||
.call(env, &values)
|
||||
}
|
||||
(head, MemberKind::Call(name, args)) => {
|
||||
let mut values = vec![head];
|
||||
for arg in &args.exprs {
|
||||
@@ -366,22 +775,37 @@ impl Interpret for Index {
|
||||
}
|
||||
impl Interpret for Structor {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
todo!("struct construction in {env}")
|
||||
let Self { to: Path { absolute: _, parts }, init } = self;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let name = match parts.last() {
|
||||
Some(PathPart::Ident(name)) => *name,
|
||||
Some(PathPart::SelfKw) => "self".into(),
|
||||
Some(PathPart::SelfTy) => "Self".into(),
|
||||
Some(PathPart::SuperKw) => "super".into(),
|
||||
None => "".into(),
|
||||
};
|
||||
|
||||
let mut map = HashMap::new();
|
||||
for Fielder { name, init } in init {
|
||||
let value = match init {
|
||||
Some(init) => init.interpret(env)?,
|
||||
None => env.get(*name)?,
|
||||
};
|
||||
map.insert(*name, value);
|
||||
}
|
||||
Ok(ConValue::Struct(Box::new((name, map))))
|
||||
}
|
||||
}
|
||||
|
||||
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::SelfTy => todo!("Path navigation to Self"),
|
||||
PathPart::Ident(name) => env.get(*name),
|
||||
}
|
||||
} else {
|
||||
todo!("Path navigation!")
|
||||
}
|
||||
assignment::addrof_path(env, parts)
|
||||
.cloned()
|
||||
.transpose()
|
||||
.ok_or_else(|| Error::NotInitialized(format!("{self}").into()))?
|
||||
}
|
||||
}
|
||||
impl Interpret for Literal {
|
||||
@@ -390,7 +814,7 @@ impl Interpret for Literal {
|
||||
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::Float(value) => ConValue::Float(f64::from_bits(*value)),
|
||||
Literal::Int(value) => ConValue::Int(*value as _),
|
||||
})
|
||||
}
|
||||
@@ -418,13 +842,9 @@ impl Interpret for ArrayRep {
|
||||
}
|
||||
impl Interpret for AddrOf {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { count: _, mutable: _, expr } = self;
|
||||
let Self { mutable: _, expr } = self;
|
||||
match expr.as_ref() {
|
||||
ExprKind::Index(_) => todo!("AddrOf array index"),
|
||||
// ExprKind::Path(Path { absolute: false, parts }) => match parts.as_slice() {
|
||||
// [PathPart::Ident(id)] => env.get_ref(id),
|
||||
// _ => todo!("Path traversal in addrof"),
|
||||
// },
|
||||
ExprKind::Path(_) => todo!("Path traversal in addrof"),
|
||||
_ => Ok(ConValue::Ref(Rc::new(expr.interpret(env)?))),
|
||||
}
|
||||
@@ -461,18 +881,6 @@ impl Interpret for Tuple {
|
||||
))
|
||||
}
|
||||
}
|
||||
impl Interpret for Loop {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { body } = self;
|
||||
loop {
|
||||
match body.interpret(env) {
|
||||
Err(Error::Break(value)) => return Ok(value),
|
||||
Err(Error::Continue) => continue,
|
||||
e => e?,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Interpret for While {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { cond, pass, fail } = self;
|
||||
@@ -502,16 +910,19 @@ impl Interpret for If {
|
||||
impl Interpret for For {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Self { bind: name, cond, pass, fail } = self;
|
||||
let cond = cond.interpret(env)?;
|
||||
// TODO: A better iterator model
|
||||
let mut bounds = match cond.interpret(env)? {
|
||||
ConValue::RangeExc(a, b) => a..=b,
|
||||
ConValue::RangeInc(a, b) => a..=b,
|
||||
let mut bounds: Box<dyn Iterator<Item = ConValue>> = match &cond {
|
||||
&ConValue::RangeExc(a, b) => Box::new((a..b).map(ConValue::Int)),
|
||||
&ConValue::RangeInc(a, b) => Box::new((a..=b).map(ConValue::Int)),
|
||||
ConValue::Array(a) => Box::new(a.iter().cloned()),
|
||||
ConValue::String(s) => Box::new(s.chars().map(ConValue::Char)),
|
||||
_ => Err(Error::TypeError)?,
|
||||
};
|
||||
loop {
|
||||
let mut env = env.frame("loop variable");
|
||||
if let Some(loop_var) = bounds.next() {
|
||||
env.insert(*name, Some(loop_var.into()));
|
||||
env.insert(*name, Some(loop_var));
|
||||
match pass.interpret(&mut env) {
|
||||
Err(Error::Break(value)) => return Ok(value),
|
||||
Err(Error::Continue) => continue,
|
||||
@@ -532,11 +943,6 @@ impl Interpret for Else {
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -17,616 +17,17 @@ pub trait Callable: std::fmt::Debug {
|
||||
fn name(&self) -> Sym;
|
||||
}
|
||||
|
||||
/// [BuiltIn]s are [Callable]s with bespoke definitions
|
||||
pub trait BuiltIn: std::fmt::Debug + Callable {
|
||||
fn description(&self) -> &str;
|
||||
}
|
||||
|
||||
pub mod convalue {
|
||||
//! Values in the dynamically typed AST interpreter.
|
||||
//!
|
||||
//! The most permanent fix is a temporary one.
|
||||
use cl_ast::Sym;
|
||||
|
||||
use super::{
|
||||
error::{Error, IResult},
|
||||
function::Function,
|
||||
BuiltIn, Callable, Environment,
|
||||
};
|
||||
use std::{ops::*, rc::Rc};
|
||||
|
||||
type Integer = isize;
|
||||
|
||||
/// A Conlang value stores data in the interpreter
|
||||
#[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(Sym),
|
||||
/// A reference
|
||||
Ref(Rc<ConValue>),
|
||||
/// An Array
|
||||
Array(Rc<[ConValue]>),
|
||||
/// A tuple
|
||||
Tuple(Rc<[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) -> Sym {
|
||||
match self {
|
||||
ConValue::Function(func) => func.name(),
|
||||
ConValue::BuiltIn(func) => func.name(),
|
||||
_ => "".into(),
|
||||
}
|
||||
}
|
||||
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()) }
|
||||
})*
|
||||
}
|
||||
impl From<&Sym> for ConValue {
|
||||
fn from(value: &Sym) -> Self {
|
||||
ConValue::String(*value)
|
||||
}
|
||||
}
|
||||
from! {
|
||||
Integer => ConValue::Int,
|
||||
bool => ConValue::Bool,
|
||||
char => ConValue::Char,
|
||||
Sym => ConValue::String,
|
||||
&str => ConValue::String,
|
||||
String => ConValue::String,
|
||||
Rc<str> => 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.wrapping_add(b)),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
|
||||
(ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(c); s.into() }
|
||||
(ConValue::Char(a), ConValue::Char(b)) => {
|
||||
ConValue::String([a, b].into_iter().collect::<String>().into())
|
||||
}
|
||||
_ => 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.checked_div(b).unwrap_or_else(|| {
|
||||
eprintln!("Warning: Divide by zero in {a} / {b}"); a
|
||||
})),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Mul: mul = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_mul(b)),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Rem: rem = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.checked_rem(b).unwrap_or_else(|| {
|
||||
eprintln!("Warning: Divide by zero in {a} % {b}"); a
|
||||
})),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Shl: shl = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shl(b as _)),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Shr: shr = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_shr(b as _)),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
Sub: sub = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a.wrapping_sub(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::Ref(v) => write!(f, "&{v}"),
|
||||
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) => {
|
||||
write!(f, "{}", func.decl())
|
||||
}
|
||||
ConValue::BuiltIn(func) => {
|
||||
write!(f, "{}", func.description())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod convalue;
|
||||
|
||||
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, Param, Sym};
|
||||
use std::rc::Rc;
|
||||
/// Represents a block of code which persists inside the Interpreter
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Function {
|
||||
/// Stores the contents of the function declaration
|
||||
decl: Rc<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) -> Sym {
|
||||
let FnDecl { name, .. } = *self.decl;
|
||||
name
|
||||
}
|
||||
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
let FnDecl { name, bind, body, sign: _ } = &*self.decl;
|
||||
// Check arg mapping
|
||||
if args.len() != bind.len() {
|
||||
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
|
||||
}
|
||||
let Some(body) = body else {
|
||||
return Err(Error::NotDefined(*name));
|
||||
};
|
||||
// TODO: completely refactor data storage
|
||||
let mut frame = env.frame("fn args");
|
||||
for (Param { mutability: _, name }, value) in bind.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 function;
|
||||
|
||||
pub mod builtin;
|
||||
|
||||
pub mod env {
|
||||
//! Lexical and non-lexical scoping for variables
|
||||
use super::{
|
||||
builtin::{BINARY, MISC, RANGE, UNARY},
|
||||
convalue::ConValue,
|
||||
error::{Error, IResult},
|
||||
function::Function,
|
||||
BuiltIn, Callable, Interpret,
|
||||
};
|
||||
use cl_ast::{Function as FnDecl, Sym};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
pub mod env;
|
||||
|
||||
type StackFrame = HashMap<Sym, Option<ConValue>>;
|
||||
|
||||
/// Implements a nested lexical scope
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Environment {
|
||||
frames: Vec<(StackFrame, &'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<Sym, Option<ConValue>> {
|
||||
from.iter().map(|&v| (v.name(), 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: Sym, 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: Sym) -> 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))
|
||||
}
|
||||
/// Resolves a variable immutably.
|
||||
///
|
||||
/// Returns a reference to the variable's contents, if it is defined and initialized.
|
||||
pub fn get(&self, id: Sym) -> IResult<ConValue> {
|
||||
for (frame, _) in self.frames.iter().rev() {
|
||||
match frame.get(&id) {
|
||||
Some(Some(var)) => return Ok(var.clone()),
|
||||
Some(None) => return Err(Error::NotInitialized(id)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Err(Error::NotDefined(id))
|
||||
}
|
||||
/// Inserts a new [ConValue] into this [Environment]
|
||||
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
|
||||
if let Some((frame, _)) = self.frames.last_mut() {
|
||||
frame.insert(id, value);
|
||||
}
|
||||
}
|
||||
/// A convenience function for registering a [FnDecl] as a [Function]
|
||||
pub fn insert_fn(&mut self, decl: &FnDecl) {
|
||||
let FnDecl { name, .. } = decl;
|
||||
let (name, function) = (name, 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 cl_ast::Sym;
|
||||
|
||||
use super::convalue::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(Sym),
|
||||
/// A name was defined but not initialized
|
||||
NotInitialized(Sym),
|
||||
/// 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,
|
||||
},
|
||||
Outlined(Sym),
|
||||
}
|
||||
|
||||
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::Outlined(name) => {
|
||||
write!(f, "Module {name} specified, but not imported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![allow(unused_imports)]
|
||||
use crate::{env::Environment, convalue::ConValue, Interpret};
|
||||
use crate::{convalue::ConValue, env::Environment, Interpret};
|
||||
use cl_ast::*;
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
@@ -48,6 +48,7 @@ mod macros {
|
||||
//! ```
|
||||
#![allow(unused_macros)]
|
||||
use crate::IResult;
|
||||
use cl_parser::parser::Parse;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -63,14 +64,14 @@ mod macros {
|
||||
///
|
||||
/// Returns a `Result<`[`File`]`, ParseError>`
|
||||
pub macro file($($t:tt)*) {
|
||||
Parser::new(Lexer::new(stringify!( $($t)* ))).file()
|
||||
File::parse(&mut Parser::new(Lexer::new(stringify!( $($t)* ))))
|
||||
}
|
||||
|
||||
/// Stringifies, lexes, and parses everything you give to it
|
||||
///
|
||||
/// Returns a `Result<`[`Block`]`, ParseError>`
|
||||
pub macro block($($t:tt)*) {
|
||||
Parser::new(Lexer::new(stringify!({ $($t)* }))).block()
|
||||
Block::parse(&mut Parser::new(Lexer::new(stringify!({ $($t)* }))))
|
||||
}
|
||||
|
||||
/// Evaluates a block of code in the given environment
|
||||
@@ -177,6 +178,45 @@ mod let_declarations {
|
||||
env_eq!(env.x, 10);
|
||||
env_eq!(env.y, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_destructuring_tuple() {
|
||||
let mut env = Environment::new();
|
||||
assert_eval!(env,
|
||||
let (x, y) = (10, 20);
|
||||
);
|
||||
|
||||
env_eq!(env.x, 10);
|
||||
env_eq!(env.y, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_destructuring_array() {
|
||||
let mut env = Environment::new();
|
||||
assert_eval!(env,
|
||||
let [x, y] = [10, 20];
|
||||
);
|
||||
|
||||
env_eq!(env.x, 10);
|
||||
env_eq!(env.y, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_destructuring_nested() {
|
||||
let mut env = Environment::new();
|
||||
assert_eval!(env,
|
||||
let (x, [one, two, three], (a, b, c))
|
||||
= ('x', [1, 2, 3], ('a', 'b', 'c'));
|
||||
);
|
||||
|
||||
env_eq!(env.x, 'x');
|
||||
env_eq!(env.one, 1);
|
||||
env_eq!(env.two, 2);
|
||||
env_eq!(env.three, 3);
|
||||
env_eq!(env.a, 'a');
|
||||
env_eq!(env.b, 'b');
|
||||
env_eq!(env.c, 'c');
|
||||
}
|
||||
}
|
||||
|
||||
mod fn_declarations {
|
||||
@@ -187,7 +227,7 @@ mod fn_declarations {
|
||||
assert_eval!(env, fn empty_fn() {});
|
||||
// TODO: true equality for functions
|
||||
assert_eq!(
|
||||
"fn empty_fn () {\n \n}",
|
||||
"fn empty_fn () {}",
|
||||
format!(
|
||||
"{}",
|
||||
env.get("empty_fn".into())
|
||||
@@ -436,16 +476,17 @@ mod operators {
|
||||
env_eq!(env.y, 10);
|
||||
env_eq!(env.z, 10);
|
||||
}
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assignment_accounts_for_type() {
|
||||
let mut env = Default::default();
|
||||
assert_eval!(env,
|
||||
let x = "a string";
|
||||
let y = 0xdeadbeef;
|
||||
y = x; // should crash: type error
|
||||
);
|
||||
}
|
||||
// Test is disabled, since new assignment system intentionally does not care.
|
||||
// #[test]
|
||||
// #[should_panic]
|
||||
// fn assignment_accounts_for_type() {
|
||||
// let mut env = Default::default();
|
||||
// assert_eval!(env,
|
||||
// let x = "a string";
|
||||
// let y = 0xdeadbeef;
|
||||
// y = x; // should crash: type error
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn precedence() {
|
||||
let mut env = Default::default();
|
||||
@@ -468,6 +509,56 @@ mod operators {
|
||||
}
|
||||
}
|
||||
|
||||
mod control_flow {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn if_evaluates_pass_block_on_true() {
|
||||
let mut env = Default::default();
|
||||
assert_eval!(env,
|
||||
let evaluated = if true { "pass" } else { "fail" }
|
||||
);
|
||||
env_eq!(env.evaluated, "pass");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_evaluates_fail_block_on_false() {
|
||||
let mut env = Default::default();
|
||||
assert_eval!(env,
|
||||
let evaluated = if false { "pass" } else { "fail" }
|
||||
);
|
||||
env_eq!(env.evaluated, "fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_evaluates_in_order() {
|
||||
let mut env = Default::default();
|
||||
assert_eval!(env,
|
||||
let x = '\u{1f988}';
|
||||
let passed = match x {
|
||||
'\u{1f988}' => true,
|
||||
_ => false,
|
||||
};
|
||||
);
|
||||
env_eq!(env.passed, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_sinkoles_underscore_patterns() {
|
||||
let mut env = Default::default();
|
||||
assert_eval!(env,
|
||||
let x = '\u{1f988}';
|
||||
let passed = match x {
|
||||
_ => true,
|
||||
'\u{1f988}' => false,
|
||||
};
|
||||
);
|
||||
env_eq!(env.passed, true);
|
||||
}
|
||||
|
||||
//TODO: test other control flow constructs like loops, while-else, etc.
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn test_template() {
|
||||
let mut env = Default::default();
|
||||
|
||||
@@ -23,7 +23,7 @@ pub mod lexer_iter {
|
||||
pub struct LexerIter<'t> {
|
||||
lexer: Lexer<'t>,
|
||||
}
|
||||
impl<'t> Iterator for LexerIter<'t> {
|
||||
impl Iterator for LexerIter<'_> {
|
||||
type Item = LResult<Token>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.lexer.scan() {
|
||||
@@ -97,33 +97,33 @@ 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_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()?.produce_op(Kind::LCurly),
|
||||
'}' => self.consume()?.produce_op(Kind::RCurly),
|
||||
'[' => self.consume()?.produce_op(Kind::LBrack),
|
||||
']' => self.consume()?.produce_op(Kind::RBrack),
|
||||
'(' => self.consume()?.produce_op(Kind::LParen),
|
||||
')' => self.consume()?.produce_op(Kind::RParen),
|
||||
'&' => self.consume()?.amp(),
|
||||
'@' => self.consume()?.produce_op(Punct::At),
|
||||
'\\' => self.consume()?.produce_op(Punct::Backslash),
|
||||
'@' => self.consume()?.produce_op(Kind::At),
|
||||
'\\' => self.consume()?.produce_op(Kind::Backslash),
|
||||
'!' => self.consume()?.bang(),
|
||||
'|' => self.consume()?.bar(),
|
||||
':' => self.consume()?.colon(),
|
||||
',' => self.consume()?.produce_op(Punct::Comma),
|
||||
',' => self.consume()?.produce_op(Kind::Comma),
|
||||
'.' => self.consume()?.dot(),
|
||||
'=' => self.consume()?.equal(),
|
||||
'`' => self.consume()?.produce_op(Punct::Grave),
|
||||
'`' => self.consume()?.produce_op(Kind::Grave),
|
||||
'>' => self.consume()?.greater(),
|
||||
'#' => self.consume()?.hash(),
|
||||
'<' => self.consume()?.less(),
|
||||
'-' => self.consume()?.minus(),
|
||||
'+' => self.consume()?.plus(),
|
||||
'?' => self.consume()?.produce_op(Punct::Question),
|
||||
'?' => self.consume()?.produce_op(Kind::Question),
|
||||
'%' => self.consume()?.rem(),
|
||||
';' => self.consume()?.produce_op(Punct::Semi),
|
||||
';' => self.consume()?.produce_op(Kind::Semi),
|
||||
'/' => self.consume()?.slash(),
|
||||
'*' => self.consume()?.star(),
|
||||
'~' => self.consume()?.produce_op(Punct::Tilde),
|
||||
'~' => self.consume()?.produce_op(Kind::Tilde),
|
||||
'^' => self.consume()?.xor(),
|
||||
'0' => self.consume()?.int_with_base(),
|
||||
'1'..='9' => self.digits::<10>(),
|
||||
@@ -157,14 +157,14 @@ impl<'t> Lexer<'t> {
|
||||
.copied()
|
||||
.ok_or(Error::end_of_file(self.line(), self.col()))
|
||||
}
|
||||
fn produce(&mut self, kind: TokenKind, data: impl Into<TokenData>) -> LResult<Token> {
|
||||
fn produce(&mut self, kind: Kind, data: impl Into<TokenData>) -> LResult<Token> {
|
||||
let loc = self.start_loc;
|
||||
self.start_loc = self.current_loc;
|
||||
self.start = self.current;
|
||||
Ok(Token::new(kind, data, loc.0, loc.1))
|
||||
}
|
||||
fn produce_op(&mut self, kind: Punct) -> LResult<Token> {
|
||||
self.produce(TokenKind::Punct(kind), ())
|
||||
fn produce_op(&mut self, kind: Kind) -> LResult<Token> {
|
||||
self.produce(kind, ())
|
||||
}
|
||||
fn skip_whitespace(&mut self) -> &mut Self {
|
||||
while let Ok(c) = self.peek() {
|
||||
@@ -192,145 +192,154 @@ impl<'t> Lexer<'t> {
|
||||
}
|
||||
}
|
||||
/// Digraphs and trigraphs
|
||||
impl<'t> Lexer<'t> {
|
||||
impl Lexer<'_> {
|
||||
fn amp(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('&') => self.consume()?.produce_op(Punct::AmpAmp),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::AmpEq),
|
||||
_ => self.produce_op(Punct::Amp),
|
||||
Ok('&') => self.consume()?.produce_op(Kind::AmpAmp),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::AmpEq),
|
||||
_ => self.produce_op(Kind::Amp),
|
||||
}
|
||||
}
|
||||
fn bang(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('!') => self.consume()?.produce_op(Punct::BangBang),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::BangEq),
|
||||
_ => self.produce_op(Punct::Bang),
|
||||
Ok('!') => self.consume()?.produce_op(Kind::BangBang),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::BangEq),
|
||||
_ => self.produce_op(Kind::Bang),
|
||||
}
|
||||
}
|
||||
fn bar(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('|') => self.consume()?.produce_op(Punct::BarBar),
|
||||
Ok('=') => self.consume()?.produce_op(Punct::BarEq),
|
||||
_ => self.produce_op(Punct::Bar),
|
||||
Ok('|') => self.consume()?.produce_op(Kind::BarBar),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::BarEq),
|
||||
_ => self.produce_op(Kind::Bar),
|
||||
}
|
||||
}
|
||||
fn colon(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok(':') => self.consume()?.produce_op(Punct::ColonColon),
|
||||
_ => self.produce_op(Punct::Colon),
|
||||
Ok(':') => self.consume()?.produce_op(Kind::ColonColon),
|
||||
_ => self.produce_op(Kind::Colon),
|
||||
}
|
||||
}
|
||||
fn dot(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('.') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce_op(Punct::DotDotEq)
|
||||
self.consume()?.produce_op(Kind::DotDotEq)
|
||||
} else {
|
||||
self.produce_op(Punct::DotDot)
|
||||
self.produce_op(Kind::DotDot)
|
||||
}
|
||||
}
|
||||
_ => self.produce_op(Punct::Dot),
|
||||
_ => self.produce_op(Kind::Dot),
|
||||
}
|
||||
}
|
||||
fn equal(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::EqEq),
|
||||
Ok('>') => self.consume()?.produce_op(Punct::FatArrow),
|
||||
_ => self.produce_op(Punct::Eq),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::EqEq),
|
||||
Ok('>') => self.consume()?.produce_op(Kind::FatArrow),
|
||||
_ => self.produce_op(Kind::Eq),
|
||||
}
|
||||
}
|
||||
fn greater(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::GtEq),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::GtEq),
|
||||
Ok('>') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce_op(Punct::GtGtEq)
|
||||
self.consume()?.produce_op(Kind::GtGtEq)
|
||||
} else {
|
||||
self.produce_op(Punct::GtGt)
|
||||
self.produce_op(Kind::GtGt)
|
||||
}
|
||||
}
|
||||
_ => self.produce_op(Punct::Gt),
|
||||
_ => self.produce_op(Kind::Gt),
|
||||
}
|
||||
}
|
||||
fn hash(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('!') => self.consume()?.produce_op(Punct::HashBang),
|
||||
_ => self.produce_op(Punct::Hash),
|
||||
Ok('!') => self.consume()?.hashbang(),
|
||||
_ => self.produce_op(Kind::Hash),
|
||||
}
|
||||
}
|
||||
fn hashbang(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('/' | '\'') => self.line_comment(),
|
||||
_ => self.produce_op(Kind::HashBang),
|
||||
}
|
||||
}
|
||||
fn less(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::LtEq),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::LtEq),
|
||||
Ok('<') => {
|
||||
if let Ok('=') = self.consume()?.peek() {
|
||||
self.consume()?.produce_op(Punct::LtLtEq)
|
||||
self.consume()?.produce_op(Kind::LtLtEq)
|
||||
} else {
|
||||
self.produce_op(Punct::LtLt)
|
||||
self.produce_op(Kind::LtLt)
|
||||
}
|
||||
}
|
||||
_ => self.produce_op(Punct::Lt),
|
||||
_ => self.produce_op(Kind::Lt),
|
||||
}
|
||||
}
|
||||
fn minus(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::MinusEq),
|
||||
Ok('>') => self.consume()?.produce_op(Punct::Arrow),
|
||||
_ => self.produce_op(Punct::Minus),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::MinusEq),
|
||||
Ok('>') => self.consume()?.produce_op(Kind::Arrow),
|
||||
_ => self.produce_op(Kind::Minus),
|
||||
}
|
||||
}
|
||||
fn plus(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::PlusEq),
|
||||
_ => self.produce_op(Punct::Plus),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::PlusEq),
|
||||
_ => self.produce_op(Kind::Plus),
|
||||
}
|
||||
}
|
||||
fn rem(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::RemEq),
|
||||
_ => self.produce_op(Punct::Rem),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::RemEq),
|
||||
_ => self.produce_op(Kind::Rem),
|
||||
}
|
||||
}
|
||||
fn slash(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::SlashEq),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::SlashEq),
|
||||
Ok('/') => self.consume()?.line_comment(),
|
||||
Ok('*') => self.consume()?.block_comment(),
|
||||
_ => self.produce_op(Punct::Slash),
|
||||
_ => self.produce_op(Kind::Slash),
|
||||
}
|
||||
}
|
||||
fn star(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::StarEq),
|
||||
_ => self.produce_op(Punct::Star),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::StarEq),
|
||||
_ => self.produce_op(Kind::Star),
|
||||
}
|
||||
}
|
||||
fn xor(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('=') => self.consume()?.produce_op(Punct::XorEq),
|
||||
Ok('^') => self.consume()?.produce_op(Punct::XorXor),
|
||||
_ => self.produce_op(Punct::Xor),
|
||||
Ok('=') => self.consume()?.produce_op(Kind::XorEq),
|
||||
Ok('^') => self.consume()?.produce_op(Kind::XorXor),
|
||||
_ => self.produce_op(Kind::Xor),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Comments
|
||||
impl<'t> Lexer<'t> {
|
||||
impl Lexer<'_> {
|
||||
fn line_comment(&mut self) -> LResult<Token> {
|
||||
let mut comment = String::new();
|
||||
while Ok('\n') != self.peek() {
|
||||
self.consume()?;
|
||||
comment.push(self.next()?);
|
||||
}
|
||||
self.produce(Kind::Comment, ())
|
||||
self.produce(Kind::Comment, comment)
|
||||
}
|
||||
fn block_comment(&mut self) -> LResult<Token> {
|
||||
let mut comment = String::new();
|
||||
while let Ok(c) = self.next() {
|
||||
if '*' == c && Ok('/') == self.next() {
|
||||
if '*' == c && Ok('/') == self.peek() {
|
||||
break;
|
||||
}
|
||||
comment.push(c);
|
||||
}
|
||||
self.produce(Kind::Comment, ())
|
||||
self.consume()?.produce(Kind::Comment, comment)
|
||||
}
|
||||
}
|
||||
/// Identifiers
|
||||
impl<'t> Lexer<'t> {
|
||||
impl Lexer<'_> {
|
||||
fn identifier(&mut self) -> LResult<Token> {
|
||||
let mut out = String::from(self.xid_start()?);
|
||||
while let Ok(c) = self.xid_continue() {
|
||||
@@ -362,23 +371,39 @@ impl<'t> Lexer<'t> {
|
||||
}
|
||||
}
|
||||
/// Integers
|
||||
impl<'t> Lexer<'t> {
|
||||
impl Lexer<'_> {
|
||||
fn int_with_base(&mut self) -> LResult<Token> {
|
||||
match self.peek() {
|
||||
Ok('x') => self.consume()?.digits::<16>(),
|
||||
Ok('d') => self.consume()?.digits::<10>(),
|
||||
Ok('o') => self.consume()?.digits::<8>(),
|
||||
Ok('b') => self.consume()?.digits::<2>(),
|
||||
Ok('0'..='9') => self.digits::<10>(),
|
||||
Ok('0'..='9' | '.') => self.digits::<10>(),
|
||||
_ => self.produce(Kind::Literal, 0),
|
||||
}
|
||||
}
|
||||
fn digits<const B: u32>(&mut self) -> LResult<Token> {
|
||||
let mut value = self.digit::<B>()? as u128;
|
||||
let mut value = 0;
|
||||
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(Kind::Literal, value)
|
||||
// TODO: find a better way to handle floats in the tokenizer
|
||||
match self.peek() {
|
||||
Ok('.') => {
|
||||
// FIXME: hack: 0.. is not [0.0, '.']
|
||||
if let Ok('.') = self.clone().consume()?.next() {
|
||||
return self.produce(Kind::Literal, value);
|
||||
}
|
||||
let mut float = format!("{value}.");
|
||||
self.consume()?;
|
||||
while let Ok(true) = self.peek().as_ref().map(char::is_ascii_digit) {
|
||||
float.push(self.iter.next().unwrap_or_default());
|
||||
}
|
||||
let float = f64::from_str(&float).expect("must be parsable as float");
|
||||
self.produce(Kind::Literal, float)
|
||||
}
|
||||
_ => self.produce(Kind::Literal, value),
|
||||
}
|
||||
}
|
||||
fn digit<const B: u32>(&mut self) -> LResult<u32> {
|
||||
let digit = self.peek()?;
|
||||
@@ -389,7 +414,7 @@ impl<'t> Lexer<'t> {
|
||||
}
|
||||
}
|
||||
/// Strings and characters
|
||||
impl<'t> Lexer<'t> {
|
||||
impl Lexer<'_> {
|
||||
fn string(&mut self) -> LResult<Token> {
|
||||
let mut value = String::new();
|
||||
while '"'
|
||||
|
||||
@@ -110,7 +110,7 @@ mod string {
|
||||
}
|
||||
mod punct {
|
||||
macro op($op:ident) {
|
||||
TokenKind::Punct(Punct::$op)
|
||||
TokenKind::$op
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -99,6 +99,7 @@ pub enum Parsing {
|
||||
BinaryKind,
|
||||
Unary,
|
||||
UnaryKind,
|
||||
Cast,
|
||||
Index,
|
||||
Structor,
|
||||
Fielder,
|
||||
@@ -118,6 +119,10 @@ pub enum Parsing {
|
||||
Break,
|
||||
Return,
|
||||
Continue,
|
||||
|
||||
Pattern,
|
||||
Match,
|
||||
MatchArm,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
@@ -204,6 +209,7 @@ impl Display for Parsing {
|
||||
Parsing::BinaryKind => "a binary operator",
|
||||
Parsing::Unary => "a unary expression",
|
||||
Parsing::UnaryKind => "a unary operator",
|
||||
Parsing::Cast => "an `as`-casting expression",
|
||||
Parsing::Index => "an indexing expression",
|
||||
Parsing::Structor => "a struct constructor expression",
|
||||
Parsing::Fielder => "a struct field expression",
|
||||
@@ -223,6 +229,10 @@ impl Display for Parsing {
|
||||
Parsing::Break => "a break expression",
|
||||
Parsing::Return => "a return expression",
|
||||
Parsing::Continue => "a continue expression",
|
||||
|
||||
Parsing::Pattern => "a pattern",
|
||||
Parsing::Match => "a match expression",
|
||||
Parsing::MatchArm => "a match arm",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl Fold for ModuleInliner {
|
||||
Ok(file) => file,
|
||||
};
|
||||
|
||||
let kind = match Parser::new(Lexer::new(&file)).file() {
|
||||
let kind = match Parser::new(Lexer::new(&file)).parse() {
|
||||
Err(e) => return self.handle_parse_error(e),
|
||||
Ok(file) => ModuleKind::Inline(file),
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
381
compiler/cl-parser/src/parser/prec.rs
Normal file
381
compiler/cl-parser/src/parser/prec.rs
Normal file
@@ -0,0 +1,381 @@
|
||||
//! Parses an [ExprKind] using a modified pratt parser
|
||||
//!
|
||||
//! See also: [Expr::parse], [ExprKind::parse]
|
||||
//!
|
||||
//! Implementer's note: [ExprKind::parse] is the public API for parsing [ExprKind]s.
|
||||
//! Do not call it from within this function.
|
||||
|
||||
use super::{Parse, *};
|
||||
|
||||
/// Parses an [ExprKind]
|
||||
pub fn exprkind(p: &mut Parser, power: u8) -> PResult<ExprKind> {
|
||||
let parsing = Parsing::ExprKind;
|
||||
|
||||
// Prefix expressions
|
||||
let mut head = match p.peek_kind(Parsing::Unary)? {
|
||||
literal_like!() => Literal::parse(p)?.into(),
|
||||
path_like!() => exprkind_pathlike(p)?,
|
||||
TokenKind::Amp | TokenKind::AmpAmp => AddrOf::parse(p)?.into(),
|
||||
TokenKind::Grave => Quote::parse(p)?.into(),
|
||||
TokenKind::LCurly => Block::parse(p)?.into(),
|
||||
TokenKind::LBrack => exprkind_arraylike(p)?,
|
||||
TokenKind::LParen => exprkind_tuplelike(p)?,
|
||||
TokenKind::Let => Let::parse(p)?.into(),
|
||||
TokenKind::Match => Match::parse(p)?.into(),
|
||||
TokenKind::While => ExprKind::While(While::parse(p)?),
|
||||
TokenKind::If => ExprKind::If(If::parse(p)?),
|
||||
TokenKind::For => ExprKind::For(For::parse(p)?),
|
||||
TokenKind::Break => ExprKind::Break(Break::parse(p)?),
|
||||
TokenKind::Return => ExprKind::Return(Return::parse(p)?),
|
||||
TokenKind::Continue => {
|
||||
p.consume_peeked();
|
||||
ExprKind::Continue
|
||||
}
|
||||
|
||||
op => {
|
||||
let (kind, prec) = from_prefix(op).ok_or_else(|| p.error(Unexpected(op), parsing))?;
|
||||
let ((), after) = prec.prefix().expect("should have a precedence");
|
||||
p.consume_peeked();
|
||||
Unary { kind, tail: exprkind(p, after)?.into() }.into()
|
||||
}
|
||||
};
|
||||
|
||||
fn from_postfix(op: TokenKind) -> Option<Precedence> {
|
||||
Some(match op {
|
||||
TokenKind::LBrack => Precedence::Index,
|
||||
TokenKind::LParen => Precedence::Call,
|
||||
TokenKind::Dot => Precedence::Member,
|
||||
_ => None?,
|
||||
})
|
||||
}
|
||||
|
||||
while let Ok(op) = p.peek_kind(parsing) {
|
||||
// Postfix expressions
|
||||
if let Some((before, ())) = from_postfix(op).and_then(Precedence::postfix) {
|
||||
if before < power {
|
||||
break;
|
||||
}
|
||||
p.consume_peeked();
|
||||
|
||||
head = match op {
|
||||
TokenKind::LBrack => {
|
||||
let indices =
|
||||
sep(Expr::parse, TokenKind::Comma, TokenKind::RBrack, parsing)(p)?;
|
||||
p.match_type(TokenKind::RBrack, parsing)?;
|
||||
ExprKind::Index(Index { head: head.into(), indices })
|
||||
}
|
||||
TokenKind::LParen => {
|
||||
let exprs = sep(Expr::parse, TokenKind::Comma, TokenKind::RParen, parsing)(p)?;
|
||||
p.match_type(TokenKind::RParen, parsing)?;
|
||||
Binary { kind: BinaryKind::Call, parts: (head, Tuple { exprs }.into()).into() }
|
||||
.into()
|
||||
}
|
||||
TokenKind::Dot => {
|
||||
let kind = MemberKind::parse(p)?;
|
||||
Member { head: Box::new(head), kind }.into()
|
||||
}
|
||||
_ => Err(p.error(Unexpected(op), parsing))?,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
// infix expressions
|
||||
if let Some((kind, prec)) = from_infix(op) {
|
||||
let (before, after) = prec.infix().expect("should have a precedence");
|
||||
if before < power {
|
||||
break;
|
||||
}
|
||||
p.consume_peeked();
|
||||
|
||||
let tail = exprkind(p, after)?;
|
||||
head = Binary { kind, parts: (head, tail).into() }.into();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((kind, prec)) = from_modify(op) {
|
||||
let (before, after) = prec.infix().expect("should have a precedence");
|
||||
if before < power {
|
||||
break;
|
||||
}
|
||||
p.consume_peeked();
|
||||
|
||||
let tail = exprkind(p, after)?;
|
||||
head = Modify { kind, parts: (head, tail).into() }.into();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let TokenKind::Eq = op {
|
||||
let (before, after) = Precedence::Assign
|
||||
.infix()
|
||||
.expect("should have a precedence");
|
||||
if before < power {
|
||||
break;
|
||||
}
|
||||
p.consume_peeked();
|
||||
|
||||
let tail = exprkind(p, after)?;
|
||||
head = Assign { parts: (head, tail).into() }.into();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let TokenKind::As = op {
|
||||
let before = Precedence::Cast.level();
|
||||
if before < power {
|
||||
break;
|
||||
}
|
||||
p.consume_peeked();
|
||||
|
||||
let ty = Ty::parse(p)?;
|
||||
head = Cast { head: head.into(), ty }.into();
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(head)
|
||||
}
|
||||
|
||||
/// [Array] = '[' ([Expr] ',')* [Expr]? ']'
|
||||
///
|
||||
/// Array and ArrayRef are ambiguous until the second token,
|
||||
/// so they can't be independent subexpressions
|
||||
fn exprkind_arraylike(p: &mut Parser) -> PResult<ExprKind> {
|
||||
const P: Parsing = Parsing::Array;
|
||||
const START: TokenKind = TokenKind::LBrack;
|
||||
const END: TokenKind = TokenKind::RBrack;
|
||||
|
||||
p.match_type(START, P)?;
|
||||
let out = match p.peek_kind(P)? {
|
||||
END => Array { values: vec![] }.into(),
|
||||
_ => exprkind_array_rep(p)?,
|
||||
};
|
||||
p.match_type(END, P)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// [ArrayRep] = `[` [Expr] `;` [Expr] `]`
|
||||
fn exprkind_array_rep(p: &mut Parser) -> PResult<ExprKind> {
|
||||
const P: Parsing = Parsing::Array;
|
||||
const END: TokenKind = TokenKind::RBrack;
|
||||
|
||||
let first = Expr::parse(p)?;
|
||||
Ok(match p.peek_kind(P)? {
|
||||
TokenKind::Semi => ArrayRep {
|
||||
value: first.kind.into(),
|
||||
repeat: {
|
||||
p.consume_peeked();
|
||||
Box::new(exprkind(p, 0)?)
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
TokenKind::RBrack => Array { values: vec![first] }.into(),
|
||||
TokenKind::Comma => Array {
|
||||
values: {
|
||||
p.consume_peeked();
|
||||
let mut out = vec![first];
|
||||
out.extend(sep(Expr::parse, TokenKind::Comma, END, P)(p)?);
|
||||
out
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
ty => Err(p.error(Unexpected(ty), P))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
|
||||
///
|
||||
/// [ExprKind::Empty] and [Group] are special cases of [Tuple]
|
||||
fn exprkind_tuplelike(p: &mut Parser) -> PResult<ExprKind> {
|
||||
p.match_type(TokenKind::LParen, Parsing::Group)?;
|
||||
let out = match p.peek_kind(Parsing::Group)? {
|
||||
TokenKind::RParen => Ok(ExprKind::Empty),
|
||||
_ => exprkind_group(p),
|
||||
};
|
||||
p.match_type(TokenKind::RParen, Parsing::Group)?;
|
||||
out
|
||||
}
|
||||
|
||||
/// [Group] = `(`([Empty](ExprKind::Empty)|[Expr]|[Tuple])`)`
|
||||
fn exprkind_group(p: &mut Parser) -> PResult<ExprKind> {
|
||||
let first = Expr::parse(p)?;
|
||||
match p.peek_kind(Parsing::Group)? {
|
||||
TokenKind::Comma => {
|
||||
let mut exprs = vec![first];
|
||||
p.consume_peeked();
|
||||
while TokenKind::RParen != p.peek_kind(Parsing::Tuple)? {
|
||||
exprs.push(Expr::parse(p)?);
|
||||
match p.peek_kind(Parsing::Tuple)? {
|
||||
TokenKind::Comma => p.consume_peeked(),
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(Tuple { exprs }.into())
|
||||
}
|
||||
_ => Ok(Group { expr: first.kind.into() }.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an expression beginning with a [Path] (i.e. [Path] or [Structor])
|
||||
fn exprkind_pathlike(p: &mut Parser) -> PResult<ExprKind> {
|
||||
let head = Path::parse(p)?;
|
||||
Ok(match p.match_type(TokenKind::Colon, Parsing::Path) {
|
||||
Ok(_) => ExprKind::Structor(structor_body(p, head)?),
|
||||
Err(_) => ExprKind::Path(head),
|
||||
})
|
||||
}
|
||||
|
||||
/// [Structor]Body = `{` ([Fielder] `,`)* [Fielder]? `}`
|
||||
fn structor_body(p: &mut Parser, to: Path) -> PResult<Structor> {
|
||||
let init = delim(
|
||||
sep(
|
||||
Fielder::parse,
|
||||
TokenKind::Comma,
|
||||
CURLIES.1,
|
||||
Parsing::Structor,
|
||||
),
|
||||
CURLIES,
|
||||
Parsing::Structor,
|
||||
)(p)?;
|
||||
|
||||
Ok(Structor { to, init })
|
||||
}
|
||||
|
||||
/// Precedence provides a total ordering among operators
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Precedence {
|
||||
Assign,
|
||||
Logic,
|
||||
Compare,
|
||||
Range,
|
||||
Bitwise,
|
||||
Shift,
|
||||
Factor,
|
||||
Term,
|
||||
Unary,
|
||||
Index,
|
||||
Cast,
|
||||
Member, // left-associative
|
||||
Call,
|
||||
Highest,
|
||||
}
|
||||
|
||||
impl Precedence {
|
||||
#[inline]
|
||||
pub const fn level(self) -> u8 {
|
||||
(self as u8) << 1
|
||||
}
|
||||
|
||||
pub fn prefix(self) -> Option<((), u8)> {
|
||||
match self {
|
||||
Self::Assign => Some(((), self.level())),
|
||||
Self::Unary => Some(((), self.level())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn infix(self) -> Option<(u8, u8)> {
|
||||
let level = self.level();
|
||||
match self {
|
||||
Self::Unary => None,
|
||||
Self::Assign => Some((level + 1, level)),
|
||||
_ => Some((level, level + 1)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn postfix(self) -> Option<(u8, ())> {
|
||||
match self {
|
||||
Self::Index | Self::Call | Self::Member => Some((self.level(), ())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModifyKind> for Precedence {
|
||||
fn from(_value: ModifyKind) -> Self {
|
||||
Precedence::Assign
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BinaryKind> for Precedence {
|
||||
fn from(value: BinaryKind) -> Self {
|
||||
use BinaryKind as Op;
|
||||
match value {
|
||||
Op::Call => Precedence::Call,
|
||||
Op::Mul | Op::Div | Op::Rem => Precedence::Term,
|
||||
Op::Add | Op::Sub => Precedence::Factor,
|
||||
Op::Shl | Op::Shr => Precedence::Shift,
|
||||
Op::BitAnd | Op::BitOr | Op::BitXor => Precedence::Bitwise,
|
||||
Op::LogAnd | Op::LogOr | Op::LogXor => Precedence::Logic,
|
||||
Op::RangeExc | Op::RangeInc => Precedence::Range,
|
||||
Op::Lt | Op::LtEq | Op::Equal | Op::NotEq | Op::GtEq | Op::Gt => Precedence::Compare,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnaryKind> for Precedence {
|
||||
fn from(value: UnaryKind) -> Self {
|
||||
use UnaryKind as Op;
|
||||
match value {
|
||||
Op::Loop => Precedence::Assign,
|
||||
Op::Deref | Op::Neg | Op::Not | Op::At | Op::Tilde => Precedence::Unary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates helper functions for turning TokenKinds into AST operators
|
||||
macro operator($($name:ident ($takes:ident => $returns:ident) {$($t:ident => $p:ident),*$(,)?};)*) {$(
|
||||
pub fn $name (value: $takes) -> Option<($returns, Precedence)> {
|
||||
match value {
|
||||
$($takes::$t => Some(($returns::$p, Precedence::from($returns::$p))),)*
|
||||
_ => None?,
|
||||
}
|
||||
})*
|
||||
}
|
||||
|
||||
operator! {
|
||||
from_prefix (TokenKind => UnaryKind) {
|
||||
Loop => Loop,
|
||||
Star => Deref,
|
||||
Minus => Neg,
|
||||
Bang => Not,
|
||||
At => At,
|
||||
Tilde => Tilde,
|
||||
};
|
||||
|
||||
from_modify(TokenKind => ModifyKind) {
|
||||
AmpEq => And,
|
||||
BarEq => Or,
|
||||
XorEq => Xor,
|
||||
LtLtEq => Shl,
|
||||
GtGtEq => Shr,
|
||||
PlusEq => Add,
|
||||
MinusEq => Sub,
|
||||
StarEq => Mul,
|
||||
SlashEq => Div,
|
||||
RemEq => Rem,
|
||||
};
|
||||
|
||||
from_infix (TokenKind => BinaryKind) {
|
||||
Lt => Lt,
|
||||
LtEq => LtEq,
|
||||
EqEq => Equal,
|
||||
BangEq => NotEq,
|
||||
GtEq => GtEq,
|
||||
Gt => Gt,
|
||||
DotDot => RangeExc,
|
||||
DotDotEq => RangeInc,
|
||||
AmpAmp => LogAnd,
|
||||
BarBar => LogOr,
|
||||
XorXor => LogXor,
|
||||
Amp => BitAnd,
|
||||
Bar => BitOr,
|
||||
Xor => BitXor,
|
||||
LtLt => Shl,
|
||||
GtGt => Shr,
|
||||
Plus => Add,
|
||||
Minus => Sub,
|
||||
Star => Mul,
|
||||
Slash => Div,
|
||||
Rem => Rem,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Pretty prints a conlang AST in yaml
|
||||
|
||||
use cl_ast::Stmt;
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use repline::{error::Error as RlError, Repline};
|
||||
@@ -19,7 +20,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
};
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(&line));
|
||||
let code = match parser.stmt() {
|
||||
let code = match parser.parse::<Stmt>() {
|
||||
Ok(code) => {
|
||||
rl.accept();
|
||||
code
|
||||
@@ -119,19 +120,19 @@ pub mod yamler {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Deref for Section<'y> {
|
||||
impl Deref for Section<'_> {
|
||||
type Target = Yamler;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
impl<'y> DerefMut for Section<'y> {
|
||||
impl DerefMut for Section<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Drop for Section<'y> {
|
||||
impl Drop for Section<'_> {
|
||||
fn drop(&mut self) {
|
||||
let Self { yamler } = self;
|
||||
yamler.decrease();
|
||||
@@ -363,22 +364,11 @@ pub mod yamlify {
|
||||
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;
|
||||
@@ -388,10 +378,14 @@ pub mod yamlify {
|
||||
impl Yamlify for ExprKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ExprKind::Quote(k) => k.yaml(y),
|
||||
ExprKind::Let(k) => k.yaml(y),
|
||||
ExprKind::Match(k) => k.yaml(y),
|
||||
ExprKind::Assign(k) => k.yaml(y),
|
||||
ExprKind::Modify(k) => k.yaml(y),
|
||||
ExprKind::Binary(k) => k.yaml(y),
|
||||
ExprKind::Unary(k) => k.yaml(y),
|
||||
ExprKind::Cast(k) => k.yaml(y),
|
||||
ExprKind::Member(k) => k.yaml(y),
|
||||
ExprKind::Index(k) => k.yaml(y),
|
||||
ExprKind::Structor(k) => k.yaml(y),
|
||||
@@ -404,16 +398,71 @@ pub mod yamlify {
|
||||
ExprKind::Empty => {}
|
||||
ExprKind::Group(k) => k.yaml(y),
|
||||
ExprKind::Tuple(k) => k.yaml(y),
|
||||
ExprKind::Loop(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),
|
||||
ExprKind::Continue => {
|
||||
y.key("Continue");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for Quote {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.key("Quote").value(self);
|
||||
}
|
||||
}
|
||||
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 Pattern {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
Pattern::Path(path) => y.value(path),
|
||||
Pattern::Literal(literal) => y.value(literal),
|
||||
Pattern::Ref(mutability, pattern) => {
|
||||
y.pair("mutability", mutability).pair("subpattern", pattern)
|
||||
}
|
||||
Pattern::Tuple(patterns) => y.key("Tuple").yaml(patterns),
|
||||
Pattern::Array(patterns) => y.key("Array").yaml(patterns),
|
||||
Pattern::Struct(path, items) => {
|
||||
{
|
||||
let mut y = y.key("Struct");
|
||||
y.pair("name", path);
|
||||
for (name, item) in items {
|
||||
y.pair(name, item);
|
||||
}
|
||||
}
|
||||
y
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Match {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { scrutinee, arms } = self;
|
||||
y.key("Match")
|
||||
.pair("scrutinee", scrutinee)
|
||||
.pair("arms", arms);
|
||||
}
|
||||
}
|
||||
|
||||
impl Yamlify for MatchArm {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self(pat, expr) = self;
|
||||
y.pair("pat", pat).pair("expr", expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Assign {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { parts } = self;
|
||||
@@ -461,6 +510,12 @@ pub mod yamlify {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Cast {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { head, ty } = self;
|
||||
y.key("Cast").pair("head", head).pair("ty", ty);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Member {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { head, kind } = self;
|
||||
@@ -516,11 +571,8 @@ pub mod yamlify {
|
||||
}
|
||||
impl Yamlify for AddrOf {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { count, mutable, expr } = self;
|
||||
y.key("AddrOf")
|
||||
.pair("count", count)
|
||||
.yaml(mutable)
|
||||
.pair("expr", expr);
|
||||
let Self { mutable, expr } = self;
|
||||
y.key("AddrOf").yaml(mutable).pair("expr", expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Group {
|
||||
@@ -529,12 +581,6 @@ pub mod yamlify {
|
||||
y.key("Group").yaml(expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Loop {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Loop").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for While {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
@@ -578,11 +624,6 @@ pub mod yamlify {
|
||||
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}\""));
|
||||
|
||||
@@ -35,6 +35,10 @@ argwerk::define! {
|
||||
[#[option] path] if file.is_none() => {
|
||||
file = path.map(Into::into);
|
||||
}
|
||||
|
||||
[path] if file.is_some() => {
|
||||
include.push(path.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// gets whether stdin AND stdout are a terminal, for pipelining
|
||||
|
||||
@@ -5,7 +5,8 @@ use crate::{
|
||||
menu,
|
||||
tools::print_token,
|
||||
};
|
||||
use cl_interpret::{convalue::ConValue, env::Environment, interpret::Interpret};
|
||||
use cl_ast::File;
|
||||
use cl_interpret::{builtin::builtins, convalue::ConValue, env::Environment, interpret::Interpret};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use std::{error::Error, path::Path};
|
||||
@@ -15,6 +16,31 @@ pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
||||
let Args { file, include, mode, repl } = args;
|
||||
|
||||
let mut env = Environment::new();
|
||||
|
||||
env.add_builtins(&builtins! {
|
||||
/// Clears the screen
|
||||
fn clear() {
|
||||
menu::clear();
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
/// Evaluates a quoted expression
|
||||
fn eval(ConValue::Quote(quote)) @env {
|
||||
env.eval(quote.as_ref())
|
||||
}
|
||||
/// Executes a file
|
||||
fn import(ConValue::String(path)) @env {
|
||||
load_file(env, &**path).or(Ok(ConValue::Empty))
|
||||
}
|
||||
|
||||
/// Gets a line of input from stdin
|
||||
fn get_line() {
|
||||
match repline::Repline::new("", "", "").read() {
|
||||
Ok(line) => Ok(ConValue::String(line.into())),
|
||||
Err(e) => Ok(ConValue::String(e.to_string().into())),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for path in include {
|
||||
load_file(&mut env, path)?;
|
||||
}
|
||||
@@ -49,7 +75,7 @@ fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue,
|
||||
let inliner =
|
||||
cl_parser::inliner::ModuleInliner::new(path.as_ref().parent().unwrap_or(Path::new("")));
|
||||
let file = std::fs::read_to_string(path)?;
|
||||
let code = Parser::new(Lexer::new(&file)).file()?;
|
||||
let code = Parser::new(Lexer::new(&file)).parse()?;
|
||||
let code = match inliner.inline(code) {
|
||||
Ok(a) => a,
|
||||
Err((code, io_errs, parse_errs)) => {
|
||||
@@ -79,13 +105,13 @@ fn lex_code(code: &str, path: Option<impl AsRef<Path>>) -> Result<(), Box<dyn Er
|
||||
}
|
||||
|
||||
fn fmt_code(code: &str) -> Result<(), Box<dyn Error>> {
|
||||
let code = Parser::new(Lexer::new(code)).file()?;
|
||||
let code = Parser::new(Lexer::new(code)).parse::<File>()?;
|
||||
println!("{code}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_code(code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
|
||||
let code = Parser::new(Lexer::new(code)).file()?;
|
||||
let code = Parser::new(Lexer::new(code)).parse::<File>()?;
|
||||
match code.interpret(env)? {
|
||||
ConValue::Empty => {}
|
||||
ret => println!("{ret}"),
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use cl_interpret::{
|
||||
env::Environment, error::IResult, interpret::Interpret, convalue::ConValue,
|
||||
};
|
||||
use cl_interpret::{convalue::ConValue, env::Environment, error::IResult, interpret::Interpret};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Context {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::{ansi, ctx};
|
||||
use cl_ast::Stmt;
|
||||
use cl_interpret::convalue::ConValue;
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use repline::{error::ReplResult, prebaked::*};
|
||||
|
||||
fn clear() {
|
||||
println!("{}", ansi::CLEAR_ALL);
|
||||
pub fn clear() {
|
||||
print!("{}", ansi::CLEAR_ALL);
|
||||
banner()
|
||||
}
|
||||
|
||||
@@ -42,11 +44,15 @@ pub fn run(ctx: &mut ctx::Context) -> ReplResult<()> {
|
||||
use cl_parser::inliner::ModuleInliner;
|
||||
|
||||
read_and(ansi::CYAN, "cl>", " ?>", |line| {
|
||||
let code = Parser::new(Lexer::new(line)).stmt()?;
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
let code = Parser::new(Lexer::new(line)).parse::<Stmt>()?;
|
||||
let code = ModuleInliner::new(".").fold_stmt(code);
|
||||
|
||||
print!("{}", ansi::OUTPUT);
|
||||
match ctx.run(&code) {
|
||||
Ok(ConValue::Empty) => print!("{}", ansi::RESET),
|
||||
Ok(v) => println!("{}{v}", ansi::RESET),
|
||||
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
|
||||
}
|
||||
@@ -71,7 +77,7 @@ pub fn fmt(_ctx: &mut ctx::Context) -> ReplResult<()> {
|
||||
read_and(ansi::BRIGHT_MAGENTA, "cl>", " ?>", |line| {
|
||||
let mut p = Parser::new(Lexer::new(line));
|
||||
|
||||
match p.stmt() {
|
||||
match p.parse::<Stmt>() {
|
||||
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@ license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cl-arena = { path = "../cl-arena" }
|
||||
cl-arena = { version = "0", registry = "soft-fish" }
|
||||
|
||||
@@ -106,7 +106,7 @@ impl<V, K: MapIndex> IndexMap<K, V> {
|
||||
pub fn get_many_mut<const N: usize>(
|
||||
&mut self,
|
||||
indices: [K; N],
|
||||
) -> Result<[&mut V; N], GetManyMutError<N>> {
|
||||
) -> Result<[&mut V; N], GetManyMutError> {
|
||||
self.map.get_many_mut(indices.map(|id| id.get()))
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,14 @@ pub mod interned {
|
||||
pub fn as_ptr(interned: &Self) -> *const T {
|
||||
interned.value
|
||||
}
|
||||
|
||||
/// Gets the internal value as a reference with the interner's lifetime
|
||||
pub fn to_ref(interned: &Self) -> &'a T {
|
||||
interned.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + Debug> Debug for Interned<'a, T> {
|
||||
impl<T: ?Sized + Debug> Debug for Interned<'_, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Interned")
|
||||
.field("value", &self.value)
|
||||
@@ -49,14 +54,14 @@ pub mod interned {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
impl<'a, T: ?Sized> Deref for Interned<'a, T> {
|
||||
impl<T: ?Sized> Deref for Interned<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
impl<'a, T: ?Sized> Copy for Interned<'a, T> {}
|
||||
impl<'a, T: ?Sized> Clone for Interned<'a, T> {
|
||||
impl<T: ?Sized> Copy for Interned<'_, T> {}
|
||||
impl<T: ?Sized> Clone for Interned<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
@@ -79,13 +84,13 @@ pub mod interned {
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<'a, T: ?Sized> Eq for Interned<'a, T> {}
|
||||
impl<'a, T: ?Sized> PartialEq for Interned<'a, T> {
|
||||
impl<T: ?Sized> Eq for Interned<'_, T> {}
|
||||
impl<T: ?Sized> PartialEq for Interned<'_, T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self.value, other.value)
|
||||
}
|
||||
}
|
||||
impl<'a, T: ?Sized> Hash for Interned<'a, T> {
|
||||
impl<T: ?Sized> Hash for Interned<'_, T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
Self::as_ptr(self).hash(state)
|
||||
}
|
||||
@@ -119,6 +124,7 @@ pub mod string_interner {
|
||||
};
|
||||
|
||||
/// A string interner hands out [Interned] copies of each unique string given to it.
|
||||
#[derive(Default)]
|
||||
pub struct StringInterner<'a> {
|
||||
arena: DroplessArena<'a>,
|
||||
keys: RwLock<HashSet<&'a str>>,
|
||||
@@ -185,12 +191,30 @@ pub mod string_interner {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StringInterner<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Ok(keys) = self.keys.read() else {
|
||||
return write!(f, "Could not lock StringInterner key map.");
|
||||
};
|
||||
let mut keys: Vec<_> = keys.iter().collect();
|
||||
keys.sort();
|
||||
|
||||
writeln!(f, "Keys:")?;
|
||||
for (idx, key) in keys.iter().enumerate() {
|
||||
writeln!(f, "{idx}:\t\"{key}\"")?
|
||||
}
|
||||
writeln!(f, "Count: {}", keys.len())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// # Safety:
|
||||
// This is fine because StringInterner::get_or_insert(v) holds a RwLock
|
||||
// for its entire duration, and doesn't touch the non-(Send+Sync) arena
|
||||
// unless the lock is held by a write guard.
|
||||
unsafe impl<'a> Send for StringInterner<'a> {}
|
||||
unsafe impl<'a> Sync for StringInterner<'a> {}
|
||||
unsafe impl Send for StringInterner<'_> {}
|
||||
unsafe impl Sync for StringInterner<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -240,6 +264,7 @@ pub mod typed_interner {
|
||||
/// A [TypedInterner] hands out [Interned] references for arbitrary types.
|
||||
///
|
||||
/// See the [module-level documentation](self) for more information.
|
||||
#[derive(Default)]
|
||||
pub struct TypedInterner<'a, T: Eq + Hash> {
|
||||
arena: TypedArena<'a, T>,
|
||||
keys: RwLock<HashSet<&'a T>>,
|
||||
@@ -286,5 +311,5 @@ pub mod typed_interner {
|
||||
/// [get_or_insert](TypedInterner::get_or_insert) are unique, and the function uses
|
||||
/// the [RwLock] around the [HashSet] to ensure mutual exclusion
|
||||
unsafe impl<'a, T: Eq + Hash + Send> Send for TypedInterner<'a, T> where &'a T: Send {}
|
||||
unsafe impl<'a, T: Eq + Hash + Send + Sync> Sync for TypedInterner<'a, T> {}
|
||||
unsafe impl<T: Eq + Hash + Send + Sync> Sync for TypedInterner<'_, T> {}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,4 @@ pub mod token_type;
|
||||
|
||||
pub use token::Token;
|
||||
pub use token_data::TokenData;
|
||||
pub use token_type::{Punct, TokenKind};
|
||||
pub use token_type::TokenKind;
|
||||
|
||||
@@ -13,41 +13,36 @@ pub enum TokenKind {
|
||||
/// A non-keyword identifier
|
||||
Identifier,
|
||||
// A keyword
|
||||
As,
|
||||
Break,
|
||||
Cl,
|
||||
Const,
|
||||
Continue,
|
||||
Else,
|
||||
Enum,
|
||||
False,
|
||||
For,
|
||||
Fn,
|
||||
If,
|
||||
Impl,
|
||||
In,
|
||||
Let,
|
||||
Loop,
|
||||
Mod,
|
||||
Mut,
|
||||
Pub,
|
||||
Return,
|
||||
SelfKw,
|
||||
SelfTy,
|
||||
Static,
|
||||
Struct,
|
||||
Super,
|
||||
True,
|
||||
Type,
|
||||
Use,
|
||||
While,
|
||||
/// Delimiter or punctuation
|
||||
Punct(Punct),
|
||||
}
|
||||
|
||||
/// An operator character (delimiter, punctuation)
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Punct {
|
||||
As, // as
|
||||
Break, // "break"
|
||||
Cl, // "cl"
|
||||
Const, // "const"
|
||||
Continue, // "continue"
|
||||
Else, // "else"
|
||||
Enum, // "enum"
|
||||
False, // "false"
|
||||
Fn, // "fn"
|
||||
For, // "for"
|
||||
If, // "if"
|
||||
Impl, // "impl"
|
||||
In, // "in"
|
||||
Let, // "let"
|
||||
Loop, // "loop"
|
||||
Match, // "match"
|
||||
Mod, // "mod"
|
||||
Mut, // "mut"
|
||||
Pub, // "pub"
|
||||
Return, // "return"
|
||||
SelfKw, // "self"
|
||||
SelfTy, // "Self"
|
||||
Static, // "static"
|
||||
Struct, // "struct"
|
||||
Super, // "super"
|
||||
True, // "true"
|
||||
Type, // "type"
|
||||
Use, // "use"
|
||||
While, // "while"
|
||||
// Delimiter or punctuation
|
||||
LCurly, // {
|
||||
RCurly, // }
|
||||
LBrack, // [
|
||||
@@ -112,36 +107,90 @@ impl Display for TokenKind {
|
||||
TokenKind::Literal => "literal".fmt(f),
|
||||
TokenKind::Identifier => "identifier".fmt(f),
|
||||
|
||||
TokenKind::As => "as".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::Loop => "loop".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::Use => "use".fmt(f),
|
||||
TokenKind::While => "while".fmt(f),
|
||||
TokenKind::As => "sama".fmt(f),
|
||||
TokenKind::Break => "pana".fmt(f),
|
||||
TokenKind::Cl => "la".fmt(f),
|
||||
TokenKind::Const => "kiwen".fmt(f),
|
||||
TokenKind::Continue => "tawa".fmt(f),
|
||||
TokenKind::Else => "taso".fmt(f),
|
||||
TokenKind::Enum => "kulupu".fmt(f),
|
||||
TokenKind::False => "ike".fmt(f),
|
||||
TokenKind::Fn => "nasin".fmt(f),
|
||||
TokenKind::For => "ale".fmt(f),
|
||||
TokenKind::If => "tan".fmt(f),
|
||||
TokenKind::Impl => "insa".fmt(f),
|
||||
TokenKind::In => "lon".fmt(f),
|
||||
TokenKind::Let => "poki".fmt(f),
|
||||
TokenKind::Loop => "awen".fmt(f),
|
||||
TokenKind::Match => "seme".fmt(f),
|
||||
TokenKind::Mod => "selo".fmt(f),
|
||||
TokenKind::Mut => "ante".fmt(f),
|
||||
TokenKind::Pub => "lukin".fmt(f),
|
||||
TokenKind::Return => "pini".fmt(f),
|
||||
TokenKind::SelfKw => "mi".fmt(f),
|
||||
TokenKind::SelfTy => "Mi".fmt(f),
|
||||
TokenKind::Static => "mute".fmt(f),
|
||||
TokenKind::Struct => "lipu".fmt(f),
|
||||
TokenKind::Super => "mama".fmt(f),
|
||||
TokenKind::True => "pona".fmt(f),
|
||||
TokenKind::Type => "ijo".fmt(f),
|
||||
TokenKind::Use => "jo".fmt(f),
|
||||
TokenKind::While => "lawa".fmt(f),
|
||||
|
||||
TokenKind::Punct(op) => op.fmt(f),
|
||||
TokenKind::LCurly => "{".fmt(f),
|
||||
TokenKind::RCurly => "}".fmt(f),
|
||||
TokenKind::LBrack => "[".fmt(f),
|
||||
TokenKind::RBrack => "]".fmt(f),
|
||||
TokenKind::LParen => "(".fmt(f),
|
||||
TokenKind::RParen => ")".fmt(f),
|
||||
TokenKind::Amp => "&".fmt(f),
|
||||
TokenKind::AmpAmp => "&&".fmt(f),
|
||||
TokenKind::AmpEq => "&=".fmt(f),
|
||||
TokenKind::Arrow => "->".fmt(f),
|
||||
TokenKind::At => "@".fmt(f),
|
||||
TokenKind::Backslash => "\\".fmt(f),
|
||||
TokenKind::Bang => "!".fmt(f),
|
||||
TokenKind::BangBang => "!!".fmt(f),
|
||||
TokenKind::BangEq => "!=".fmt(f),
|
||||
TokenKind::Bar => "|".fmt(f),
|
||||
TokenKind::BarBar => "||".fmt(f),
|
||||
TokenKind::BarEq => "|=".fmt(f),
|
||||
TokenKind::Colon => ":".fmt(f),
|
||||
TokenKind::ColonColon => "::".fmt(f),
|
||||
TokenKind::Comma => ",".fmt(f),
|
||||
TokenKind::Dot => ".".fmt(f),
|
||||
TokenKind::DotDot => "..".fmt(f),
|
||||
TokenKind::DotDotEq => "..=".fmt(f),
|
||||
TokenKind::Eq => "=".fmt(f),
|
||||
TokenKind::EqEq => "==".fmt(f),
|
||||
TokenKind::FatArrow => "=>".fmt(f),
|
||||
TokenKind::Grave => "`".fmt(f),
|
||||
TokenKind::Gt => ">".fmt(f),
|
||||
TokenKind::GtEq => ">=".fmt(f),
|
||||
TokenKind::GtGt => ">>".fmt(f),
|
||||
TokenKind::GtGtEq => ">>=".fmt(f),
|
||||
TokenKind::Hash => "#".fmt(f),
|
||||
TokenKind::HashBang => "#!".fmt(f),
|
||||
TokenKind::Lt => "<".fmt(f),
|
||||
TokenKind::LtEq => "<=".fmt(f),
|
||||
TokenKind::LtLt => "<<".fmt(f),
|
||||
TokenKind::LtLtEq => "<<=".fmt(f),
|
||||
TokenKind::Minus => "-".fmt(f),
|
||||
TokenKind::MinusEq => "-=".fmt(f),
|
||||
TokenKind::Plus => "+".fmt(f),
|
||||
TokenKind::PlusEq => "+=".fmt(f),
|
||||
TokenKind::Question => "?".fmt(f),
|
||||
TokenKind::Rem => "%".fmt(f),
|
||||
TokenKind::RemEq => "%=".fmt(f),
|
||||
TokenKind::Semi => ";".fmt(f),
|
||||
TokenKind::Slash => "/".fmt(f),
|
||||
TokenKind::SlashEq => "/=".fmt(f),
|
||||
TokenKind::Star => "*".fmt(f),
|
||||
TokenKind::StarEq => "*=".fmt(f),
|
||||
TokenKind::Tilde => "~".fmt(f),
|
||||
TokenKind::Xor => "^".fmt(f),
|
||||
TokenKind::XorEq => "^=".fmt(f),
|
||||
TokenKind::XorXor => "^^".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,96 +200,36 @@ impl FromStr for TokenKind {
|
||||
/// Parses a string s to return a Keyword
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"as" => Self::As,
|
||||
"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,
|
||||
"loop" => Self::Loop,
|
||||
"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,
|
||||
"use" => Self::Use,
|
||||
"while" => Self::While,
|
||||
"as" | "sama" => Self::As,
|
||||
"break" | "pana" => Self::Break,
|
||||
"cl" | "la" => Self::Cl,
|
||||
"const" | "kiwen" => Self::Const,
|
||||
"continue" | "tawa" => Self::Continue,
|
||||
"else" | "taso" => Self::Else,
|
||||
"enum" | "kulupu" => Self::Enum,
|
||||
"false" | "ike" => Self::False,
|
||||
"fn" | "nasin" => Self::Fn,
|
||||
"for" | "ale" => Self::For,
|
||||
"if" | "tan" => Self::If,
|
||||
"impl" | "insa" => Self::Impl,
|
||||
"in" | "lon" => Self::In,
|
||||
"let" | "poki" => Self::Let,
|
||||
"loop" | "awen" => Self::Loop,
|
||||
"match" | "seme" => Self::Match,
|
||||
"mod" | "selo" => Self::Mod,
|
||||
"mut" | "ante" => Self::Mut,
|
||||
"pub" | "lukin" => Self::Pub,
|
||||
"return" | "pini" => Self::Return,
|
||||
"self" | "mi" => Self::SelfKw,
|
||||
"Self" | "Mi" => Self::SelfTy,
|
||||
"static" | "mute" => Self::Static,
|
||||
"struct" | "lipu" => Self::Struct,
|
||||
"super" | "mama" => Self::Super,
|
||||
"true" | "pona" => Self::True,
|
||||
"type" | "ijo" => Self::Type,
|
||||
"use" | "jo" => Self::Use,
|
||||
"while" | "lawa" => 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,43 +3,48 @@ use cl_typeck::{entry::Entry, stage::*, table::Table, type_expression::TypeExpre
|
||||
use cl_ast::{
|
||||
ast_visitor::{Fold, Visit},
|
||||
desugar::*,
|
||||
Stmt, Ty,
|
||||
};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::{inliner::ModuleInliner, Parser};
|
||||
use cl_structures::intern::string_interner::StringInterner;
|
||||
use repline::{error::Error as RlError, prebaked::*};
|
||||
use std::{error::Error, path};
|
||||
use std::{
|
||||
error::Error,
|
||||
path::{self, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
// Path to display in standard library errors
|
||||
const STDLIB_DISPLAY_PATH: &str = "stdlib/lib.cl";
|
||||
// Statically included standard library
|
||||
const STDLIB: &str = include_str!("../../../stdlib/lib.cl");
|
||||
const PREAMBLE: &str = r"
|
||||
pub mod std;
|
||||
pub use std::preamble::*;
|
||||
";
|
||||
|
||||
// Colors
|
||||
const C_MAIN: &str = C_LISTING;
|
||||
const C_RESV: &str = "\x1b[35m";
|
||||
const C_CODE: &str = "\x1b[36m";
|
||||
const C_BYID: &str = "\x1b[95m";
|
||||
const C_ERROR: &str = "\x1b[31m";
|
||||
const C_LISTING: &str = "\x1b[38;5;117m";
|
||||
|
||||
/// A home for immutable intermediate ASTs
|
||||
///
|
||||
/// TODO: remove this.
|
||||
static mut TREES: TreeManager = TreeManager::new();
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut prj = Table::default();
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(STDLIB));
|
||||
let code = match parser.file() {
|
||||
let mut parser = Parser::new(Lexer::new(PREAMBLE));
|
||||
let code = match parser.parse() {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
|
||||
Err(e)?
|
||||
}
|
||||
};
|
||||
let code = inline_modules(code, concat!(env!("PWD"), "/stdlib"));
|
||||
Populator::new(&mut prj).visit_file(unsafe { TREES.push(code) });
|
||||
// NameCollector::new(&mut prj).visit_file(unsafe { TREES.push(code) });
|
||||
// This code is special - it gets loaded from a hard-coded project directory (for now)
|
||||
let code = inline_modules(code, concat!(env!("CARGO_MANIFEST_DIR"), "/../../stdlib"));
|
||||
Populator::new(&mut prj).visit_file(interned(code));
|
||||
|
||||
main_menu(&mut prj)?;
|
||||
Ok(())
|
||||
@@ -51,21 +56,25 @@ fn main_menu(prj: &mut Table) -> Result<(), RlError> {
|
||||
match line.trim() {
|
||||
"c" | "code" => enter_code(prj)?,
|
||||
"clear" => clear()?,
|
||||
"d" | "desugar" => live_desugar()?,
|
||||
"e" | "exit" => return Ok(Response::Break),
|
||||
"f" | "file" => import_files(prj)?,
|
||||
"i" | "id" => get_by_id(prj)?,
|
||||
"l" | "list" => list_types(prj),
|
||||
"q" | "query" => query_type_expression(prj)?,
|
||||
"i" | "id" => get_by_id(prj)?,
|
||||
"r" | "resolve" => resolve_all(prj)?,
|
||||
"d" | "desugar" => live_desugar()?,
|
||||
"h" | "help" => {
|
||||
"s" | "strings" => print_strings(),
|
||||
"h" | "help" | "" => {
|
||||
println!(
|
||||
"Valid commands are:
|
||||
clear : Clear the screen
|
||||
code (c): Enter code to type-check
|
||||
desugar (d): WIP: Test the experimental desugaring passes
|
||||
file (f): Load files from disk
|
||||
id (i): Get a type by its type ID
|
||||
list (l): List all known types
|
||||
query (q): Query the type system
|
||||
id (i): Get a type by its type ID
|
||||
resolve (r): Perform type resolution
|
||||
desugar (d): WIP: Test the experimental desugaring passes
|
||||
help (h): Print this list
|
||||
exit (e): Exit the program"
|
||||
);
|
||||
@@ -82,18 +91,18 @@ fn enter_code(prj: &mut Table) -> Result<(), RlError> {
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
let code = Parser::new(Lexer::new(line)).file()?;
|
||||
let code = Parser::new(Lexer::new(line)).parse()?;
|
||||
let code = inline_modules(code, "");
|
||||
let code = WhileElseDesugar.fold_file(code);
|
||||
// Safety: this is totally unsafe
|
||||
Populator::new(prj).visit_file(unsafe { TREES.push(code) });
|
||||
|
||||
Populator::new(prj).visit_file(interned(code));
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn live_desugar() -> Result<(), RlError> {
|
||||
read_and(C_RESV, "se>", "? >", |line| {
|
||||
let code = Parser::new(Lexer::new(line)).stmt()?;
|
||||
let code = Parser::new(Lexer::new(line)).parse::<Stmt>()?;
|
||||
println!("Raw, as parsed:\n{C_LISTING}{code}\x1b[0m");
|
||||
|
||||
let code = SquashGroups.fold_stmt(code);
|
||||
@@ -109,13 +118,17 @@ fn live_desugar() -> Result<(), RlError> {
|
||||
})
|
||||
}
|
||||
|
||||
fn print_strings() {
|
||||
println!("{}", StringInterner::global());
|
||||
}
|
||||
|
||||
fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
|
||||
read_and(C_RESV, "ty>", "? >", |line| {
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
// parse it as a path, and convert the path into a borrowed path
|
||||
let ty = Parser::new(Lexer::new(line)).ty()?.kind;
|
||||
let ty: Ty = Parser::new(Lexer::new(line)).parse()?;
|
||||
let id = ty.evaluate(prj, prj.root())?;
|
||||
pretty_handle(id.to_entry(prj))?;
|
||||
Ok(Response::Accept)
|
||||
@@ -123,6 +136,7 @@ fn query_type_expression(prj: &mut Table) -> Result<(), RlError> {
|
||||
}
|
||||
|
||||
fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
|
||||
use cl_parser::parser::Parse;
|
||||
use cl_structures::index_map::MapIndex;
|
||||
use cl_typeck::handle::Handle;
|
||||
read_and(C_BYID, "id>", "? >", |line| {
|
||||
@@ -130,11 +144,11 @@ fn get_by_id(prj: &mut Table) -> Result<(), RlError> {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
let mut parser = Parser::new(Lexer::new(line));
|
||||
let def_id = match parser.literal()? {
|
||||
let def_id = match Parse::parse(&mut parser)? {
|
||||
cl_ast::Literal::Int(int) => int as _,
|
||||
other => Err(format!("Expected integer, got {other}"))?,
|
||||
};
|
||||
let mut path = parser.path().unwrap_or_default();
|
||||
let mut path = parser.parse::<cl_ast::Path>().unwrap_or_default();
|
||||
path.absolute = false;
|
||||
|
||||
let handle = Handle::from_usize(def_id).to_entry(prj);
|
||||
@@ -182,6 +196,35 @@ fn list_types(table: &mut Table) {
|
||||
}
|
||||
}
|
||||
|
||||
fn import_files(table: &mut Table) -> Result<(), RlError> {
|
||||
read_and(C_RESV, "fi>", "? >", |line| {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
let Ok(file) = std::fs::read_to_string(line) else {
|
||||
for file in std::fs::read_dir(line)? {
|
||||
println!("{}", file?.path().display())
|
||||
}
|
||||
return Ok(Response::Accept);
|
||||
};
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(&file));
|
||||
let code = match parser.parse() {
|
||||
Ok(code) => inline_modules(code, PathBuf::from(line).parent().unwrap_or("".as_ref())),
|
||||
Err(e) => {
|
||||
eprintln!("{C_ERROR}{line}:{e}\x1b[0m");
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
};
|
||||
|
||||
Populator::new(table).visit_file(interned(code));
|
||||
|
||||
println!("...Imported!");
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn pretty_handle(entry: Entry) -> Result<(), std::io::Error> {
|
||||
use std::io::Write;
|
||||
let mut out = std::io::stdout().lock();
|
||||
@@ -275,18 +318,11 @@ fn banner() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Keeps leaked references to past ASTs, for posterity:tm:
|
||||
struct TreeManager {
|
||||
trees: Vec<&'static cl_ast::File>,
|
||||
}
|
||||
/// Interns a [File](cl_ast::File), returning a static reference to it.
|
||||
fn interned(file: cl_ast::File) -> &'static cl_ast::File {
|
||||
use cl_structures::intern::{interned::Interned, typed_interner::TypedInterner};
|
||||
static INTERNER: LazyLock<TypedInterner<'static, cl_ast::File>> =
|
||||
LazyLock::new(Default::default);
|
||||
|
||||
impl TreeManager {
|
||||
const fn new() -> Self {
|
||||
Self { trees: vec![] }
|
||||
}
|
||||
fn push(&mut self, tree: cl_ast::File) -> &'static cl_ast::File {
|
||||
let ptr = Box::leak(Box::new(tree));
|
||||
self.trees.push(ptr);
|
||||
ptr
|
||||
}
|
||||
Interned::to_ref(&INTERNER.get_or_insert(file))
|
||||
}
|
||||
|
||||
@@ -21,10 +21,8 @@ impl fmt::Display for Entry<'_, '_> {
|
||||
TypeKind::Instance(id) => write!(f, "{}", self.with_id(*id)),
|
||||
TypeKind::Intrinsic(kind) => write!(f, "{kind}"),
|
||||
TypeKind::Adt(adt) => write_adt(adt, self, f),
|
||||
&TypeKind::Ref(cnt, id) => {
|
||||
for _ in 0..cnt {
|
||||
&TypeKind::Ref(id) => {
|
||||
f.write_str("&")?;
|
||||
}
|
||||
let h_id = self.with_id(id);
|
||||
write_name_or(h_id, f)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ pub enum Source<'a> {
|
||||
Ty(&'a TyKind),
|
||||
}
|
||||
|
||||
impl<'a> Source<'a> {
|
||||
impl Source<'_> {
|
||||
pub fn name(&self) -> Option<Sym> {
|
||||
match self {
|
||||
Source::Root => None,
|
||||
@@ -32,7 +32,7 @@ impl<'a> Source<'a> {
|
||||
Source::Const(v) => Some(v.name),
|
||||
Source::Static(v) => Some(v.name),
|
||||
Source::Function(v) => Some(v.name),
|
||||
Source::Local(l) => Some(l.name),
|
||||
Source::Local(_) => None,
|
||||
Source::Impl(_) | Source::Use(_) | Source::Ty(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ impl Type {
|
||||
}
|
||||
/// Checks whether there are any unbound type variables in this type.
|
||||
/// ```rust
|
||||
/// # use cl_typeck::inference::*;
|
||||
/// # use cl_typeck::stage::infer::*;
|
||||
/// let bool = Type::new_op("bool".into(), &[]);
|
||||
/// let true_v = Type::new_inst(&bool);
|
||||
/// let unbound = Type::new_var();
|
||||
@@ -149,7 +149,7 @@ impl Type {
|
||||
/// Panics if this type variable's instance field is already borrowed.
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use cl_typeck::inference::*;
|
||||
/// # use cl_typeck::stage::infer::*;
|
||||
/// let t_bool = Type::new_op("bool".into(), &[]);
|
||||
/// let t_nest = Type::new_inst(&Type::new_inst(&Type::new_inst(&t_bool)));
|
||||
/// let pruned = t_nest.prune();
|
||||
|
||||
@@ -145,35 +145,23 @@ impl<'a> Visit<'a> for Populator<'_, 'a> {
|
||||
self.visit_use_tree(tree);
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, s: &'a cl_ast::Stmt) {
|
||||
let cl_ast::Stmt { extents, kind, semi } = s;
|
||||
let cl_ast::StmtKind::Local(local) = kind else {
|
||||
self.visit_span(extents);
|
||||
self.visit_stmt_kind(kind);
|
||||
self.visit_semi(semi);
|
||||
return;
|
||||
};
|
||||
|
||||
let mut entry = self.new_entry(NodeKind::Local);
|
||||
entry.inner.set_span(*extents);
|
||||
entry.visit_let(local);
|
||||
|
||||
if let (Some(name), child) = (entry.name, entry.inner.id()) {
|
||||
self.inner.add_child(name, child);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_let(&mut self, l: &'a cl_ast::Let) {
|
||||
let cl_ast::Let { mutable, name, ty, init } = l;
|
||||
self.inner.set_source(Source::Local(l));
|
||||
self.set_name(*name);
|
||||
let cl_ast::Let { mutable, name: _, ty, init } = l;
|
||||
let mut entry = self.new_entry(NodeKind::Local);
|
||||
|
||||
self.visit_mutability(mutable);
|
||||
entry.inner.set_source(Source::Local(l));
|
||||
// entry.set_name(*name);
|
||||
|
||||
entry.visit_mutability(mutable);
|
||||
if let Some(ty) = ty {
|
||||
self.visit_ty(ty);
|
||||
entry.visit_ty(ty);
|
||||
}
|
||||
if let Some(init) = init {
|
||||
self.visit_expr(init)
|
||||
}
|
||||
entry.visit_expr(init)
|
||||
}
|
||||
|
||||
// let child = entry.inner.id();
|
||||
// self.inner.add_child(*name, child);
|
||||
todo!("Pattern destructuring in cl-typeck")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ impl<'a> Table<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for Table<'a> {
|
||||
impl Default for Table<'_> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
@@ -97,8 +97,12 @@ impl TypeExpression for TyTuple {
|
||||
impl TypeExpression for TyRef {
|
||||
fn evaluate(&self, table: &mut Table, node: Handle) -> Result<Handle, Error> {
|
||||
let Self { mutable: _, count, to } = self;
|
||||
let kind = TypeKind::Ref(*count, to.evaluate(table, node)?);
|
||||
Ok(table.anon_type(kind))
|
||||
let mut t = to.evaluate(table, node)?;
|
||||
for _ in 0..*count {
|
||||
let kind = TypeKind::Ref(t);
|
||||
t = table.anon_type(kind)
|
||||
}
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ pub enum TypeKind {
|
||||
/// A user-defined aromatic data type
|
||||
Adt(Adt),
|
||||
/// A reference to an already-defined type: &T
|
||||
Ref(u16, Handle),
|
||||
Ref(Handle),
|
||||
/// A contiguous view of dynamically sized memory
|
||||
Slice(Handle),
|
||||
/// A contiguous view of statically sized memory
|
||||
@@ -54,39 +54,18 @@ pub enum Adt {
|
||||
|
||||
/// The set of compiler-intrinsic types.
|
||||
/// These primitive types have native implementations of the basic operations.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[rustfmt::skip]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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,
|
||||
/// A ptr-len signed integer: `#[intrinsic = "isize"]`
|
||||
Isize,
|
||||
/// 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 ptr-len unsigned integer: `#[intrinsic = "isize"]`
|
||||
Usize,
|
||||
/// A boolean (`true` or `false`): `#[intrinsic = "bool"]`
|
||||
Bool,
|
||||
/// The unicode codepoint type: #[intrinsic = "char"]
|
||||
Char,
|
||||
I8, I16, I32, I64, I128, Isize, // Signed integers
|
||||
U8, U16, U32, U64, U128, Usize, // Unsigned integers
|
||||
F8, F16, F32, F64, F128, Fsize, // Floating point numbers
|
||||
Bool, // boolean value
|
||||
Char, // Unicode codepoint
|
||||
}
|
||||
|
||||
// Author's note: the fsize type is a meme
|
||||
|
||||
impl FromStr for Intrinsic {
|
||||
type Err = ();
|
||||
|
||||
@@ -96,12 +75,20 @@ impl FromStr for Intrinsic {
|
||||
"i16" => Intrinsic::I16,
|
||||
"i32" => Intrinsic::I32,
|
||||
"i64" => Intrinsic::I64,
|
||||
"i128" => Intrinsic::I128,
|
||||
"isize" => Intrinsic::Isize,
|
||||
"u8" => Intrinsic::U8,
|
||||
"u16" => Intrinsic::U16,
|
||||
"u32" => Intrinsic::U32,
|
||||
"u64" => Intrinsic::U64,
|
||||
"u128" => Intrinsic::U128,
|
||||
"usize" => Intrinsic::Usize,
|
||||
"f8" => Intrinsic::F8,
|
||||
"f16" => Intrinsic::F16,
|
||||
"f32" => Intrinsic::F32,
|
||||
"f64" => Intrinsic::F64,
|
||||
"f128" => Intrinsic::F128,
|
||||
"fsize" => Intrinsic::Fsize,
|
||||
"bool" => Intrinsic::Bool,
|
||||
"char" => Intrinsic::Char,
|
||||
_ => Err(())?,
|
||||
|
||||
@@ -11,12 +11,7 @@ impl Display for TypeKind {
|
||||
TypeKind::Instance(def) => write!(f, "alias to #{def}"),
|
||||
TypeKind::Intrinsic(i) => i.fmt(f),
|
||||
TypeKind::Adt(a) => a.fmt(f),
|
||||
TypeKind::Ref(cnt, def) => {
|
||||
for _ in 0..*cnt {
|
||||
f.write_str("&")?;
|
||||
}
|
||||
def.fmt(f)
|
||||
}
|
||||
TypeKind::Ref(def) => write!(f, "&{def}"),
|
||||
TypeKind::Slice(def) => write!(f, "slice [#{def}]"),
|
||||
TypeKind::Array(def, size) => write!(f, "array [#{def}; {size}]"),
|
||||
TypeKind::Tuple(defs) => {
|
||||
@@ -80,12 +75,20 @@ impl Display for Intrinsic {
|
||||
Intrinsic::I16 => f.write_str("i16"),
|
||||
Intrinsic::I32 => f.write_str("i32"),
|
||||
Intrinsic::I64 => f.write_str("i64"),
|
||||
Intrinsic::I128 => f.write_str("i128"),
|
||||
Intrinsic::Isize => f.write_str("isize"),
|
||||
Intrinsic::U8 => f.write_str("u8"),
|
||||
Intrinsic::U16 => f.write_str("u16"),
|
||||
Intrinsic::U32 => f.write_str("u32"),
|
||||
Intrinsic::U64 => f.write_str("u64"),
|
||||
Intrinsic::U128 => f.write_str("u128"),
|
||||
Intrinsic::Usize => f.write_str("usize"),
|
||||
Intrinsic::F8 => f.write_str("f8"),
|
||||
Intrinsic::F16 => f.write_str("f16"),
|
||||
Intrinsic::F32 => f.write_str("f32"),
|
||||
Intrinsic::F64 => f.write_str("f64"),
|
||||
Intrinsic::F128 => f.write_str("f128"),
|
||||
Intrinsic::Fsize => f.write_str("fsize"),
|
||||
Intrinsic::Bool => f.write_str("bool"),
|
||||
Intrinsic::Char => f.write_str("char"),
|
||||
}
|
||||
|
||||
@@ -96,7 +96,9 @@ Shift = Factor (ShiftOp Factor )* ;
|
||||
Factor = Term (FactorOp Term )* ;
|
||||
Term = Unary (TermOp Unary )* ;
|
||||
|
||||
Unary = (UnaryKind)* Member ;
|
||||
Unary = (UnaryKind)* Cast ;
|
||||
|
||||
Cast = Member ("as" Ty)? ;
|
||||
|
||||
Member = Call (Access)* ;
|
||||
Access = '.' (Identifier ('(' Tuple? ')')? | Literal) ;
|
||||
|
||||
17
repline/examples/repl_float.rs
Normal file
17
repline/examples/repl_float.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Demonstrates the use of [read_and()]:
|
||||
//!
|
||||
//! The provided closure:
|
||||
//! 1. Takes a line of input (a [String])
|
||||
//! 2. Performs some calculation (using [FromStr])
|
||||
//! 3. Returns a [Result] containing a [Response] or an [Err]
|
||||
|
||||
use repline::{prebaked::read_and, Response};
|
||||
use std::{error::Error, str::FromStr};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
read_and("\x1b[33m", " >", " ?>", |line| {
|
||||
println!("-> {:?}", f64::from_str(line.trim())?);
|
||||
Ok(Response::Accept)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -302,7 +302,7 @@ impl<'a> Editor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'e> IntoIterator for &'e Editor<'a> {
|
||||
impl<'e> IntoIterator for &'e Editor<'_> {
|
||||
type Item = &'e char;
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::collections::vec_deque::Iter<'e, char>,
|
||||
@@ -313,7 +313,7 @@ impl<'a, 'e> IntoIterator for &'e Editor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Editor<'a> {
|
||||
impl Display for Editor<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::fmt::Write;
|
||||
for c in self.iter() {
|
||||
|
||||
@@ -39,7 +39,7 @@ pub mod chars {
|
||||
if cont & 0xc0 != 0x80 {
|
||||
return None;
|
||||
}
|
||||
out = out << 6 | (cont & 0x3f);
|
||||
out = (out << 6) | (cont & 0x3f);
|
||||
}
|
||||
Some(char::from_u32(out).ok_or(BadUnicode(out)))
|
||||
}
|
||||
|
||||
48
sample-code/ascii.cl
Executable file
48
sample-code/ascii.cl
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env -S conlang -r false
|
||||
//! Prints out the characters in the ASCII printable range
|
||||
//! and the Latin-1 supplement in the format of a hex-dump
|
||||
|
||||
fn main () {
|
||||
ascii()
|
||||
}
|
||||
|
||||
fn n_digit(n: u32) -> char {
|
||||
(if n > 9 {
|
||||
('a' as u32) + n - 10
|
||||
} else {
|
||||
('0' as u32) + n
|
||||
}) as char
|
||||
}
|
||||
|
||||
fn in_range(num: u32, start: u32, end: u32) -> bool {
|
||||
(start <= num) && (num <= end )
|
||||
}
|
||||
|
||||
fn ascii() {
|
||||
for row in 0..16 {
|
||||
for col in 0..16 {
|
||||
if col == 8 {
|
||||
print(' ')
|
||||
}
|
||||
print(n_digit(row), n_digit(col), ' ')
|
||||
}
|
||||
print(" │")
|
||||
for col in 0..16 {
|
||||
print(ascii_picture(row << 4 | col))
|
||||
}
|
||||
println("│")
|
||||
}
|
||||
}
|
||||
|
||||
// Start of the C0 control pictures region
|
||||
const CO_CONTROL_PICTURES: u32 = '\u{2400}' as u32;
|
||||
|
||||
fn ascii_picture(c: u32) -> char {
|
||||
if c < ' ' as u32 { // C0
|
||||
(CO_CONTROL_PICTURES + c) as char
|
||||
} else if c == 127 { // C0:DEL
|
||||
'␡' // SYMBOL_FOR_DELETE
|
||||
} else if c.in_range(0x7f, 0xa0) { // C1
|
||||
' '
|
||||
} else c as char
|
||||
}
|
||||
7
sample-code/fib.cl
Normal file → Executable file
7
sample-code/fib.cl
Normal file → Executable file
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env -S conlang -r false
|
||||
// Calculate Fibonacci numbers
|
||||
|
||||
fn main() {
|
||||
for num in 0..=30 {
|
||||
print("fib(", num, ") = ", fib_iterative(num))
|
||||
println("fib(", num, ") = ", fibit(num))
|
||||
}
|
||||
for num in 0..=30 {
|
||||
print("fib(", num, ") = ", fib(num))
|
||||
println("fib(", num, ") = ", fib(num))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +18,7 @@ fn fib(a: i64) -> i64 {
|
||||
}
|
||||
|
||||
/// The classic iterative algorithm for fib()
|
||||
fn fib_iterative(n: i64) -> i64 {
|
||||
fn fibit(n: i64) -> i64 {
|
||||
let mut a = 0;
|
||||
let mut b = 1;
|
||||
let mut c = 1;
|
||||
|
||||
5
sample-code/fizzbuzz.cl
Normal file → Executable file
5
sample-code/fizzbuzz.cl
Normal file → Executable file
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env -S conlang -r false
|
||||
// FizzBuzz, using the unstable variadic-`print` builtin
|
||||
|
||||
fn main() {
|
||||
fizzbuzz(10, 20)
|
||||
fizzbuzz(0, 30)
|
||||
}
|
||||
|
||||
// Outputs FizzBuzz for numbers between `start` and `end`, inclusive
|
||||
fn fizzbuzz(start: i128, end: i128) {
|
||||
for x in start..=end {
|
||||
print(if x % 15 == 0 {
|
||||
println(if x % 15 == 0 {
|
||||
"FizzBuzz"
|
||||
} else if 0 == x % 3 {
|
||||
"Fizz"
|
||||
|
||||
5
sample-code/hello.cl
Normal file → Executable file
5
sample-code/hello.cl
Normal file → Executable file
@@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env -S conlang -r false
|
||||
//! Prints "Hello, world!"
|
||||
|
||||
fn main() {
|
||||
print("Hello, world!")
|
||||
println("Hello, world!")
|
||||
}
|
||||
|
||||
5
sample-code/hello.tp
Normal file
5
sample-code/hello.tp
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
// The main function
|
||||
nasin wan () {
|
||||
toki_linja("mi toki ale a!")
|
||||
}
|
||||
@@ -1,23 +1,40 @@
|
||||
//! Formats numbers in hexadecimal, octal, or binary
|
||||
mod math;
|
||||
use math::{min, count_leading_zeroes};
|
||||
|
||||
// TODO: casting and/or conversion
|
||||
const HEX_LUT: Array = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
];
|
||||
fn as_digit(n: u32) -> char {
|
||||
(if n > 9 {
|
||||
n - 10 + ('a' as u32)
|
||||
} else {
|
||||
n + ('0' as u32)
|
||||
}) as char
|
||||
}
|
||||
|
||||
pub fn radix(n: i64, radix: i64) {
|
||||
fn r_str_radix(n: i64, radix: i64) {
|
||||
if n != 0 {
|
||||
r_str_radix(n / radix, radix) + as_digit(n % radix)
|
||||
} else ""
|
||||
}
|
||||
if n == 0 {
|
||||
"0"
|
||||
} else if n < 0 {
|
||||
// TODO: breaks at i64::MIN
|
||||
"-" + r_str_radix(-n, radix)
|
||||
} else r_str_radix(n, radix)
|
||||
}
|
||||
|
||||
pub fn hex(n: u64) {
|
||||
let out = "0x";
|
||||
for xd in min(count_leading_zeroes(n) / 4, 15)..16 {
|
||||
out += HEX_LUT[(n >> (15 - xd) * 4) & 0xf]
|
||||
out += as_digit((n >> (15 - xd) * 4) & 0xf)
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn oct(n: u64) {
|
||||
let out = "0o";
|
||||
for xd in min((count_leading_zeroes(n) + 2) / 3, 21)..22 {
|
||||
out += HEX_LUT[(n >> max(63 - (3 * xd), 0)) & 7]
|
||||
out += as_digit((n >> max(63 - (3 * xd), 0)) & 7)
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -25,7 +42,7 @@ pub fn oct(n: u64) {
|
||||
pub fn bin(n: u64) {
|
||||
let out = "0b";
|
||||
for xd in min(count_leading_zeroes(n), 63)..64 {
|
||||
out += HEX_LUT[(n >> 63 - xd) & 1]
|
||||
out += as_digit((n >> 63 - xd) & 1)
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
44
sample-code/letter_frequency.cl
Executable file
44
sample-code/letter_frequency.cl
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env -S conlang -r false
|
||||
// Showcases `get_line` behavior by sorting stdin
|
||||
|
||||
fn in_range(this: Ord, min: Ord, max: Ord) -> bool {
|
||||
min < this && this < max
|
||||
}
|
||||
|
||||
fn frequency(s: str) -> [i32; 128] {
|
||||
let letters = [0;128];
|
||||
for letter in s {
|
||||
if (letter).in_range(' ', letters.len() as char) {
|
||||
letters[(letter as i32)] += 1;
|
||||
}
|
||||
}
|
||||
letters
|
||||
}
|
||||
|
||||
fn plot_freq(freq: [i32; 128]) -> str {
|
||||
let buf = "";
|
||||
for idx in 0..len(freq) {
|
||||
for n in 0..(freq[idx]) {
|
||||
buf += idx as char;
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
const msg: str ="letter_frequency.cl
|
||||
Computes the frequency of ascii characters in a block of text, and prints it bucket-sorted.
|
||||
Press Ctrl+D to quit.";
|
||||
|
||||
fn main () {
|
||||
println(msg)
|
||||
let lines = "";
|
||||
loop {
|
||||
let line = get_line();
|
||||
if line == "" { break() }
|
||||
lines += line;
|
||||
}
|
||||
|
||||
let freq = frequency(lines)
|
||||
let plot = plot_freq(freq)
|
||||
println(plot)
|
||||
}
|
||||
18
sample-code/match_test.cl
Normal file
18
sample-code/match_test.cl
Normal file
@@ -0,0 +1,18 @@
|
||||
//! This is a Conlang library demonstrating `match`
|
||||
|
||||
struct Student {
|
||||
name: str,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
fn Student (name: str, age: i32) -> Student {
|
||||
Student: { name, age }
|
||||
}
|
||||
|
||||
fn match_test(student: Student) {
|
||||
match student {
|
||||
Student: { name: "shark", age } => println("Found a shark of ", age, " year(s)"),
|
||||
Student: { name, age: 22 } => println("Found a 22-year-old named ", name),
|
||||
Student: { name, age } => println("Found someone named ", name, " of ", age, " year(s)"),
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
//! Useful math functions
|
||||
|
||||
// FIXME:
|
||||
// These two functions shouldn't actually be polymorphic, but
|
||||
// the AST interpreter doesn't know about type annotations
|
||||
// or operator overloading.
|
||||
#[generic("T")]
|
||||
pub fn max(a: T, b: T) -> T {
|
||||
(if a < b { b } else { a })
|
||||
}
|
||||
|
||||
#[generic("T")]
|
||||
pub fn min(a: T, b: T) -> T {
|
||||
(if a > b { b } else { a })
|
||||
}
|
||||
|
||||
10
sample-code/pona.tp
Normal file
10
sample-code/pona.tp
Normal file
@@ -0,0 +1,10 @@
|
||||
//! toki!
|
||||
|
||||
nasin toki_pona(nimi: linja) -> linja {
|
||||
seme nimi {
|
||||
"a" => "PARTICLE: Emphasis or emotion",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
kiwen main: nasin(Linja) -> Linja = toki_pona;
|
||||
37
sample-code/rand.cl
Normal file
37
sample-code/rand.cl
Normal file
@@ -0,0 +1,37 @@
|
||||
//! Pseudo-random number generation using a LFSR algorithm
|
||||
|
||||
static state: u64 = 0xdeadbeefdeadbeef;
|
||||
|
||||
pub fn seed(seed: u64) {
|
||||
state = seed;
|
||||
}
|
||||
|
||||
pub fn lfsr_next() {
|
||||
state ^= state >> 7;
|
||||
state ^= state << 9;
|
||||
state ^= state >> 13;
|
||||
}
|
||||
|
||||
/// Returns a pseudorandom byte
|
||||
pub fn rand() -> u8 {
|
||||
for _ in 0..8 {
|
||||
lfsr_next()
|
||||
}
|
||||
state & 0xff
|
||||
}
|
||||
|
||||
// Prints a maze out of diagonal box drawing characters, ['╲', '╱']
|
||||
fn mazel(width: u64, height: u64) {
|
||||
let walls = ['\u{2571}', '\u{2572}'];
|
||||
rand_rect(width, height, walls)
|
||||
}
|
||||
|
||||
// Prints a rectangle with the provided walls
|
||||
fn rand_rect(width: u64, height: u64, walls: [char; 2]) {
|
||||
for _ in 0..height {
|
||||
for _ in 0..width {
|
||||
print(walls[rand() % 2])
|
||||
}
|
||||
println()
|
||||
}
|
||||
}
|
||||
56
sample-code/sqrt.cl
Executable file
56
sample-code/sqrt.cl
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env conlang-run
|
||||
//! Square root approximation, and example applications
|
||||
|
||||
/// A really small nonzero number
|
||||
const EPSILON: f64 = 8.8541878188 / 1000000000000.0;
|
||||
|
||||
/// Calcuates the absolute value of a number
|
||||
fn f64_abs(n: f64) -> f64 {
|
||||
let n = n as f64
|
||||
if n < (0.0) { -n } else { n }
|
||||
}
|
||||
|
||||
/// Square root approximation using Newton's method
|
||||
fn sqrt(n: f64) -> f64 {
|
||||
let n = n as f64
|
||||
if n < 0.0 {
|
||||
return 0.0 / 0.0 // TODO: NaN constant
|
||||
}
|
||||
if n == 0.0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
let z = n
|
||||
loop {
|
||||
let adj = (z * z - n) / (2.0 * z)
|
||||
z -= adj
|
||||
if adj.f64_abs() < EPSILON {
|
||||
break z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pythagorean theorem: a² + b² = c²
|
||||
fn pythag(a: f64, b: f64) -> f64 {
|
||||
sqrt(a * a + b * b)
|
||||
}
|
||||
|
||||
/// Quadratic formula: (-b ± √(b² - 4ac)) / 2a
|
||||
fn quadratic(a: f64, b: f64, c: f64) -> (f64, f64) {
|
||||
let a = a as f64; let b = b as f64; let c = c as f64;
|
||||
(
|
||||
(-b + sqrt(b * b - 4.0 * a * c)) / 2.0 * a,
|
||||
(-b - sqrt(b * b - 4.0 * a * c)) / 2.0 * a,
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for i in 0..10 {
|
||||
println("sqrt(",i,") ≅ ",sqrt(i as f64))
|
||||
}
|
||||
println("\nPythagorean Theorem")
|
||||
println("Hypotenuse of ⊿(5, 12): ", pythag(5.0, 12.0))
|
||||
|
||||
println("\nQuadratic formula")
|
||||
println("Roots of 10x² + 4x - 1: ", quadratic(10.0, 40, -1.0))
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
//! # The Conlang Standard Library
|
||||
|
||||
pub mod preamble {
|
||||
pub use super::num::*;
|
||||
pub use super::{num::*, str::str};
|
||||
}
|
||||
|
||||
pub mod num;
|
||||
|
||||
pub mod str;
|
||||
|
||||
#[cfg("test")]
|
||||
mod test;
|
||||
@@ -18,6 +18,9 @@ pub type i32;
|
||||
#[intrinsic = "i64"]
|
||||
pub type i64;
|
||||
|
||||
#[intrinsic = "i128"]
|
||||
pub type i128;
|
||||
|
||||
#[intrinsic = "isize"]
|
||||
pub type isize;
|
||||
|
||||
@@ -33,9 +36,18 @@ pub type u32;
|
||||
#[intrinsic = "u64"]
|
||||
pub type u64;
|
||||
|
||||
#[intrinsic = "u128"]
|
||||
pub type u128;
|
||||
|
||||
#[intrinsic = "usize"]
|
||||
pub type usize;
|
||||
|
||||
#[intrinsic = "f32"]
|
||||
pub type f32;
|
||||
|
||||
#[intrinsic = "f64"]
|
||||
pub type f64;
|
||||
|
||||
// Contains implementations for (TODO) overloaded operators on num types
|
||||
pub mod ops {
|
||||
use super::*;
|
||||
@@ -226,6 +238,45 @@ pub mod ops {
|
||||
}
|
||||
}
|
||||
|
||||
impl u128 {
|
||||
pub const MIN: Self = 0;
|
||||
pub const MAX: Self = !0;
|
||||
pub const BIT_WIDTH: u32 = 8;
|
||||
pub fn default() -> Self {
|
||||
0
|
||||
}
|
||||
pub fn mul(a: Self, b: Self) -> Self {
|
||||
a * b
|
||||
}
|
||||
pub fn div(a: Self, b: Self) -> Self {
|
||||
a / b
|
||||
}
|
||||
pub fn rem(a: Self, b: Self) -> Self {
|
||||
a % b
|
||||
}
|
||||
pub fn add(a: Self, b: Self) -> Self {
|
||||
a + b
|
||||
}
|
||||
pub fn sub(a: Self, b: Self) -> Self {
|
||||
a - b
|
||||
}
|
||||
pub fn shl(a: Self, b: u32) -> Self {
|
||||
a << b
|
||||
}
|
||||
pub fn shr(a: Self, b: u32) -> Self {
|
||||
a >> b
|
||||
}
|
||||
pub fn and(a: Self, b: Self) -> Self {
|
||||
a & b
|
||||
}
|
||||
pub fn or(a: Self, b: Self) -> Self {
|
||||
a | b
|
||||
}
|
||||
pub fn xor(a: Self, b: Self) -> Self {
|
||||
a ^ b
|
||||
}
|
||||
}
|
||||
|
||||
impl usize {
|
||||
pub const MIN: Self = __march_ptr_width_unsigned_min();
|
||||
pub const MAX: Self = __march_ptr_width_unsigned_max();
|
||||
@@ -421,6 +472,45 @@ pub mod ops {
|
||||
}
|
||||
}
|
||||
|
||||
impl i128 {
|
||||
pub const MIN: Self = !(1 << 128);
|
||||
pub const MAX: Self = 1 << 128;
|
||||
pub const BIT_WIDTH: u32 = 8;
|
||||
pub fn default() -> Self {
|
||||
0
|
||||
}
|
||||
pub fn mul(a: Self, b: Self) -> Self {
|
||||
a * b
|
||||
}
|
||||
pub fn div(a: Self, b: Self) -> Self {
|
||||
a / b
|
||||
}
|
||||
pub fn rem(a: Self, b: Self) -> Self {
|
||||
a % b
|
||||
}
|
||||
pub fn add(a: Self, b: Self) -> Self {
|
||||
a + b
|
||||
}
|
||||
pub fn sub(a: Self, b: Self) -> Self {
|
||||
a - b
|
||||
}
|
||||
pub fn shl(a: Self, b: u32) -> Self {
|
||||
a << b
|
||||
}
|
||||
pub fn shr(a: Self, b: u32) -> Self {
|
||||
a >> b
|
||||
}
|
||||
pub fn and(a: Self, b: Self) -> Self {
|
||||
a & b
|
||||
}
|
||||
pub fn or(a: Self, b: Self) -> Self {
|
||||
a | b
|
||||
}
|
||||
pub fn xor(a: Self, b: Self) -> Self {
|
||||
a ^ b
|
||||
}
|
||||
}
|
||||
|
||||
impl isize {
|
||||
pub const MIN: Self = __march_ptr_width_signed_min();
|
||||
pub const MAX: Self = __march_ptr_width_signed_max();
|
||||
4
stdlib/std/str.cl
Normal file
4
stdlib/std/str.cl
Normal file
@@ -0,0 +1,4 @@
|
||||
//! TODO: give conland a string type
|
||||
use super::num::u8;
|
||||
|
||||
type str = [u8];
|
||||
Reference in New Issue
Block a user