cl-repl: Rename from cl-frontend.

Also disable escape code printing when debug assertions are off
This commit is contained in:
2024-02-28 05:32:10 -06:00
parent 048e41836a
commit 862d81a9d4
7 changed files with 10 additions and 6 deletions

14
cl-repl/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[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]
conlang = { path = "../libconlang" }
crossterm = "0.27.0"

View File

@@ -0,0 +1,523 @@
//! Collects identifiers into a list
use cl_repl::repline::Repline;
use conlang::{common::Loc, lexer::Lexer, parser::Parser};
use std::{
collections::HashMap,
error::Error,
fmt::Display,
ops::{Deref, DerefMut},
};
fn main() -> Result<(), Box<dyn Error>> {
let mut rl = Repline::new("\x1b[33m", "cl>", "? >");
while let Ok(line) = rl.read() {
let mut parser = Parser::new(Lexer::new(&line));
let code = match parser.stmt() {
Ok(code) => {
rl.accept();
code
}
Err(_) => {
continue;
}
};
let c = Collector::from(&code);
print!("\x1b[G\x1b[J{c}");
}
Ok(())
}
/// Ties the [Collector] location stack to the program stack
pub struct CollectorLoc<'loc, 'code> {
inner: &'loc mut Collector<'code>,
}
impl<'l, 'c> CollectorLoc<'l, 'c> {
pub fn new(c: &'l mut Collector<'c>, loc: Loc) -> Self {
c.location.push(loc);
Self { inner: c }
}
}
impl<'l, 'c> Deref for CollectorLoc<'l, 'c> {
type Target = Collector<'c>;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'l, 'c> DerefMut for CollectorLoc<'l, 'c> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}
impl<'l, 'c> Drop for CollectorLoc<'l, 'c> {
fn drop(&mut self) {
let Self { inner: c } = self;
c.location.pop();
}
}
#[derive(Clone, Debug, Default)]
pub struct Collector<'code> {
location: Vec<Loc>,
defs: HashMap<&'code str, Vec<Loc>>,
refs: HashMap<&'code str, Vec<Loc>>,
}
impl<'c> Collector<'c> {
pub fn new() -> Self {
Self::default()
}
pub fn location<'loc>(&'loc mut self, loc: Loc) -> CollectorLoc<'loc, 'c> {
CollectorLoc::new(self, loc)
}
pub fn collect(&mut self, collectible: &'c impl Collectible<'c>) -> &mut Self {
collectible.collect(self);
self
}
pub fn define(&mut self, name: &'c str) -> &mut Self {
// println!("Inserted definition of {name}");
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
self.defs
.entry(name)
.and_modify(|c| c.push(loc))
.or_insert_with(|| vec![loc]);
self
}
pub fn reference(&mut self, name: &'c str) -> &mut Self {
// println!("Inserted usage of {name}");
let loc = self.location.last().copied().unwrap_or(Loc(0, 0));
self.refs
.entry(name)
.and_modify(|c| c.push(loc))
.or_insert_with(|| vec![loc]);
self
}
}
impl<'c> Display for Collector<'c> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { location: _, defs, refs } = self;
writeln!(f, "Definitions:")?;
for (name, locs) in defs {
for loc in locs {
writeln!(f, "{loc} {name}")?;
}
}
writeln!(f, "Usages:")?;
for (name, locs) in refs {
for loc in locs {
writeln!(f, "{loc} {name}")?;
}
}
Ok(())
}
}
impl<'c, C: Collectible<'c>> From<&'c C> for Collector<'c> {
fn from(value: &'c C) -> Self {
let mut c = Self::new();
c.collect(value);
c
}
}
use collectible::Collectible;
pub mod collectible {
use super::Collector;
use conlang::ast::*;
pub trait Collectible<'code> {
fn collect(&'code self, c: &mut Collector<'code>);
}
impl<'c> Collectible<'c> for File {
fn collect(&'c self, c: &mut Collector<'c>) {
let File { items } = self;
for item in items {
item.collect(c)
}
}
}
impl<'c> Collectible<'c> for Item {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, attrs: _, vis: _, kind } = self;
kind.collect(&mut c.location(extents.head));
}
}
impl<'c> Collectible<'c> for ItemKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ItemKind::Alias(f) => f.collect(c),
ItemKind::Const(f) => f.collect(c),
ItemKind::Static(f) => f.collect(c),
ItemKind::Module(f) => f.collect(c),
ItemKind::Function(f) => f.collect(c),
ItemKind::Struct(f) => f.collect(c),
ItemKind::Enum(f) => f.collect(c),
ItemKind::Impl(f) => f.collect(c),
}
}
}
impl<'c> Collectible<'c> for Alias {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { to, from } = self;
c.collect(to).collect(from);
}
}
impl<'c> Collectible<'c> for Const {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), ty, init } = self;
c.define(name).collect(init).collect(ty);
}
}
impl<'c> Collectible<'c> for Static {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutable: _, name: Identifier(name), ty, init } = self;
c.define(name).collect(init).collect(ty);
}
}
impl<'c> Collectible<'c> for Module {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for ModuleKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ModuleKind::Inline(f) => f.collect(c),
ModuleKind::Outline => {}
}
}
}
impl<'c> Collectible<'c> for Function {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), args, body, rety } = self;
c.define(name).collect(args).collect(body).collect(rety);
}
}
impl<'c> Collectible<'c> for Struct {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for StructKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
StructKind::Empty => {}
StructKind::Tuple(k) => k.collect(c),
StructKind::Struct(k) => k.collect(c),
}
}
}
impl<'c> Collectible<'c> for StructMember {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { vis: _, name: Identifier(name), ty } = self;
c.define(name).collect(ty);
}
}
impl<'c> Collectible<'c> for Enum {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for EnumKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
EnumKind::NoVariants => {}
EnumKind::Variants(v) => v.collect(c),
}
}
}
impl<'c> Collectible<'c> for Variant {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { name: Identifier(name), kind } = self;
c.define(name).collect(kind);
}
}
impl<'c> Collectible<'c> for VariantKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
VariantKind::Plain => {}
VariantKind::CLike(_) => {}
VariantKind::Tuple(v) => v.collect(c),
VariantKind::Struct(v) => v.collect(c),
}
}
}
impl<'c> Collectible<'c> for Impl {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { target, body } = self;
c.collect(target).collect(body);
}
}
impl<'c> Collectible<'c> for Block {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { stmts } = self;
for stmt in stmts {
stmt.collect(c);
}
}
}
impl<'c> Collectible<'c> for Stmt {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind, semi: _ } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for StmtKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
StmtKind::Empty => {}
StmtKind::Local(s) => s.collect(c),
StmtKind::Item(s) => s.collect(c),
StmtKind::Expr(s) => s.collect(c),
}
}
}
impl<'c> Collectible<'c> for Let {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutable: _, name: Identifier(name), ty, init } = self;
c.collect(init).collect(ty).define(name);
}
}
impl<'c> Collectible<'c> for Expr {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for ExprKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
ExprKind::Assign(k) => k.collect(c),
ExprKind::Binary(k) => k.collect(c),
ExprKind::Unary(k) => k.collect(c),
ExprKind::Member(k) => k.collect(c),
ExprKind::Call(k) => k.collect(c),
ExprKind::Index(k) => k.collect(c),
ExprKind::Path(k) => k.collect(c),
ExprKind::Literal(k) => k.collect(c),
ExprKind::Array(k) => k.collect(c),
ExprKind::ArrayRep(k) => k.collect(c),
ExprKind::AddrOf(k) => k.collect(c),
ExprKind::Block(k) => k.collect(c),
ExprKind::Empty => {}
ExprKind::Group(k) => k.collect(c),
ExprKind::Tuple(k) => k.collect(c),
ExprKind::While(k) => k.collect(c),
ExprKind::If(k) => k.collect(c),
ExprKind::For(k) => k.collect(c),
ExprKind::Break(k) => k.collect(c),
ExprKind::Return(k) => k.collect(c),
ExprKind::Continue(k) => k.collect(c),
}
}
}
impl<'c> Collectible<'c> for Assign {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, op: _, tail } = self;
c.collect(head).collect(tail);
}
}
impl<'c> Collectible<'c> for Binary {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, tail } = self;
c.collect(head);
for (_, tail) in tail {
c.collect(tail);
}
}
}
impl<'c> Collectible<'c> for Unary {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { ops: _, tail } = self;
c.collect(tail);
}
}
impl<'c> Collectible<'c> for Member {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, tail } = self;
c.collect(head).collect(tail);
}
}
impl<'c> Collectible<'c> for Call {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { callee, args } = self;
c.collect(callee).collect(args);
}
}
impl<'c> Collectible<'c> for Tuple {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { exprs } = self;
c.collect(exprs);
}
}
impl<'c> Collectible<'c> for Index {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { head, indices } = self;
c.collect(head).collect(indices);
}
}
impl<'c> Collectible<'c> for Indices {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { exprs } = self;
c.collect(exprs);
}
}
impl<'c> Collectible<'c> for Array {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { values } = self;
c.collect(values);
}
}
impl<'c> Collectible<'c> for ArrayRep {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { value, repeat } = self;
c.collect(value).collect(repeat);
}
}
impl<'c> Collectible<'c> for AddrOf {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { count: _, mutable: _, expr } = self;
c.collect(expr);
}
}
impl<'c> Collectible<'c> for Group {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { expr } = self;
c.collect(expr);
}
}
impl<'c> Collectible<'c> for While {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { cond, pass, fail } = self;
c.collect(cond).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for Else {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for If {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { cond, pass, fail } = self;
c.collect(cond).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for For {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { bind: Identifier(name), cond, pass, fail } = self;
c.collect(cond).define(name).collect(pass).collect(fail);
}
}
impl<'c> Collectible<'c> for Break {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for Return {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { body } = self;
c.collect(body);
}
}
impl<'c> Collectible<'c> for Continue {
fn collect(&'c self, _c: &mut Collector<'c>) {}
}
impl<'c> Collectible<'c> for Literal {
fn collect(&'c self, _c: &mut Collector<'c>) {}
}
impl<'c> Collectible<'c> for Identifier {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self(name) = self;
c.reference(name);
}
}
impl<'c> Collectible<'c> for Param {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { mutability: _, name, ty } = self;
c.collect(name).collect(ty);
}
}
impl<'c> Collectible<'c> for Ty {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { extents, kind } = self;
c.location(extents.head).collect(kind);
}
}
impl<'c> Collectible<'c> for TyKind {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
TyKind::Never => {}
TyKind::Empty => {}
TyKind::SelfTy => {}
TyKind::Path(t) => t.collect(c),
TyKind::Tuple(t) => t.collect(c),
TyKind::Ref(t) => t.collect(c),
TyKind::Fn(t) => t.collect(c),
}
}
}
impl<'c> Collectible<'c> for Path {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { absolute: _, parts } = self;
for part in parts {
c.collect(part);
}
}
}
impl<'c> Collectible<'c> for PathPart {
fn collect(&'c self, c: &mut Collector<'c>) {
match self {
PathPart::SuperKw => {}
PathPart::SelfKw => {}
PathPart::Ident(i) => i.collect(c),
}
}
}
impl<'c> Collectible<'c> for TyTuple {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { types } = self;
for ty in types {
c.collect(ty);
}
}
}
impl<'c> Collectible<'c> for TyRef {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { count: _, to } = self;
c.collect(to);
}
}
impl<'c> Collectible<'c> for TyFn {
fn collect(&'c self, c: &mut Collector<'c>) {
let Self { args, rety } = self;
c.collect(args).collect(rety);
}
}
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Vec<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
for i in self {
c.collect(i);
}
}
}
impl<'c, C: Collectible<'c> + Sized> Collectible<'c> for Option<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
if let Some(i) = self {
c.collect(i);
}
}
}
impl<'c, C: Collectible<'c>> Collectible<'c> for Box<C> {
fn collect(&'c self, c: &mut Collector<'c>) {
c.collect(self.as_ref());
}
}
}

