conlang: Move all cl-libs into the compiler directory
This commit is contained in:
22
compiler/cl-repl/Cargo.toml
Normal file
22
compiler/cl-repl/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "cl-repl"
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cl-ast = { path = "../cl-ast" }
|
||||
cl-lexer = { path = "../cl-lexer" }
|
||||
cl-token = { path = "../cl-token" }
|
||||
cl-parser = { path = "../cl-parser" }
|
||||
cl-interpret = { path = "../cl-interpret" }
|
||||
repline = { path = "../../repline" }
|
||||
argh = "0.1.12"
|
||||
|
||||
[dev-dependencies]
|
||||
cl-typeck = { path = "../cl-typeck" }
|
||||
69
compiler/cl-repl/examples/identify_tokens.rs
Normal file
69
compiler/cl-repl/examples/identify_tokens.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
|
||||
#![allow(unused_imports)]
|
||||
use cl_lexer::Lexer;
|
||||
use cl_token::Token;
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{stdin, IsTerminal, Read},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let conf = Config::new();
|
||||
if conf.paths.is_empty() {
|
||||
take_stdin()?;
|
||||
} else {
|
||||
for path in conf.paths.iter().map(PathBuf::as_path) {
|
||||
lex_tokens(&std::fs::read_to_string(path)?, Some(path))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Config {
|
||||
paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config { paths: std::env::args().skip(1).map(PathBuf::from).collect() }
|
||||
}
|
||||
}
|
||||
|
||||
fn take_stdin() -> Result<(), Box<dyn Error>> {
|
||||
if stdin().is_terminal() {
|
||||
for line in stdin().lines() {
|
||||
lex_tokens(&line?, None)?
|
||||
}
|
||||
} else {
|
||||
lex_tokens(&std::io::read_to_string(stdin())?, None)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lex_tokens(file: &str, path: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
for token in Lexer::new(file) {
|
||||
let token = match token {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
println!("{e:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Some(path) = path {
|
||||
print!("{path:?}:")
|
||||
}
|
||||
print_token(token);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_token(t: Token) {
|
||||
println!(
|
||||
"{:02}:{:02}: {:#19} │{}│",
|
||||
t.line(),
|
||||
t.col(),
|
||||
t.ty(),
|
||||
t.data(),
|
||||
)
|
||||
}
|
||||
179
compiler/cl-repl/examples/typeck.rs
Normal file
179
compiler/cl-repl/examples/typeck.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use cl_ast::{
|
||||
ast_visitor::fold::Fold,
|
||||
desugar::{squash_groups::SquashGroups, while_else::WhileElseDesugar},
|
||||
};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use cl_typeck::{
|
||||
definition::Def, name_collector::NameCollectable, project::Project, type_resolver::resolve,
|
||||
};
|
||||
use repline::{error::Error as RlError, prebaked::*};
|
||||
use std::error::Error;
|
||||
|
||||
// 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");
|
||||
|
||||
// Colors
|
||||
const C_MAIN: &str = "";
|
||||
const C_RESV: &str = "\x1b[35m";
|
||||
const C_CODE: &str = "\x1b[36m";
|
||||
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 = Project::default();
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(STDLIB));
|
||||
let code = match parser.file() {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{STDLIB_DISPLAY_PATH}:{e}");
|
||||
Err(e)?
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { TREES.push(code) }.collect_in_root(&mut prj)?;
|
||||
|
||||
main_menu(&mut prj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main_menu(prj: &mut Project) -> Result<(), RlError> {
|
||||
banner();
|
||||
read_and(C_MAIN, "mu>", "? >", |line| {
|
||||
match line.trim() {
|
||||
"c" | "code" => enter_code(prj)?,
|
||||
"clear" => clear()?,
|
||||
"e" | "exit" => return Ok(Response::Break),
|
||||
"l" | "list" => list_types(prj),
|
||||
"q" | "query" => query_type_expression(prj)?,
|
||||
"r" | "resolve" => resolve_all(prj)?,
|
||||
"d" | "desugar" => live_desugar()?,
|
||||
"h" | "help" => {
|
||||
println!(
|
||||
"Valid commands are:
|
||||
code (c): Enter code to type-check
|
||||
list (l): List all known types
|
||||
query (q): Query the type system
|
||||
resolve (r): Perform type resolution
|
||||
desugar (d): WIP: Test the experimental desugaring passes
|
||||
help (h): Print this list
|
||||
exit (e): Exit the program"
|
||||
);
|
||||
return Ok(Response::Deny);
|
||||
}
|
||||
_ => Err(r#"Invalid command. Type "help" to see the list of valid commands."#)?,
|
||||
}
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn enter_code(prj: &mut Project) -> Result<(), RlError> {
|
||||
read_and(C_CODE, "cl>", "? >", |line| {
|
||||
if line.trim().is_empty() {
|
||||
return Ok(Response::Break);
|
||||
}
|
||||
let code = Parser::new(Lexer::new(line)).file()?;
|
||||
let code = WhileElseDesugar.fold_file(code);
|
||||
// Safety: this is totally unsafe
|
||||
unsafe { TREES.push(code) }.collect_in_root(prj)?;
|
||||
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn live_desugar() -> Result<(), RlError> {
|
||||
read_and(C_RESV, "se>", "? >", |line| {
|
||||
let code = Parser::new(Lexer::new(line)).stmt()?;
|
||||
println!("Raw, as parsed:\n{C_LISTING}{code}\x1b[0m");
|
||||
|
||||
let code = SquashGroups.fold_stmt(code);
|
||||
println!("SquashGroups\n{C_LISTING}{code}\x1b[0m");
|
||||
|
||||
let code = WhileElseDesugar.fold_stmt(code);
|
||||
println!("WhileElseDesugar\n{C_LISTING}{code}\x1b[0m");
|
||||
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn query_type_expression(prj: &mut Project) -> 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 id = prj.evaluate(&ty, prj.root)?;
|
||||
pretty_def(&prj[id], id);
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_all(prj: &mut Project) -> Result<(), Box<dyn Error>> {
|
||||
for id in prj.pool.key_iter() {
|
||||
resolve(prj, id)?;
|
||||
}
|
||||
println!("Types resolved successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_types(prj: &mut Project) {
|
||||
println!(" name\x1b[30G type");
|
||||
for (idx, Def { name, vis, kind, .. }) in prj.pool.iter().enumerate() {
|
||||
print!("{idx:3}: {vis}");
|
||||
if name.is_empty() {
|
||||
print!("\x1b[30m_\x1b[0m")
|
||||
}
|
||||
println!("{name}\x1b[30G| {kind}");
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_def(def: &Def, id: impl Into<usize>) {
|
||||
let id = id.into();
|
||||
let Def { vis, name, kind, module, meta, source } = def;
|
||||
for meta in *meta {
|
||||
println!("#[{meta}]")
|
||||
}
|
||||
println!("{vis}{name} [id: {id}] = {kind}");
|
||||
if let Some(source) = source {
|
||||
println!("Source:\n{C_LISTING}{source}\x1b[0m");
|
||||
}
|
||||
println!("\x1b[90m{module}\x1b[0m");
|
||||
}
|
||||
|
||||
fn clear() -> Result<(), Box<dyn Error>> {
|
||||
println!("\x1b[H\x1b[2J");
|
||||
banner();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn banner() {
|
||||
println!(
|
||||
"--- {} v{} 💪🦈 ---",
|
||||
env!("CARGO_BIN_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
}
|
||||
|
||||
/// Keeps leaked references to past ASTs, for posterity:tm:
|
||||
struct TreeManager {
|
||||
trees: Vec<&'static cl_ast::File>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
656
compiler/cl-repl/examples/yaml.rs
Normal file
656
compiler/cl-repl/examples/yaml.rs
Normal file
@@ -0,0 +1,656 @@
|
||||
//! Pretty prints a conlang AST in yaml
|
||||
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use repline::{error::Error as RlError, Repline};
|
||||
use std::error::Error;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
|
||||
loop {
|
||||
let line = match rl.read() {
|
||||
Err(RlError::CtrlC(_)) => break,
|
||||
Err(RlError::CtrlD(line)) => {
|
||||
rl.deny();
|
||||
line
|
||||
}
|
||||
Ok(line) => line,
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
|
||||
let mut parser = Parser::new(Lexer::new(&line));
|
||||
let code = match parser.stmt() {
|
||||
Ok(code) => {
|
||||
rl.accept();
|
||||
code
|
||||
}
|
||||
Err(e) => {
|
||||
print!("\x1b[40G\x1bJ\x1b[91m{e}\x1b[0m");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
print!("\x1b[G\x1b[J\x1b[A");
|
||||
Yamler::new().yaml(&code);
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub use yamler::Yamler;
|
||||
pub mod yamler {
|
||||
use crate::yamlify::Yamlify;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::Write,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Yamler {
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl Yamler {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn indent(&mut self) -> Section {
|
||||
Section::new(self)
|
||||
}
|
||||
|
||||
/// Prints a [Yamlify] value
|
||||
#[inline]
|
||||
pub fn yaml<T: Yamlify>(&mut self, yaml: &T) -> &mut Self {
|
||||
yaml.yaml(self);
|
||||
self
|
||||
}
|
||||
|
||||
fn increase(&mut self) {
|
||||
self.depth += 1;
|
||||
}
|
||||
|
||||
fn decrease(&mut self) {
|
||||
self.depth -= 1;
|
||||
}
|
||||
|
||||
fn print_indentation(&self, writer: &mut impl Write) {
|
||||
for _ in 0..self.depth {
|
||||
let _ = write!(writer, " ");
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a section header and increases indentation
|
||||
pub fn key(&mut self, name: impl Display) -> Section {
|
||||
println!();
|
||||
self.print_indentation(&mut std::io::stdout().lock());
|
||||
print!("- {name}:");
|
||||
self.indent()
|
||||
}
|
||||
|
||||
/// Prints a yaml key value pair: `- name: "value"`
|
||||
pub fn pair<D: Display, T: Yamlify>(&mut self, name: D, value: T) -> &mut Self {
|
||||
self.key(name).yaml(&value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Prints a yaml scalar value: `"name"``
|
||||
pub fn value<D: Display>(&mut self, value: D) -> &mut Self {
|
||||
print!(" {value}");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list<D: Yamlify>(&mut self, list: &[D]) -> &mut Self {
|
||||
for (idx, value) in list.iter().enumerate() {
|
||||
self.pair(idx, value);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the start and end of an indented block (a "section")
|
||||
pub struct Section<'y> {
|
||||
yamler: &'y mut Yamler,
|
||||
}
|
||||
|
||||
impl<'y> Section<'y> {
|
||||
pub fn new(yamler: &'y mut Yamler) -> Self {
|
||||
yamler.increase();
|
||||
Self { yamler }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Deref for Section<'y> {
|
||||
type Target = Yamler;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
impl<'y> DerefMut for Section<'y> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.yamler
|
||||
}
|
||||
}
|
||||
|
||||
impl<'y> Drop for Section<'y> {
|
||||
fn drop(&mut self) {
|
||||
let Self { yamler } = self;
|
||||
yamler.decrease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod yamlify {
|
||||
use super::yamler::Yamler;
|
||||
use cl_ast::*;
|
||||
|
||||
pub trait Yamlify {
|
||||
fn yaml(&self, y: &mut Yamler);
|
||||
}
|
||||
|
||||
impl Yamlify for File {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let File { items } = self;
|
||||
y.key("File").yaml(items);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Visibility {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Visibility::Public = self {
|
||||
y.pair("vis", "pub");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for Mutability {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Mutability::Mut = self {
|
||||
y.pair("mut", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Yamlify for Attrs {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { meta } = self;
|
||||
y.key("Attrs").yaml(meta);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Meta {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Meta").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for MetaKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
MetaKind::Plain => y,
|
||||
MetaKind::Equals(value) => y.pair("equals", value),
|
||||
MetaKind::Func(args) => y.pair("args", args),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Yamlify for Item {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, attrs, vis, kind } = self;
|
||||
y.key("Item").yaml(attrs).yaml(vis).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ItemKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ItemKind::Alias(f) => y.yaml(f),
|
||||
ItemKind::Const(f) => y.yaml(f),
|
||||
ItemKind::Static(f) => y.yaml(f),
|
||||
ItemKind::Module(f) => y.yaml(f),
|
||||
ItemKind::Function(f) => y.yaml(f),
|
||||
ItemKind::Struct(f) => y.yaml(f),
|
||||
ItemKind::Enum(f) => y.yaml(f),
|
||||
ItemKind::Impl(f) => y.yaml(f),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Alias {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { to, from } = self;
|
||||
y.key("Alias").pair("to", to).pair("from", from);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Const {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, ty, init } = self;
|
||||
y.key("Const")
|
||||
.pair("name", name)
|
||||
.pair("ty", ty)
|
||||
.pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Static {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
y.key(name).yaml(mutable).pair("ty", ty).pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Module {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Module").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ModuleKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ModuleKind::Inline(f) => y.yaml(f),
|
||||
ModuleKind::Outline => y,
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Function {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, sign, bind, body } = self;
|
||||
y.key("Function")
|
||||
.pair("name", name)
|
||||
.pair("sign", sign)
|
||||
.pair("bind", bind)
|
||||
.pair("body", body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Struct {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Struct").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for StructKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
StructKind::Empty => y,
|
||||
StructKind::Tuple(k) => y.yaml(k),
|
||||
StructKind::Struct(k) => y.yaml(k),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for StructMember {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { vis, name, ty } = self;
|
||||
y.key("StructMember").yaml(vis).pair("name", name).yaml(ty);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Enum {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Enum").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for EnumKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
EnumKind::NoVariants => y,
|
||||
EnumKind::Variants(v) => y.yaml(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Variant {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { name, kind } = self;
|
||||
y.key("Variant").pair("name", name).yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for VariantKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
VariantKind::Plain => y,
|
||||
VariantKind::CLike(v) => y.yaml(v),
|
||||
VariantKind::Tuple(v) => y.yaml(v),
|
||||
VariantKind::Struct(v) => y.yaml(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Impl {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { target, body } = self;
|
||||
y.key("Impl").pair("target", target).pair("body", body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ImplKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ImplKind::Type(t) => y.value(t),
|
||||
ImplKind::Trait { impl_trait, for_type } => {
|
||||
y.pair("trait", impl_trait).pair("for_type", for_type)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Block {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { stmts } = self;
|
||||
y.key("Block").yaml(stmts);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Stmt {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind, semi } = self;
|
||||
y.key("Stmt").yaml(kind).yaml(semi);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Semi {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Semi::Terminated = self {
|
||||
y.pair("terminated", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for StmtKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
StmtKind::Empty => y,
|
||||
StmtKind::Local(s) => y.yaml(s),
|
||||
StmtKind::Item(s) => y.yaml(s),
|
||||
StmtKind::Expr(s) => y.yaml(s),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Let {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutable, name, ty, init } = self;
|
||||
y.key("Let")
|
||||
.pair("name", name)
|
||||
.yaml(mutable)
|
||||
.pair("ty", ty)
|
||||
.pair("init", init);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Expr {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind } = self;
|
||||
y.yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ExprKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
ExprKind::Assign(k) => k.yaml(y),
|
||||
ExprKind::Binary(k) => k.yaml(y),
|
||||
ExprKind::Unary(k) => k.yaml(y),
|
||||
ExprKind::Index(k) => k.yaml(y),
|
||||
ExprKind::Path(k) => k.yaml(y),
|
||||
ExprKind::Literal(k) => k.yaml(y),
|
||||
ExprKind::Array(k) => k.yaml(y),
|
||||
ExprKind::ArrayRep(k) => k.yaml(y),
|
||||
ExprKind::AddrOf(k) => k.yaml(y),
|
||||
ExprKind::Block(k) => k.yaml(y),
|
||||
ExprKind::Empty => {}
|
||||
ExprKind::Group(k) => k.yaml(y),
|
||||
ExprKind::Tuple(k) => k.yaml(y),
|
||||
ExprKind::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),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for Assign {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, parts } = self;
|
||||
y.key("Assign")
|
||||
.pair("kind", kind)
|
||||
.pair("head", &parts.0)
|
||||
.pair("tail", &parts.1);
|
||||
}
|
||||
}
|
||||
impl Yamlify for AssignKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Binary {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, parts } = self;
|
||||
y.key("Binary")
|
||||
.pair("kind", kind)
|
||||
.pair("head", &parts.0)
|
||||
.pair("tail", &parts.1);
|
||||
}
|
||||
}
|
||||
impl Yamlify for BinaryKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Unary {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { kind, tail } = self;
|
||||
y.key("Unary").pair("kind", kind).pair("tail", tail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for UnaryKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Tuple {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { exprs } = self;
|
||||
y.key("Tuple").list(exprs);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Index {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { head, indices } = self;
|
||||
y.key("Index").pair("head", head).list(indices);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Array {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { values } = self;
|
||||
y.key("Array").list(values);
|
||||
}
|
||||
}
|
||||
impl Yamlify for ArrayRep {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { value, repeat } = self;
|
||||
y.key("ArrayRep")
|
||||
.pair("value", value)
|
||||
.pair("repeat", repeat);
|
||||
}
|
||||
}
|
||||
impl Yamlify for AddrOf {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { count, mutable, expr } = self;
|
||||
y.key("AddrOf")
|
||||
.pair("count", count)
|
||||
.yaml(mutable)
|
||||
.pair("expr", expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Group {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { expr } = self;
|
||||
y.key("Group").yaml(expr);
|
||||
}
|
||||
}
|
||||
impl Yamlify for 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;
|
||||
y.key("While")
|
||||
.pair("cond", cond)
|
||||
.pair("pass", pass)
|
||||
.yaml(fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Else {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Else").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for If {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { cond, pass, fail } = self;
|
||||
y.key("If").pair("cond", cond).pair("pass", pass).yaml(fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for For {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { bind, cond, pass, fail } = self;
|
||||
y.key("For")
|
||||
.pair("bind", bind)
|
||||
.pair("cond", cond)
|
||||
.pair("pass", pass)
|
||||
.yaml(fail);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Break {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Break").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Return {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { body } = self;
|
||||
y.key("Return").yaml(body);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Continue {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.key("Continue");
|
||||
}
|
||||
}
|
||||
impl Yamlify for Literal {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(format_args!("\"{self}\""));
|
||||
}
|
||||
}
|
||||
impl Yamlify for Identifier {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self(name) = self;
|
||||
y.value(name);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Param {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { mutability, name } = self;
|
||||
y.key("Param").yaml(mutability).pair("name", name);
|
||||
}
|
||||
}
|
||||
impl Yamlify for Ty {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { extents: _, kind } = self;
|
||||
y.key("Ty").yaml(kind);
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyKind {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
TyKind::Never => y.value("Never"),
|
||||
TyKind::Empty => y.value("Empty"),
|
||||
TyKind::SelfTy => y.value("Self"),
|
||||
TyKind::Path(t) => y.yaml(t),
|
||||
TyKind::Tuple(t) => y.yaml(t),
|
||||
TyKind::Ref(t) => y.yaml(t),
|
||||
TyKind::Fn(t) => y.yaml(t),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for Path {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { absolute, parts } = self;
|
||||
let mut y = y.key("Path");
|
||||
if *absolute {
|
||||
y.pair("absolute", absolute);
|
||||
}
|
||||
for part in parts {
|
||||
y.pair("part", part);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for PathPart {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
match self {
|
||||
PathPart::SuperKw => y.value("super"),
|
||||
PathPart::SelfKw => y.value("self"),
|
||||
PathPart::Ident(i) => y.yaml(i),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyTuple {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { types } = self;
|
||||
let mut y = y.key("TyTuple");
|
||||
for ty in types {
|
||||
y.yaml(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyRef {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { count, mutable, to } = self;
|
||||
y.key("TyRef")
|
||||
.pair("count", count)
|
||||
.yaml(mutable)
|
||||
.pair("to", to);
|
||||
}
|
||||
}
|
||||
impl Yamlify for TyFn {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
let Self { args, rety } = self;
|
||||
y.key("TyFn").pair("args", args).pair("rety", rety);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Yamlify> Yamlify for Option<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
if let Some(v) = self {
|
||||
y.yaml(v);
|
||||
} else {
|
||||
y.value("");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Yamlify> Yamlify for Box<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.yaml(&**self);
|
||||
}
|
||||
}
|
||||
impl<T: Yamlify> Yamlify for Vec<T> {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
for thing in self {
|
||||
y.yaml(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Yamlify for () {
|
||||
fn yaml(&self, _y: &mut Yamler) {}
|
||||
}
|
||||
|
||||
impl<T: Yamlify> Yamlify for &T {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
(*self).yaml(y)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! scalar {
|
||||
($($t:ty),*$(,)?) => {
|
||||
$(impl Yamlify for $t {
|
||||
fn yaml(&self, y: &mut Yamler) {
|
||||
y.value(self);
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
scalar! {
|
||||
bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, &str, String
|
||||
}
|
||||
}
|
||||
14
compiler/cl-repl/src/ansi.rs
Normal file
14
compiler/cl-repl/src/ansi.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! ANSI escape sequences
|
||||
|
||||
pub const RED: &str = "\x1b[31m";
|
||||
pub const GREEN: &str = "\x1b[32m"; // the color of type checker mode
|
||||
pub const CYAN: &str = "\x1b[36m";
|
||||
pub const BRIGHT_GREEN: &str = "\x1b[92m";
|
||||
pub const BRIGHT_BLUE: &str = "\x1b[94m";
|
||||
pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
|
||||
pub const BRIGHT_CYAN: &str = "\x1b[96m";
|
||||
pub const RESET: &str = "\x1b[0m";
|
||||
pub const OUTPUT: &str = "\x1b[38;5;117m";
|
||||
|
||||
pub const CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
||||
pub const CLEAR_ALL: &str = "\x1b[H\x1b[2J";
|
||||
51
compiler/cl-repl/src/args.rs
Normal file
51
compiler/cl-repl/src/args.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
//! Handles argument parsing (currently using the [argh] crate)
|
||||
|
||||
use argh::FromArgs;
|
||||
use std::{io::IsTerminal, path::PathBuf, str::FromStr};
|
||||
|
||||
/// The Conlang prototype debug interface
|
||||
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Args {
|
||||
/// the main source file
|
||||
#[argh(positional)]
|
||||
pub file: Option<PathBuf>,
|
||||
|
||||
/// files to include
|
||||
#[argh(option, short = 'I')]
|
||||
pub include: Vec<PathBuf>,
|
||||
|
||||
/// the CLI operating mode (`f`mt | `l`ex | `r`un)
|
||||
#[argh(option, short = 'm', default = "Default::default()")]
|
||||
pub mode: Mode,
|
||||
|
||||
/// whether to start the repl (`true` or `false`)
|
||||
#[argh(option, short = 'r', default = "is_terminal()")]
|
||||
pub repl: bool,
|
||||
}
|
||||
|
||||
/// gets whether stdin AND stdout are a terminal, for pipelining
|
||||
pub fn is_terminal() -> bool {
|
||||
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
|
||||
}
|
||||
|
||||
/// The CLI's operating mode
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Menu,
|
||||
Lex,
|
||||
Fmt,
|
||||
Run,
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = &'static str;
|
||||
fn from_str(s: &str) -> Result<Self, &'static str> {
|
||||
Ok(match s {
|
||||
"f" | "fmt" | "p" | "pretty" => Mode::Fmt,
|
||||
"l" | "lex" | "tokenize" | "token" => Mode::Lex,
|
||||
"r" | "run" => Mode::Run,
|
||||
_ => Err("Recognized modes are: 'r' \"run\", 'f' \"fmt\", 'l' \"lex\"")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
5
compiler/cl-repl/src/bin/conlang.rs
Normal file
5
compiler/cl-repl/src/bin/conlang.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use cl_repl::cli::run;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run(argh::from_env())
|
||||
}
|
||||
89
compiler/cl-repl/src/cli.rs
Normal file
89
compiler/cl-repl/src/cli.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
//! Implement's the command line interface
|
||||
use crate::{
|
||||
args::{Args, Mode},
|
||||
ctx::Context,
|
||||
menu,
|
||||
tools::print_token,
|
||||
};
|
||||
use cl_interpret::{env::Environment, interpret::Interpret, temp_type_impl::ConValue};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use std::{error::Error, path::Path};
|
||||
|
||||
/// Run the command line interface
|
||||
pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
||||
let Args { file, include, mode, repl } = args;
|
||||
|
||||
let mut env = Environment::new();
|
||||
for path in include {
|
||||
load_file(&mut env, path)?;
|
||||
}
|
||||
|
||||
if repl {
|
||||
if let Some(file) = file {
|
||||
load_file(&mut env, file)?;
|
||||
}
|
||||
let mut ctx = Context::with_env(env);
|
||||
match mode {
|
||||
Mode::Menu => menu::main_menu(&mut ctx)?,
|
||||
Mode::Lex => menu::lex(&mut ctx)?,
|
||||
Mode::Fmt => menu::fmt(&mut ctx)?,
|
||||
Mode::Run => menu::run(&mut ctx)?,
|
||||
}
|
||||
} else {
|
||||
let code = match &file {
|
||||
Some(file) => std::fs::read_to_string(file)?,
|
||||
None => std::io::read_to_string(std::io::stdin())?,
|
||||
};
|
||||
|
||||
match mode {
|
||||
Mode::Lex => lex_code(&code, file),
|
||||
Mode::Fmt => fmt_code(&code),
|
||||
Mode::Run | Mode::Menu => run_code(&code, &mut env),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_file(
|
||||
env: &mut Environment,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<ConValue, Box<dyn Error>> {
|
||||
let file = std::fs::read_to_string(path)?;
|
||||
let code = Parser::new(Lexer::new(&file)).file()?;
|
||||
Ok(env.eval(&code)?)
|
||||
}
|
||||
|
||||
fn lex_code(code: &str, path: Option<impl AsRef<Path>>) -> Result<(), Box<dyn Error>> {
|
||||
for token in Lexer::new(code) {
|
||||
if let Some(path) = &path {
|
||||
print!("{}:", path.as_ref().display());
|
||||
}
|
||||
match token {
|
||||
Ok(token) => print_token(&token),
|
||||
Err(e) => println!("{e}"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fmt_code(code: &str) -> Result<(), Box<dyn Error>> {
|
||||
let code = Parser::new(Lexer::new(code)).file()?;
|
||||
println!("{code}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_code(code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
|
||||
let code = Parser::new(Lexer::new(code)).file()?;
|
||||
match code.interpret(env)? {
|
||||
ConValue::Empty => {}
|
||||
ret => println!("{ret}"),
|
||||
}
|
||||
if env.get("main").is_ok() {
|
||||
match env.call("main", &[])? {
|
||||
ConValue::Empty => {}
|
||||
ret => println!("{ret}"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
26
compiler/cl-repl/src/ctx.rs
Normal file
26
compiler/cl-repl/src/ctx.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use cl_interpret::{
|
||||
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Context {
|
||||
pub env: Environment,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
Self { env: Environment::new() }
|
||||
}
|
||||
pub fn with_env(env: Environment) -> Self {
|
||||
Self { env }
|
||||
}
|
||||
pub fn run(&mut self, code: &impl Interpret) -> IResult<ConValue> {
|
||||
code.interpret(&mut self.env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
11
compiler/cl-repl/src/lib.rs
Normal file
11
compiler/cl-repl/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! The Conlang REPL, based on [repline]
|
||||
//!
|
||||
//! Uses [argh] for argument parsing.
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod ansi;
|
||||
pub mod args;
|
||||
pub mod cli;
|
||||
pub mod ctx;
|
||||
pub mod menu;
|
||||
pub mod tools;
|
||||
76
compiler/cl-repl/src/menu.rs
Normal file
76
compiler/cl-repl/src/menu.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use crate::{ansi, ctx};
|
||||
use cl_lexer::Lexer;
|
||||
use cl_parser::Parser;
|
||||
use repline::{error::ReplResult, prebaked::*};
|
||||
|
||||
fn clear() {
|
||||
println!("{}", ansi::CLEAR_ALL);
|
||||
banner()
|
||||
}
|
||||
|
||||
pub fn banner() {
|
||||
println!("--- conlang v{} 💪🦈 ---\n", env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
/// Presents a selection interface to the user
|
||||
pub fn main_menu(ctx: &mut ctx::Context) -> ReplResult<()> {
|
||||
banner();
|
||||
read_and(ansi::GREEN, "mu>", " ?>", |line| {
|
||||
match line.trim() {
|
||||
"clear" => clear(),
|
||||
"l" | "lex" => lex(ctx)?,
|
||||
"f" | "fmt" => fmt(ctx)?,
|
||||
"r" | "run" => run(ctx)?,
|
||||
"q" | "quit" => return Ok(Response::Break),
|
||||
"h" | "help" => println!(
|
||||
"Valid commands
|
||||
lex (l): Spin up a lexer, and lex some lines
|
||||
fmt (f): Format the input
|
||||
run (r): Enter the REPL, and evaluate some statements
|
||||
help (h): Print this list
|
||||
quit (q): Exit the program"
|
||||
),
|
||||
_ => Err("Unknown command. Type \"help\" for help")?,
|
||||
}
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(ctx: &mut ctx::Context) -> ReplResult<()> {
|
||||
read_and(ansi::CYAN, "cl>", " ?>", |line| {
|
||||
let code = Parser::new(Lexer::new(line)).stmt()?;
|
||||
|
||||
print!("{}", ansi::OUTPUT);
|
||||
match ctx.run(&code) {
|
||||
Ok(v) => println!("{}{v}", ansi::RESET),
|
||||
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
|
||||
}
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lex(_ctx: &mut ctx::Context) -> ReplResult<()> {
|
||||
read_and(ansi::BRIGHT_BLUE, "lx>", " ?>", |line| {
|
||||
for token in Lexer::new(line) {
|
||||
match token {
|
||||
Ok(token) => crate::tools::print_token(&token),
|
||||
Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
|
||||
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() {
|
||||
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
11
compiler/cl-repl/src/tools.rs
Normal file
11
compiler/cl-repl/src/tools.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use cl_token::Token;
|
||||
/// Prints a token in the particular way [cl-repl](crate) does
|
||||
pub fn print_token(t: &Token) {
|
||||
println!(
|
||||
"{:02}:{:02}: {:#19} │{}│",
|
||||
t.line(),
|
||||
t.col(),
|
||||
t.ty(),
|
||||
t.data(),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user