Skip to main content
Zep provides built-in REPL (Read-Eval-Print Loop) support through the ZepMode_Repl system, allowing you to create interactive programming environments within the editor.
REPL mode is particularly useful for Lisp-like languages (Janet, Scheme, Clojure) but can be adapted for any language that supports expression evaluation.

REPL Provider Interface

To integrate a REPL, implement the IZepReplProvider interface:
struct IZepReplProvider
{
    // Parse and evaluate text from buffer at cursor
    virtual std::string ReplParse(
        ZepBuffer& text, 
        const GlyphIterator& cursorOffset, 
        ReplParseType type
    );
    
    // Parse and evaluate a string directly
    virtual std::string ReplParse(const std::string& text);
    
    // Check if input is a complete form
    virtual bool ReplIsFormComplete(
        const std::string& input, 
        int& depth  // Nesting depth, negative means too many closing parens
    );
};

Parse Types

enum class ReplParseType
{
    SubExpression,    // Evaluate innermost expression at cursor
    OuterExpression,  // Evaluate outermost expression containing cursor
    Line,            // Evaluate current line
    All              // Evaluate entire buffer  
};

Registering REPL Commands

Zep provides multiple REPL commands with different evaluation scopes:
#include "zep/mode_repl.h"

// Create your REPL provider
class MyReplProvider : public IZepReplProvider
{
    // ... implementation ...
};

// Register REPL commands
MyReplProvider* replProvider = new MyReplProvider();

// Main REPL window (<C-e> to open)
ZepReplExCommand::Register(editor, replProvider);

// Evaluate inner expression (<C-S-Return>)  
ZepReplEvaluateInnerCommand::Register(editor, replProvider);

// Evaluate outer expression (<C-Return>)
ZepReplEvaluateOuterCommand::Register(editor, replProvider);

// Evaluate all (<S-Return>)
ZepReplEvaluateCommand::Register(editor, replProvider);

Default Keybindings

KeyCommandDescription
<C-e>Open REPLOpens REPL window
<C-Return>Eval outerEvaluates outermost expression
<C-S-Return>Eval innerEvaluates innermost expression
<S-Return>Eval allEvaluates entire buffer

REPL Provider Implementation

Here’s how to implement a REPL provider for the Janet language:
#include "zep/mode_repl.h"
#include <janet.h>  // Janet language C API

class JanetReplProvider : public IZepReplProvider
{
public:
    JanetReplProvider()
    {
        // Initialize Janet
        janet_init();
    }
    
    ~JanetReplProvider()
    {
        janet_deinit();
    }
    
    std::string ReplParse(const std::string& text) override
    {
        if (text.empty())
            return "";
            
        // Parse Janet code
        JanetTable* env = janet_core_env(NULL);
        Janet result;
        
        const uint8_t* src = (const uint8_t*)text.c_str();
        
        // Evaluate
        int status = janet_dobytes(
            env,
            src, 
            text.length(),
            "repl",
            &result
        );
        
        if (status == 0)
        {
            // Success - convert result to string  
            const uint8_t* str = janet_to_string(result);
            return std::string((const char*)str);
        }
        else
        {
            // Error
            const uint8_t* err = janet_to_string(result);
            return std::string("Error: ") + (const char*)err;
        }
    }
    
    std::string ReplParse(
        ZepBuffer& buffer,
        const GlyphIterator& cursor,
        ReplParseType type) override
    {
        // Extract text based on parse type
        std::string expr;
        
        switch (type)
        {
        case ReplParseType::SubExpression:
            expr = ExtractInnerExpression(buffer, cursor);
            break;
        case ReplParseType::OuterExpression:
            expr = ExtractOuterExpression(buffer, cursor);
            break;
        case ReplParseType::Line:
            expr = ExtractLine(buffer, cursor);
            break;
        case ReplParseType::All:
            expr = std::string(buffer.begin(), buffer.end());
            break;
        }
        
        return ReplParse(expr);
    }
    
    bool ReplIsFormComplete(
        const std::string& input, 
        int& depth) override
    {
        // Count parentheses to check if form is complete
        depth = 0;
        
        for (char c : input)
        {
            if (c == '(') depth++;
            if (c == ')') depth--;
        }
        
        // Negative depth means too many closing parens
        // Zero depth means balanced (complete)
        // Positive depth means incomplete
        return depth == 0;
    }
    
private:
    std::string ExtractInnerExpression(
        ZepBuffer& buffer, 
        const GlyphIterator& cursor)
    {
        // Find innermost parentheses around cursor
        // Implementation depends on your language syntax
        // For Lisps, scan backwards for '(' and forwards for ')'
        
        auto start = cursor;
        auto end = cursor;
        
        // Scan backwards to opening paren
        int depth = 0;
        while (start > buffer.begin())
        {
            if (*start == ')') depth++;
            if (*start == '(')
            {
                if (depth == 0) break;
                depth--;
            }
            --start;
        }
        
        // Scan forwards to closing paren
        depth = 0;
        while (end < buffer.end())
        {
            if (*end == '(') depth++;
            if (*end == ')')
            {
                if (depth == 0)
                {
                    ++end;
                    break;
                }
                depth--;
            }
            ++end;
        }
        
        return std::string(start, end);
    }
    
    // Similar implementations for ExtractOuterExpression, ExtractLine...
};