View File

@@ -0,0 +1,68 @@
//! This example grabs input from stdin, lexes it, and prints which lexer rules matched
#![allow(unused_imports)]
use conlang::lexer::Lexer;
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: conlang::token::Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}

501
cl-repl/src/lib.rs Normal file
View File

@@ -0,0 +1,501 @@
//! Utilities for cl-frontend
//!
//! # TODO
//! - [ ] Readline-like line editing
//! - [ ] Raw mode?
pub mod args {
use crate::cli::Mode;
use std::{
io::{stdin, IsTerminal},
ops::Deref,
path::{Path, PathBuf},
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args {
pub path: Option<PathBuf>, // defaults None
pub repl: bool, // defaults true if stdin is terminal
pub mode: Mode, // defaults Interpret
}
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
impl Args {
pub fn new() -> Self {
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret }
}
pub fn parse(mut self) -> Option<Self> {
let mut args = std::env::args();
let name = args.next().unwrap_or_default();
let mut unknown = false;
while let Some(arg) = args.next() {
match arg.deref() {
"--repl" => self.repl = true,
"--no-repl" => self.repl = false,
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
"-m" | "--mode" => {
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default()
}
arg => {
eprintln!("Unknown argument: {arg}");
unknown = true;
}
}
}
if unknown {
println!("Usage: {name} {HELP}");
None?
}
Some(self)
}
/// Returns the path to a file, if one was specified
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
/// Returns whether to start a REPL session or not
pub fn repl(&self) -> bool {
self.repl
}
/// Returns the repl Mode
pub fn mode(&self) -> Mode {
self.mode
}
}
impl Default for Args {
fn default() -> Self {
Self::new()
}
}
}
pub mod program {
use std::{fmt::Display, io::Write};
use conlang::{
ast::{self, ast_impl::format::Pretty},
interpreter::{
env::Environment, error::IResult, interpret::Interpret, temp_type_impl::ConValue,
},
// pretty_printer::{PrettyPrintable, Printer},
lexer::Lexer,
parser::{error::PResult, Parser},
resolver::{error::TyResult, Resolver},
};
pub struct Parsable;
pub enum Parsed {
File(ast::File),
Stmt(ast::Stmt),
Expr(ast::Expr),
}
pub struct Program<'t, Variant> {
text: &'t str,
data: Variant,
}
impl<'t, V> Program<'t, V> {
pub fn lex(&self) -> Lexer {
Lexer::new(self.text)
}
}
impl<'t> Program<'t, Parsable> {
pub fn new(text: &'t str) -> Self {
Self { text, data: Parsable }
}
pub fn parse(self) -> PResult<Program<'t, Parsed>> {
self.parse_file().or_else(|_| self.parse_stmt())
}
pub fn parse_expr(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Expr(Parser::new(self.lex()).expr()?), text: self.text })
}
pub fn parse_stmt(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::Stmt(Parser::new(self.lex()).stmt()?), text: self.text })
}
pub fn parse_file(&self) -> PResult<Program<'t, Parsed>> {
Ok(Program { data: Parsed::File(Parser::new(self.lex()).file()?), text: self.text })
}
}
impl<'t> Program<'t, Parsed> {
pub fn debug(&self) {
match &self.data {
Parsed::File(v) => eprintln!("{v:?}"),
Parsed::Stmt(v) => eprintln!("{v:?}"),
Parsed::Expr(v) => eprintln!("{v:?}"),
}
}
pub fn print(&self) {
let mut f = std::io::stdout().pretty();
let _ = match &self.data {
Parsed::File(v) => writeln!(f, "{v}"),
Parsed::Stmt(v) => writeln!(f, "{v}"),
Parsed::Expr(v) => writeln!(f, "{v}"),
};
// println!("{self}")
}
pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
todo!("Program::resolve(\n{self},\n{resolver:?}\n)")
}
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
match &self.data {
Parsed::File(v) => v.interpret(env),
Parsed::Stmt(v) => v.interpret(env),
Parsed::Expr(v) => v.interpret(env),
}
}
// pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
// match &mut self.data {
// Parsed::Program(start) => start.resolve(resolver),
// Parsed::Expr(expr) => expr.resolve(resolver),
// }
// .map(|ty| println!("{ty}"))
// }
// /// Runs the [Program] in the specified [Environment]
// pub fn run(&self, env: &mut Environment) -> IResult<()> {
// println!(
// "{}",
// match &self.data {
// Parsed::Program(start) => env.eval(start)?,
// Parsed::Expr(expr) => env.eval(expr)?,
// }
// );
// Ok(())
// }
}
impl<'t> Display for Program<'t, Parsed> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.data {
Parsed::File(v) => write!(f, "{v}"),
Parsed::Stmt(v) => write!(f, "{v}"),
Parsed::Expr(v) => write!(f, "{v}"),
}
}
}
// impl PrettyPrintable for Program<Parsed> {
// fn visit<W: Write>(&self, p: &mut Printer<W>) -> IOResult<()> {
// match &self.data {
// Parsed::Program(value) => value.visit(p),
// Parsed::Expr(value) => value.visit(p),
// }
// }
// }
}
pub mod cli {
use conlang::{interpreter::env::Environment, resolver::Resolver, token::Token};
use crate::{
args::Args,
program::{Parsable, Parsed, Program},
};
use std::{
convert::Infallible,
error::Error,
path::{Path, PathBuf},
str::FromStr,
};
// ANSI color escape sequences
const ANSI_RED: &str = "\x1b[31m";
const ANSI_GREEN: &str = "\x1b[32m";
const ANSI_CYAN: &str = "\x1b[36m";
const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
const ANSI_RESET: &str = "\x1b[0m";
const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
#[derive(Clone, Debug)]
pub enum CLI {
Repl(Repl),
File { mode: Mode, path: PathBuf },
Stdin { mode: Mode },
}
impl From<Args> for CLI {
fn from(value: Args) -> Self {
let Args { path, repl, mode } = value;
match (repl, path) {
(_, Some(path)) => Self::File { mode, path },
(true, None) => Self::Repl(Repl { mode, ..Default::default() }),
(false, None) => Self::Stdin { mode },
}
}
}
impl CLI {
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
use std::{fs, io};
match self {
CLI::Repl(repl) => repl.repl(),
CLI::File { mode, ref path } => {
// read file
Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?)
}
CLI::Stdin { mode } => {
Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?)
}
}
Ok(())
}
fn no_repl(mode: Mode, path: Option<&Path>, code: &str) {
let program = Program::new(code);
match mode {
Mode::Tokenize => {
for token in program.lex() {
if let Some(path) = path {
print!("{}:", path.display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
}
Mode::Beautify => Self::beautify(program),
Mode::Resolve => Self::resolve(program, Default::default()),
Mode::Interpret => Self::interpret(program, Environment::new()),
}
}
fn beautify(program: Program<Parsable>) {
match program.parse() {
Ok(program) => program.print(),
Err(e) => eprintln!("{e}"),
};
}
fn resolve(program: Program<Parsable>, mut resolver: Resolver) {
let mut program = match program.parse() {
Ok(program) => program,
Err(e) => {
eprintln!("{e}");
return;
}
};
if let Err(e) = program.resolve(&mut resolver) {
eprintln!("{e}");
}
}
fn interpret(program: Program<Parsable>, mut interpreter: Environment) {
let program = match program.parse() {
Ok(program) => program,
Err(e) => {
eprintln!("{e}");
return;
}
};
if let Err(e) = program.run(&mut interpreter) {
eprintln!("{e}");
return;
}
if let Err(e) = interpreter.call("main", &[]) {
eprintln!("{e}");
}
}
}
/// The CLI's operating mode
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mode {
Tokenize,
Beautify,
Resolve,
#[default]
Interpret,
}
impl Mode {
pub fn ansi_color(self) -> &'static str {
match self {
Mode::Tokenize => ANSI_BRIGHT_BLUE,
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
Mode::Resolve => ANSI_GREEN,
Mode::Interpret => ANSI_CYAN,
}
}
}
impl FromStr for Mode {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Infallible> {
Ok(match s {
"i" | "interpret" | "run" => Mode::Interpret,
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
"t" | "tokenize" | "tokens" => Mode::Tokenize,
_ => Mode::Interpret,
})
}
}
/// Implements the interactive interpreter
#[derive(Clone, Debug)]
pub struct Repl {
prompt_again: &'static str, // " ?>"
prompt_begin: &'static str, // "cl>"
prompt_error: &'static str, // "! >"
env: Environment,
resolver: Resolver,
mode: Mode,
}
impl Default for Repl {
fn default() -> Self {
Self {
prompt_begin: "cl>",
prompt_again: " ?>",
prompt_error: "! >",
env: Default::default(),
resolver: Default::default(),
mode: Default::default(),
}
}
}
/// Prompt functions
impl Repl {
pub fn prompt_error(&self, err: &impl Error) {
let Self { prompt_error: prompt, .. } = self;
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}",)
}
/// Resets the cursor to the start of the line, clears the terminal,
/// and sets the output color
pub fn begin_output(&self) {
print!("{ANSI_CLEAR_LINES}{ANSI_OUTPUT}")
}
pub fn clear_line(&self) {}
}
/// The actual REPL
impl Repl {
/// Constructs a new [Repl] with the provided [Mode]
pub fn new(mode: Mode) -> Self {
Self { mode, ..Default::default() }
}
/// Runs the main REPL loop
pub fn repl(&mut self) {
use crate::repline::{error::Error, Repline};
let mut rl = Repline::new(self.mode.ansi_color(), self.prompt_begin, self.prompt_again);
fn clear_line() {
print!("\x1b[G\x1b[J");
}
loop {
let buf = match rl.read() {
Ok(buf) => buf,
// Ctrl-C: break if current line is empty
Err(Error::CtrlC(buf)) => {
if buf.is_empty() || buf.ends_with('\n') {
return;
}
rl.accept();
println!("Cancelled. (Press Ctrl+C again to quit.)");
continue;
}
// Ctrl-D: reset input, and parse it for errors
Err(Error::CtrlD(buf)) => {
rl.deny();
if let Err(e) = Program::new(&buf).parse() {
clear_line();
self.prompt_error(&e);
}
continue;
}
Err(e) => {
self.prompt_error(&e);
return;
}
};
self.begin_output();
if self.command(&buf) {
rl.deny();
rl.set_color(self.mode.ansi_color());
continue;
}
let code = Program::new(&buf);
if self.mode == Mode::Tokenize {
self.tokenize(&code);
rl.deny();
continue;
}
match code.lex().into_iter().find(|l| l.is_err()) {
None => {}
Some(Ok(_)) => unreachable!(),
Some(Err(error)) => {
rl.deny();
self.prompt_error(&error);
continue;
}
}
if let Ok(mut code) = code.parse() {
rl.accept();
self.dispatch(&mut code);
}
}
}
fn help(&self) {
println!(
"Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message"
);
}
fn command(&mut self, line: &str) -> bool {
match line.trim() {
"$pretty" => self.mode = Mode::Beautify,
"$tokens" => self.mode = Mode::Tokenize,
"$type" => self.mode = Mode::Resolve,
"$run" => self.mode = Mode::Interpret,
"$mode" => println!("{:?} Mode", self.mode),
"$help" => self.help(),
_ => return false,
}
true
}
/// Dispatches calls to repl functions based on the program
fn dispatch(&mut self, code: &mut Program<Parsed>) {
match self.mode {
Mode::Tokenize => (),
Mode::Beautify => self.beautify(code),
Mode::Resolve => self.typecheck(code),
Mode::Interpret => self.interpret(code),
}
}
fn tokenize(&mut self, code: &Program<Parsable>) {
for token in code.lex() {
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
}
}
}
fn interpret(&mut self, code: &Program<Parsed>) {
if let Err(e) = code.run(&mut self.env) {
self.prompt_error(&e)
}
}
fn typecheck(&mut self, code: &mut Program<Parsed>) {
if let Err(e) = code.resolve(&mut self.resolver) {
self.prompt_error(&e)
}
}
fn beautify(&mut self, code: &Program<Parsed>) {
code.print()
}
}
fn print_token(t: &Token) {
println!(
"{:02}:{:02}: {:#19}{}",
t.line(),
t.col(),
t.ty(),
t.data(),
)
}
}
pub mod repline;

