Skip to main content

Crate repl_lang

Crate repl_lang 

Source
Expand description

§repl_lang

The parts of a read-eval-print loop that every language needs and none should have to write twice: deciding when typed input forms a complete entry, keeping the session’s entries so diagnostics can point back into them, and editing the line being typed.

The crate owns no syntax and does no I/O. A language plugs in its own pipeline — lex, parse, evaluate — as a closure, and its own terminal or line source. repl-lang supplies the loop state between them:

  • Session accumulates lines into an entry, runs the pipeline on it after each line, and commits the entry once the pipeline reports Status::Complete. Every committed entry becomes a source named <repl:N> in one SourceMap, so a diag_lang renderer can show any diagnostic from any entry.
  • Input is what the pipeline sees: the entry so far, its global position, a lexer_lang::Cursor already placed there, and is_incomplete — the “errors only at the end of input mean keep typing” test that turns any error-reporting parser into a continuation-line detector.
  • Editor is a terminal-agnostic line editor: cursor motion by visible character and by word, kill and yank, and a bounded history, driven by Edit commands the host maps from its own key events.

§Example

A session over a fixed script, with a pipeline that asks for more input until brackets balance:

use repl_lang::{Feed, Session, Status};

let mut session = Session::new();
let mut prompts = Vec::new();

for line in ["sum([1, 2,", "    3, 4])", "len([])"] {
    prompts.push(if session.is_pending() { "..." } else { ">>>" });
    let feed = session.feed(line, |input| {
        let open = input.text().matches('[').count() + input.text().matches('(').count();
        let close = input.text().matches(']').count() + input.text().matches(')').count();
        if open > close { Status::Incomplete } else { Status::Complete(input.text().len()) }
    })?;
    if let Feed::Complete { entry, value } = feed {
        println!("entry {} complete: {value} bytes", entry.number());
    }
}

assert_eq!(prompts, [">>>", "...", ">>>"]);
assert_eq!(session.len(), 2);
assert_eq!(session.entry(1).map(|e| e.name()), Some("<repl:1>"));

§Wiring a parser

With a parser that reports a missing token at the end of input — as parser_lang does — the whole completeness check is one call:

session.feed(line, |input| {
    let tokens = lex(input.cursor());          // spans are already global
    let mut parser = Parser::new(&tokens);
    let program = parse_program(&mut parser);
    let errors = parser.into_errors();
    if input.is_incomplete(&errors) {
        Status::Incomplete                      // "1 + (2 *" — keep reading
    } else {
        Status::Complete((program, errors))     // done, right or wrong
    }
})

The calc example in the repository is a complete REPL built this way.

§Features

  • std (default) — the standard library. Without it the crate is #![no_std] and needs only alloc; neither the session nor the editor uses operating-system facilities.

§Stability

The public surface is frozen and stable as of 1.0.0: it follows Semantic Versioning, with no breaking changes before 2.0. The surface, the behaviour that is part of the contract — entry naming and layout, the completeness rule, the history and display-unit rules — and the SemVer promise are catalogued in docs/API.md.

Structs§

BytePos
A zero-based byte offset into a single source buffer.
Cursor
A zero-copy cursor over a &str, the primitive every lexer is built on.
Diagnostic
One thing a compiler stage has to say about the source: a Severity, a headline message, a primary Label pointing at the span it concerns, and optionally secondary labels for related locations and trailing note/help lines.
Editor
The editing state of one input line: its text, the cursor, a kill buffer, and a bounded history of submitted lines.
Entry
A committed entry: its number and where its text lives in the session’s source map.
Input
The pending text of one REPL entry, as the pipeline sees it.
Session
The state of one REPL session: the entry being typed, and every entry accepted so far.
SourceFile
A single source held by a SourceMap: a display name, the owned source text, and the half-open Span the text occupies in the map’s global position space.
SourceId
A small, copyable handle to one source in a SourceMap.
SourceMap
A collection of sources laid out end to end in a single global position space.
Span
A half-open byte range start..end into a single source.

Enums§

Edit
One editing command for an Editor.
Feed
The outcome of feeding one line to a Session.
SessionError
The reason a Session could not accept a line.
Status
The pipeline’s verdict on the pending input: finished, or waiting for more.