REPL Window Behavior

When you open a REPL window with <C-e>, Zep creates an interactive buffer:
>> (+ 1 2)
3
>> (defn square [x] (* x x))
<function square>
>> (square 5)
25
>>

Features

  • Prompt: Shows >> for new input
  • Continuation: Shows .. for incomplete forms
  • Multi-line input: Automatically continues input for unclosed expressions
  • Auto-indent: Indents continuation lines based on nesting depth
  • History: Previous evaluations remain visible
  • Syntax highlighting: Uses the buffer’s syntax provider

Implementation Details

From src/mode_repl.cpp:92-231:
const std::string PromptString = ">> ";
const std::string ContinuationString = ".. ";

// On Return key:
if (key == ExtKeys::RETURN)
{
    // Check if form is complete
    int indent = 0;
    bool complete = m_pProvider->ReplIsFormComplete(input, indent);
    
    if (!complete)
    {
        // Incomplete - add continuation line
        if (indent < 0)
        {
            // Too many closing parens - don't allow
            return true;
        }
        
        buffer.Insert(buffer.End(), ContinuationString);
        
        // Add indentation
        for (int i = 0; i < indent; i++)
            buffer.Insert(buffer.End(), " ");
            
        return true;
    }
    
    // Complete - evaluate
    std::string result = m_pProvider->ReplParse(input);
    buffer.Insert(buffer.End(), result + "\n");
    
    // New prompt
    Prompt();
}

Expression Extraction

For Lisp-like languages, implement parenthesis matching:
std::pair<GlyphIterator, GlyphIterator> FindMatchingParens(
    ZepBuffer& buffer,
    GlyphIterator cursor,
    bool innermost)
{
    // For innermost: find smallest enclosing expression
    // For outermost: find largest enclosing expression
    
    auto start = cursor;
    auto end = cursor;
    
    if (innermost)
    {
        // Find closest opening paren before cursor
        int depth = 0;
        while (start > buffer.begin())
        {
            if (*start == ')') depth++;
            if (*start == '(')
            {
                if (depth == 0) break;
                depth--;
            }
            --start;
        }
        
        // Find matching closing paren
        depth = 0;
        end = start + 1;
        while (end < buffer.end())
        {
            if (*end == '(') depth++;
            if (*end == ')')
            {
                if (depth == 0) break;
                depth--;
            }
            ++end;
        }
    }
    else  // outermost
    {
        // Keep expanding until no more enclosing parens
        while (start > buffer.begin())
        {
            auto prev = start - 1;
            if (*prev == '(')
            {
                start = prev;
                // Find new matching close
                end = FindMatchingClose(buffer, start);
            }
            else if (!isspace(*prev))
            {
                break;
            }
            else
            {
                --start;
            }
        }
    }
    
    return { start, end };
}

Complete Example

#include "zep/mode_repl.h"
#include <sstream>
#include <cmath>

class CalculatorRepl : public IZepReplProvider
{
public:
    std::string ReplParse(const std::string& text) override
    {
        try
        {
            // Simple expression evaluator
            double result = EvaluateExpression(text);
            return std::to_string(result);
        }
        catch (const std::exception& e)
        {
            return std::string("Error: ") + e.what();
        }
    }
    
    bool ReplIsFormComplete(
        const std::string& input,
        int& depth) override
    {
        // For calculator, each line is complete
        // Check for balanced parentheses
        depth = 0;
        for (char c : input)
        {
            if (c == '(') depth++;
            if (c == ')') depth--;
        }
        return depth == 0;
    }
    
private:
    double EvaluateExpression(const std::string& expr)
    {
        // Simple recursive descent parser for math expressions
        // Supports: +, -, *, /, (), numbers
        // Left as exercise - use a real expression parser
        return 42.0; // Placeholder
    }
};

// Usage:
void SetupCalculatorRepl(ZepEditor& editor)
{
    auto* calc = new CalculatorRepl();
    
    ZepReplExCommand::Register(editor, calc);
    ZepReplEvaluateCommand::Register(editor, calc);
    
    // Now users can press Ctrl+E to open calculator REPL
}

Buffer Configuration

From src/mode_repl.cpp:115-126, REPL buffers are configured specially:
// REPL buffer is modifiable but not directly saveable
m_pReplBuffer = editor.GetEmptyBuffer("Repl.lisp");
m_pReplBuffer->SetBufferType(BufferType::Repl);

// Disable line highlighting for cleaner output
m_pReplBuffer->GetSyntax()->IgnoreLineHighlight();

// Set up key handler
m_pReplBuffer->SetPostKeyNotifier([&](uint32_t key, uint32_t modifier) {
    return AddKeyPress(key, modifier);
});

// Add to window
m_pReplWindow = editor.GetActiveTabWindow()->AddWindow(
    m_pReplBuffer, 
    nullptr, 
    RegionLayoutType::VBox
);

Implementation Reference

The REPL system is defined in:
  • include/zep/mode_repl.h:11-157 - REPL interfaces and commands
  • src/mode_repl.cpp - REPL implementation
For Lisp-like languages, consider using the ZepSyntaxFlags::LispLike flag when creating the syntax provider to enable proper S-expression handling.

Syntax Extensions

Add syntax highlighting for your REPL language

Keymapping

Customize REPL keybindings

Build docs developers (and LLMs) love