6
cl-repl/src/main.rs Normal file
View File

@@ -0,0 +1,6 @@
use cl_repl::{args::Args, cli::CLI};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
CLI::from(Args::new().parse().unwrap_or_default()).run()
}

636
cl-repl/src/repline.rs Normal file
View File

@@ -0,0 +1,636 @@
//! A small pseudo-multiline editing library
// #![allow(unused)]
pub mod error {
/// Result type for Repline
pub type ReplResult<T> = std::result::Result<T, Error>;
/// Borrowed error (does not implement [Error](std::error::Error)!)
#[derive(Debug)]
pub enum Error {
/// User broke with Ctrl+C
CtrlC(String),
/// User broke with Ctrl+D
CtrlD(String),
/// Invalid unicode codepoint
BadUnicode(u32),
/// Error came from [std::io]
IoFailure(std::io::Error),
/// End of input
EndOfInput,
}
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::CtrlC(_) => write!(f, "Ctrl+C"),
Error::CtrlD(_) => write!(f, "Ctrl+D"),
Error::BadUnicode(u) => write!(f, "0x{u:x} is not a valid unicode codepoint"),
Error::IoFailure(s) => write!(f, "{s}"),
Error::EndOfInput => write!(f, "End of input"),
}
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::IoFailure(value)
}
}
}
pub mod ignore {
//! Does nothing, universally.
//!
//! Introduces the [Ignore] trait, and its singular function, [ignore](Ignore::ignore),
//! which does nothing.
impl<T> Ignore for T {}
/// Does nothing
///
/// # Examples
/// ```rust
/// #![deny(unused_must_use)]
/// # use cl_frontend::repline::ignore::Ignore;
/// ().ignore();
/// Err::<(), &str>("Foo").ignore();
/// Some("Bar").ignore();
/// 42.ignore();
///
/// #[must_use]
/// fn the_meaning() -> usize {
/// 42
/// }
/// the_meaning().ignore();
/// ```
pub trait Ignore {
/// Does nothing
fn ignore(&self) {}
}
}
pub mod chars {
//! Converts an <code>[Iterator]<Item = [u8]></code> into an
//! <code>[Iterator]<Item = [char]></code>
use super::error::*;
/// Converts an <code>[Iterator]<Item = [u8]></code> into an
/// <code>[Iterator]<Item = [char]></code>
#[derive(Clone, Debug)]
pub struct Chars<I: Iterator<Item = u8>>(pub I);
impl<I: Iterator<Item = u8>> Chars<I> {
pub fn new(bytes: I) -> Self {
Self(bytes)
}
}
impl<I: Iterator<Item = u8>> Iterator for Chars<I> {
type Item = ReplResult<char>;
fn next(&mut self) -> Option<Self::Item> {
let Self(bytes) = self;
let start = bytes.next()? as u32;
let (mut out, count) = match start {
start if start & 0x80 == 0x00 => (start, 0), // ASCII valid range
start if start & 0xe0 == 0xc0 => (start & 0x1f, 1), // 1 continuation byte
start if start & 0xf0 == 0xe0 => (start & 0x0f, 2), // 2 continuation bytes
start if start & 0xf8 == 0xf0 => (start & 0x07, 3), // 3 continuation bytes
_ => return None,
};
for _ in 0..count {
let cont = bytes.next()? as u32;
if cont & 0xc0 != 0x80 {
return None;
}
out = out << 6 | (cont & 0x3f);
}
Some(char::from_u32(out).ok_or(Error::BadUnicode(out)))
}
}
}
pub mod flatten {
//! Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
//! into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
/// Flattens an [Iterator] returning [`Result<T, E>`](Result) or [`Option<T>`](Option)
/// into a *non-[FusedIterator](std::iter::FusedIterator)* over `T`
pub struct Flatten<T, I: Iterator<Item = T>>(pub I);
impl<T, E, I: Iterator<Item = Result<T, E>>> Iterator for Flatten<Result<T, E>, I> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()?.ok()
}
}
impl<T, I: Iterator<Item = Option<T>>> Iterator for Flatten<Option<T>, I> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()?
}
}
}
pub mod raw {
//! Sets the terminal to [`raw`] mode for the duration of the returned object's lifetime.
/// Sets the terminal to raw mode for the duration of the returned object's lifetime.
pub fn raw() -> impl Drop {
Raw::default()
}
struct Raw();
impl Default for Raw {
fn default() -> Self {
std::thread::yield_now();
crossterm::terminal::enable_raw_mode()
.expect("should be able to transition into raw mode");
Raw()
}
}
impl Drop for Raw {
fn drop(&mut self) {
crossterm::terminal::disable_raw_mode()
.expect("should be able to transition out of raw mode");
// std::thread::yield_now();
}
}
}
mod out {
#![allow(unused)]
use std::io::{Result, Write};
/// A [Writer](Write) that flushes after every wipe
#[derive(Clone, Debug)]
pub(super) struct EagerWriter<W: Write> {
out: W,
}
impl<W: Write> EagerWriter<W> {
pub fn new(writer: W) -> Self {
Self { out: writer }
}
}
impl<W: Write> Write for EagerWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let out = self.out.write(buf)?;
self.out.flush()?;
Ok(out)
}
fn flush(&mut self) -> Result<()> {
self.out.flush()
}
}
}
use self::{chars::Chars, editor::Editor, error::*, flatten::Flatten, ignore::Ignore, raw::raw};
use std::{
collections::VecDeque,
io::{stdout, Bytes, Read, Result, Write},
};
pub struct Repline<'a, R: Read> {
input: Chars<Flatten<Result<u8>, Bytes<R>>>,
history: VecDeque<String>, // previous lines
hindex: usize, // current index into the history buffer
ed: Editor<'a>, // the current line buffer
}
impl<'a, R: Read> Repline<'a, R> {
/// Constructs a [Repline] with the given [Reader](Read), color, begin, and again prompts.
pub fn with_input(input: R, color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self {
input: Chars(Flatten(input.bytes())),
history: Default::default(),
hindex: 0,
ed: Editor::new(color, begin, again),
}
}
/// Set the terminal prompt color
pub fn set_color(&mut self, color: &'a str) {
self.ed.color = color
}
/// Reads in a line, and returns it for validation
pub fn read(&mut self) -> ReplResult<String> {
const INDENT: &str = " ";
let mut stdout = stdout().lock();
let stdout = &mut stdout;
let _make_raw = raw();
// self.ed.begin_frame(stdout)?;
// self.ed.redraw_frame(stdout)?;
self.ed.print_head(stdout)?;
loop {
stdout.flush()?;
match self.input.next().ok_or(Error::EndOfInput)?? {
// Ctrl+C: End of Text. Immediately exits.
// Ctrl+D: End of Transmission. Ends the current line.
'\x03' => {
drop(_make_raw);
writeln!(stdout)?;
return Err(Error::CtrlC(self.ed.to_string()));
}
'\x04' => {
drop(_make_raw);
writeln!(stdout)?;
return Err(Error::CtrlD(self.ed.to_string()));
}
// Tab: extend line by 4 spaces
'\t' => {
self.ed.extend(INDENT.chars(), stdout)?;
}
// ignore newlines, process line feeds. Not sure how cross-platform this is.
'\n' => {}
'\r' => {
self.ed.push('\n', stdout)?;
return Ok(self.ed.to_string());
}
// Escape sequence
'\x1b' => self.escape(stdout)?,
// backspace
'\x08' | '\x7f' => {
let ed = &mut self.ed;
if ed.ends_with(INDENT.chars()) {
for _ in 0..INDENT.len() {
ed.pop(stdout)?;
}
} else {
ed.pop(stdout)?;
}
}
c if c.is_ascii_control() => {
if cfg!(debug_assertions) {
eprint!("\\x{:02x}", c as u32);
}
}
c => {
self.ed.push(c, stdout)?;
}
}
}
}
/// Handle ANSI Escape
fn escape<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
match self.input.next().ok_or(Error::EndOfInput)?? {
'[' => self.csi(w)?,
'O' => todo!("Process alternate character mode"),
other => self.ed.extend(['\x1b', other], w)?,
}
Ok(())
}
/// Handle ANSI Control Sequence Introducer
fn csi<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
match self.input.next().ok_or(Error::EndOfInput)?? {
'A' => {
self.hindex = self.hindex.saturating_sub(1);
self.restore_history(w)?
}
'B' => {
self.hindex = self
.hindex
.saturating_add(1)
.min(self.history.len().saturating_sub(1));
self.restore_history(w)?
}
'C' => self.ed.cursor_forward(1, w)?,
'D' => self.ed.cursor_back(1, w)?,
'H' => self.ed.home(w)?,
'F' => self.ed.end(w)?,
'3' => {
if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
self.ed.delete(w).ignore()
}
}
other => {
if cfg!(debug_assertions) {
eprint!("{}", other.escape_unicode());
}
}
}
Ok(())
}
/// Restores the currently selected history
pub fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
let Self { history, hindex, ed, .. } = self;
if !(0..history.len()).contains(hindex) {
return Ok(());
};
ed.undraw(w)?;
ed.clear();
ed.print_head(w)?;
ed.extend(
history
.get(*hindex)
.expect("history should contain index")
.chars(),
w,
)
}
/// Append line to history and clear it
pub fn accept(&mut self) {
self.history_append(self.ed.iter().collect());
self.ed.clear();
self.hindex = self.history.len();
}
/// Append line to history
pub fn history_append(&mut self, mut buf: String) {
while buf.ends_with(char::is_whitespace) {
buf.pop();
}
if !self.history.contains(&buf) {
self.history.push_back(buf)
}
while self.history.len() > 20 {
self.history.pop_front();
}
}
/// Clear the line
pub fn deny(&mut self) {
self.ed.clear()
}
}
impl<'a> Repline<'a, std::io::Stdin> {
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self::with_input(std::io::stdin(), color, begin, again)
}
}
pub mod editor {
use crossterm::{cursor::*, execute, queue, style::*, terminal::*};
use std::{collections::VecDeque, fmt::Display, io::Write};
use super::error::{Error, ReplResult};
fn is_newline(c: &char) -> bool {
*c == '\n'
}
fn write_chars<'a, W: Write>(
c: impl IntoIterator<Item = &'a char>,
w: &mut W,
) -> std::io::Result<()> {
for c in c {
write!(w, "{c}")?;
}
Ok(())
}
#[derive(Debug)]
pub struct Editor<'a> {
head: VecDeque<char>,
tail: VecDeque<char>,
pub color: &'a str,
begin: &'a str,
again: &'a str,
}
impl<'a> Editor<'a> {
pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
Self { head: Default::default(), tail: Default::default(), color, begin, again }
}
pub fn iter(&self) -> impl Iterator<Item = &char> {
self.head.iter()
}
pub fn undraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, .. } = self;
match head.iter().copied().filter(is_newline).count() {
0 => write!(w, "\x1b[0G"),
lines => write!(w, "\x1b[{}F", lines),
}?;
queue!(w, Clear(ClearType::FromCursorDown))?;
// write!(w, "\x1b[0J")?;
Ok(())
}
pub fn redraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, tail, color, begin, again } = self;
write!(w, "{color}{begin}\x1b[0m ")?;
// draw head
for c in head {
match c {
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
_ => w.write_all({ *c as u32 }.to_le_bytes().as_slice()),
}?
}
// save cursor
execute!(w, SavePosition)?;
// draw tail
for c in tail {
match c {
'\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
_ => write!(w, "{c}"),
}?
}
// restore cursor
execute!(w, RestorePosition)?;
Ok(())
}
pub fn prompt<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { head, color, begin, again, .. } = self;
queue!(
w,
MoveToColumn(0),
Print(color),
Print(if head.is_empty() { begin } else { again }),
ResetColor,
Print(' '),
)?;
Ok(())
}
pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
self.prompt(w)?;
write_chars(
self.head.iter().skip(
self.head
.iter()
.rposition(is_newline)
.unwrap_or(self.head.len())
+ 1,
),
w,
)?;
Ok(())
}
pub fn print_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
let Self { tail, .. } = self;
queue!(w, SavePosition, Clear(ClearType::UntilNewLine))?;
write_chars(tail.iter().take_while(|&c| !is_newline(c)), w)?;
queue!(w, RestorePosition)?;
Ok(())
}
pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
// Tail optimization: if the tail is empty,
//we don't have to undraw and redraw on newline
if self.tail.is_empty() {
self.head.push_back(c);
match c {
'\n' => {
write!(w, "\r\n")?;
self.print_head(w)?;
}
c => {
queue!(w, Print(c))?;
}
};
return Ok(());
}
if '\n' == c {
self.undraw(w)?;
}
self.head.push_back(c);
match c {
'\n' => self.redraw(w)?,
_ => {
write!(w, "{c}")?;
self.print_tail(w)?;
}
}
Ok(())
}
pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
if let Some('\n') = self.head.back() {
self.undraw(w)?;
}
let c = self.head.pop_back();
// if the character was a newline, we need to go back a line
match c {
Some('\n') => self.redraw(w)?,
Some(_) => {
// go back a char
queue!(w, MoveLeft(1), Print(' '), MoveLeft(1))?;
self.print_tail(w)?;
}
None => {}
}
Ok(c)
}
pub fn extend<T: IntoIterator<Item = char>, W: Write>(
&mut self,
iter: T,
w: &mut W,
) -> ReplResult<()> {
for c in iter {
self.push(c, w)?;
}
Ok(())
}
pub fn restore(&mut self, s: &str) {
self.clear();
self.head.extend(s.chars())
}
pub fn clear(&mut self) {
self.head.clear();
self.tail.clear();
}
pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<char> {
match self.tail.front() {
Some('\n') => {
self.undraw(w)?;
let out = self.tail.pop_front();
self.redraw(w)?;
out
}
_ => {
let out = self.tail.pop_front();
self.print_tail(w)?;
out
}
}
.ok_or(Error::EndOfInput)
}
pub fn len(&self) -> usize {
self.head.len() + self.tail.len()
}
pub fn is_empty(&self) -> bool {
self.head.is_empty() && self.tail.is_empty()
}
pub fn ends_with(&self, iter: impl DoubleEndedIterator<Item = char>) -> bool {
let mut iter = iter.rev();
let mut head = self.head.iter().rev();
loop {
match (iter.next(), head.next()) {
(None, _) => break true,
(Some(_), None) => break false,
(Some(a), Some(b)) if a != *b => break false,
(Some(_), Some(_)) => continue,
}
}
}
/// Moves the cursor back `steps` steps
pub fn cursor_back<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
for _ in 0..steps {
if let Some('\n') = self.head.back() {
self.undraw(w)?;
}
let Some(c) = self.head.pop_back() else {
return Ok(());
};
self.tail.push_front(c);
match c {
'\n' => self.redraw(w)?,
_ => queue!(w, MoveLeft(1))?,
}
}
Ok(())
}
/// Moves the cursor forward `steps` steps
pub fn cursor_forward<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
for _ in 0..steps {
if let Some('\n') = self.tail.front() {
self.undraw(w)?
}
let Some(c) = self.tail.pop_front() else {
return Ok(());
};
self.head.push_back(c);
match c {
'\n' => self.redraw(w)?,
_ => queue!(w, MoveRight(1))?,
}
}
Ok(())
}
/// Goes to the beginning of the current line
pub fn home<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
loop {
match self.head.back() {
Some('\n') | None => break Ok(()),
Some(_) => self.cursor_back(1, w)?,
}
}
}
/// Goes to the end of the current line
pub fn end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
loop {
match self.tail.front() {
Some('\n') | None => break Ok(()),
Some(_) => self.cursor_forward(1, w)?,
}
}
}
}
impl<'a, 'e> IntoIterator for &'e Editor<'a> {
type Item = &'e char;
type IntoIter = std::iter::Chain<
std::collections::vec_deque::Iter<'e, char>,
std::collections::vec_deque::Iter<'e, char>,
>;
fn into_iter(self) -> Self::IntoIter {
self.head.iter().chain(self.tail.iter())
}
}
impl<'a> Display for Editor<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
let Self { head, tail, .. } = self;
for c in head {
f.write_char(*c)?;
}
for c in tail {
f.write_char(*c)?;
}
Ok(())
}
}
}