DBChat: Getting a Toy REPL Going in Golang (Part 2)
Hi there! I'm Shrijith Venkatrama, the founder of Hexmos. Right now, I’m building LiveAPI, a super-convenient tool that simplifies engineering workflows by generating awesome API docs from your code in minutes. In this tutorial series, I am on a journey to build for myself DBChat - a simple tool for using AI chat to explore and evolve databases. See previous posts to get more context: Building DBChat - Explore and Evolve Your DB with Simple Chat (Part 1) Kicking Off The DBChat Project The first step is to start a GoLang project: go mod init dbchat mkdir -p cmd/dbchat pkg touch cmd/dbchat/main.go Then let's create a skeleton cmd/dbchat/main.go like this: package main import ( "fmt" "log" ) func main() { log.Println("Starting dbchat application...") fmt.Println("Welcome to dbchat!") } Also, let's create a convenience Makefile at the project root to build, run and clean go artifacts out of our source: # Binary name BINARY_NAME=dbchat # Build directory BUILD_DIR=build # Go build flags BUILD_FLAGS=-v .PHONY: build run clean # Build the application build: @echo "Building ${BINARY_NAME}..." @mkdir -p ${BUILD_DIR} @go build ${BUILD_FLAGS} -o ${BUILD_DIR}/${BINARY_NAME} ./cmd/dbchat # Run the application run: @go run ./cmd/dbchat # Clean build artifacts clean: @echo "Cleaning..." @rm -rf ${BUILD_DIR} Running dbchat for Testing Purposes make run Building dbchat to Get a Binary Output make build cd build ./dbchat And we get this nice output: 2025/01/10 22:51:37 Starting dbchat... Welcome to dbchat! Implementing a Basic REPL Loop The next thing we want to do is - get a basic REPL going. In simpler words - you can call it an "interactive shell". Usually, in C, et-cetera, the readline library within a loop is how it's achieved. In Golang, I was shopping around for any helper libraries that can save our time from implementing our own REPL from scratch. go-repl Something that looks sort of good for our purposes is go-repl As you can see - it gives us many "goodies" which we are used to in a typical shell environment. Let's plug it in for now, if necessary we will change later. Getting Started with go-repl Turns out the original repo is at: https://github.com/OpenEngineer/go-repl And we find an example in the the README in that repo. So I make a adaption, and turn my main.go to look something like this: package main import ( "fmt" "log" "strings" repl "github.com/openengineer/go-repl" ) const banner = ` ██████╗ ██████╗ ██████╗██╗ ██╗ █████╗ ████████╗ ██╔══██╗██╔══██╗██╔════╝██║ ██║██╔══██╗╚══██╔══╝ ██║ ██║██████╔╝██║ ███████║███████║ ██║ ██║ ██║██╔══██╗██║ ██╔══██║██╔══██║ ██║ ██████╔╝██████╔╝╚██████╗██║ ██║██║ ██║ ██║ ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ` var helpMessage = `Available commands: help display this message hello get a friendly greeting quit quit DBChat` // implements repl.Handler interface type DBChatHandler struct { r *repl.Repl } func main() { log.Println("Starting dbchat...") fmt.Println(banner) fmt.Println("Welcome to DBChat! Type 'help' for available commands.") h := &DBChatHandler{} h.r = repl.NewRepl(h) // start the terminal loop if err := h.r.Loop(); err != nil { log.Fatal(err) } } func (h *DBChatHandler) Prompt() string { return "dbchat> " } func (h *DBChatHandler) Tab(buffer string) string { return "" // do nothing for now } func (h *DBChatHandler) Eval(line string) string { fields := strings.Fields(line) if len(fields) == 0 { return "" } cmd, _ := fields[0], fields[1:] switch cmd { case "help": return helpMessage case "hello": return "Hello! Welcome to DBChat!
Hi there! I'm Shrijith Venkatrama, the founder of Hexmos. Right now, I’m building LiveAPI, a super-convenient tool that simplifies engineering workflows by generating awesome API docs from your code in minutes.
In this tutorial series, I am on a journey to build for myself DBChat - a simple tool for using AI chat to explore and evolve databases.
See previous posts to get more context:
Kicking Off The DBChat Project
The first step is to start a GoLang project:
go mod init dbchat
mkdir -p cmd/dbchat pkg
touch cmd/dbchat/main.go
Then let's create a skeleton cmd/dbchat/main.go
like this:
package main
import (
"fmt"
"log"
)
func main() {
log.Println("Starting dbchat application...")
fmt.Println("Welcome to dbchat!")
}
Also, let's create a convenience Makefile
at the project root to build, run and clean go artifacts out of our source:
# Binary name
BINARY_NAME=dbchat
# Build directory
BUILD_DIR=build
# Go build flags
BUILD_FLAGS=-v
.PHONY: build run clean
# Build the application
build:
@echo "Building ${BINARY_NAME}..."
@mkdir -p ${BUILD_DIR}
@go build ${BUILD_FLAGS} -o ${BUILD_DIR}/${BINARY_NAME} ./cmd/dbchat
# Run the application
run:
@go run ./cmd/dbchat
# Clean build artifacts
clean:
@echo "Cleaning..."
@rm -rf ${BUILD_DIR}
Running dbchat
for Testing Purposes
make run
Building dbchat
to Get a Binary Output
make build
cd build
./dbchat
And we get this nice output:
2025/01/10 22:51:37 Starting dbchat...
Welcome to dbchat!
Implementing a Basic REPL Loop
The next thing we want to do is - get a basic REPL going. In simpler words - you can call it an "interactive shell".
Usually, in C, et-cetera, the readline library within a loop is how it's achieved.
In Golang, I was shopping around for any helper libraries that can save our time from implementing our own
REPL from scratch.
go-repl
Something that looks sort of good for our purposes is go-repl
As you can see - it gives us many "goodies" which we are used to in a typical shell environment. Let's plug it in for now,
if necessary we will change later.
Getting Started with go-repl
Turns out the original repo is at: https://github.com/OpenEngineer/go-repl
And we find an example in the the README in that repo.
So I make a adaption, and turn my main.go
to look something like this:
package main
import (
"fmt"
"log"
"strings"
repl "github.com/openengineer/go-repl"
)
const banner = `
██████╗ ██████╗ ██████╗██╗ ██╗ █████╗ ████████╗
██╔══██╗██╔══██╗██╔════╝██║ ██║██╔══██╗╚══██╔══╝
██║ ██║██████╔╝██║ ███████║███████║ ██║
██║ ██║██╔══██╗██║ ██╔══██║██╔══██║ ██║
██████╔╝██████╔╝╚██████╗██║ ██║██║ ██║ ██║
╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
`
var helpMessage = `Available commands:
help display this message
hello get a friendly greeting
quit quit DBChat`
// implements repl.Handler interface
type DBChatHandler struct {
r *repl.Repl
}
func main() {
log.Println("Starting dbchat...")
fmt.Println(banner)
fmt.Println("Welcome to DBChat! Type 'help' for available commands.")
h := &DBChatHandler{}
h.r = repl.NewRepl(h)
// start the terminal loop
if err := h.r.Loop(); err != nil {
log.Fatal(err)
}
}
func (h *DBChatHandler) Prompt() string {
return "dbchat> "
}
func (h *DBChatHandler) Tab(buffer string) string {
return "" // do nothing for now
}
func (h *DBChatHandler) Eval(line string) string {
fields := strings.Fields(line)
if len(fields) == 0 {
return ""
}
cmd, _ := fields[0], fields[1:]
switch cmd {
case "help":
return helpMessage
case "hello":
return "Hello! Welcome to DBChat!