Skip to content

Commit fb63603

Browse files
committed
Implement basic REPL commands
List of implemented commands: - `.clear` - clears screen - `.exit` - exits REPL - `.help` - prints help message - `.save` - saves all evaluated commands in the current REPL session to a file
1 parent 46336f4 commit fb63603

File tree

2 files changed

+163
-5
lines changed

2 files changed

+163
-5
lines changed

src/module/repl.wren

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import "meta" for Meta
2-
import "io" for Stdin, Stdout
2+
import "io" for File, Stdin, Stdout
33
import "os" for Platform
44

55
/// Abstract base class for the REPL. Manages the input line and history, but
@@ -66,7 +66,9 @@ class Repl {
6666
// TODO: Handle ESC 0 sequences.
6767
}
6868
} else if (byte == Chars.carriageReturn) {
69-
executeInput()
69+
var next = executeInput()
70+
if (next == Chars.ctrlD) return true
71+
if (next != null) handleChar(next)
7072
} else if (byte == Chars.delete) {
7173
deleteLeft()
7274
} else if (byte >= Chars.space && byte <= Chars.tilde) {
@@ -166,6 +168,11 @@ class Repl {
166168

167169
System.print()
168170

171+
if (Command.isCommand(input)) {
172+
var parts = Command.split(input)
173+
return executeCommand(parts[0], parts[1...parts.count])
174+
}
175+
169176
// Guess if it looks like a statement or expression. If it looks like an
170177
// expression, we try to print the result.
171178
var token = lexFirst(input)
@@ -208,6 +215,41 @@ class Repl {
208215
}
209216
}
210217

218+
executeCommand(command, arguments) {
219+
if (command == Command.clear) {
220+
return Chars.ctrlL
221+
}
222+
223+
if (command == Command.exit) {
224+
return Chars.ctrlD
225+
}
226+
227+
if (command == Command.help) {
228+
System.print(Command.help())
229+
return
230+
}
231+
232+
if (command == Command.save) {
233+
var path = !arguments.isEmpty ? arguments[0] : ""
234+
var fiber = Fiber.new {
235+
if (path.isEmpty) Fiber.abort("missing filename")
236+
File.create(path) {|file|
237+
var code = _history.where {|line| !Command.isCommand(line) }
238+
for (line in code) {
239+
file.writeBytes(line + "\n")
240+
}
241+
file.writeBytes(line + "\n")
242+
System.print("Session saved to: %(path)")
243+
}
244+
}
245+
fiber.try()
246+
if (fiber.error != null) System.print("Failed to save: %(fiber.error)")
247+
return
248+
}
249+
250+
System.print("Invalid REPL command: %(command)")
251+
}
252+
211253
lex(line, includeWhitespace) {
212254
var lexer = Lexer.new(line)
213255
var tokens = []
@@ -387,6 +429,43 @@ class AnsiRepl is Repl {
387429
}
388430
}
389431

432+
/// REPL commands.
433+
class Command {
434+
static clear { ".clear" }
435+
static exit { ".exit" }
436+
static help { ".help" }
437+
static save { ".save" }
438+
439+
static isCommand(str) {
440+
return str.trim().startsWith(".")
441+
}
442+
443+
static split(str) {
444+
return str.trim().split(" ").where {|part| !part.isEmpty }.toList
445+
}
446+
447+
static help() {
448+
var max = Fn.new {|a, b| a > b ? a : b }
449+
var width = COMMANDS.map {|entry|
450+
var command = entry[0]
451+
return command.count
452+
}.reduce(max)
453+
return COMMANDS.map {|entry|
454+
var command = entry[0]
455+
var description = entry[1]
456+
while (command.count < width) command = command + " "
457+
return command + " " + description
458+
}.join("\n")
459+
}
460+
}
461+
462+
var COMMANDS = [
463+
[Command.clear, "Clear screen"],
464+
[Command.exit, "Exit the repl"],
465+
[Command.help, "Print this help message"],
466+
[Command.save, "Save all evaluated commands in this REPL session to a file"]
467+
]
468+
390469
/// ANSI color escape sequences.
391470
class Color {
392471
static none { "\x1b[0m" }

src/module/repl.wren.inc

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Generated automatically from src/module/repl.wren. Do not edit.
1+
// Generated automatically from ./src/module/repl.wren. Do not edit.
22
static const char* replModuleSource =
33
"import \"meta\" for Meta\n"
4-
"import \"io\" for Stdin, Stdout\n"
4+
"import \"io\" for File, Stdin, Stdout\n"
55
"import \"os\" for Platform\n"
66
"\n"
77
"/// Abstract base class for the REPL. Manages the input line and history, but\n"
@@ -68,7 +68,9 @@ static const char* replModuleSource =
6868
" // TODO: Handle ESC 0 sequences.\n"
6969
" }\n"
7070
" } else if (byte == Chars.carriageReturn) {\n"
71-
" executeInput()\n"
71+
" var next = executeInput()\n"
72+
" if (next == Chars.ctrlD) return true\n"
73+
" if (next != null) handleChar(next)\n"
7274
" } else if (byte == Chars.delete) {\n"
7375
" deleteLeft()\n"
7476
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
@@ -168,6 +170,11 @@ static const char* replModuleSource =
168170
"\n"
169171
" System.print()\n"
170172
"\n"
173+
" if (Command.isCommand(input)) {\n"
174+
" var parts = Command.split(input)\n"
175+
" return executeCommand(parts[0], parts[1...parts.count])\n"
176+
" }\n"
177+
"\n"
171178
" // Guess if it looks like a statement or expression. If it looks like an\n"
172179
" // expression, we try to print the result.\n"
173180
" var token = lexFirst(input)\n"
@@ -210,6 +217,41 @@ static const char* replModuleSource =
210217
" }\n"
211218
" }\n"
212219
"\n"
220+
" executeCommand(command, arguments) {\n"
221+
" if (command == Command.clear) {\n"
222+
" return Chars.ctrlL\n"
223+
" }\n"
224+
"\n"
225+
" if (command == Command.exit) {\n"
226+
" return Chars.ctrlD\n"
227+
" }\n"
228+
"\n"
229+
" if (command == Command.help) {\n"
230+
" System.print(Command.help())\n"
231+
" return\n"
232+
" }\n"
233+
"\n"
234+
" if (command == Command.save) {\n"
235+
" var path = !arguments.isEmpty ? arguments[0] : \"\"\n"
236+
" var fiber = Fiber.new {\n"
237+
" if (path.isEmpty) Fiber.abort(\"missing filename\")\n"
238+
" File.create(path) {|file|\n"
239+
" var code = _history.where {|line| !Command.isCommand(line) }\n"
240+
" for (line in code) {\n"
241+
" file.writeBytes(line + \"\n\")\n"
242+
" }\n"
243+
" file.writeBytes(line + \"\n\")\n"
244+
" System.print(\"Session saved to: %(path)\")\n"
245+
" }\n"
246+
" }\n"
247+
" fiber.try()\n"
248+
" if (fiber.error != null) System.print(\"Failed to save: %(fiber.error)\")\n"
249+
" return\n"
250+
" }\n"
251+
"\n"
252+
" System.print(\"Invalid REPL command: %(command)\")\n"
253+
" }\n"
254+
"\n"
213255
" lex(line, includeWhitespace) {\n"
214256
" var lexer = Lexer.new(line)\n"
215257
" var tokens = []\n"
@@ -389,6 +431,43 @@ static const char* replModuleSource =
389431
" }\n"
390432
"}\n"
391433
"\n"
434+
"/// REPL commands.\n"
435+
"class Command {\n"
436+
" static clear { \".clear\" }\n"
437+
" static exit { \".exit\" }\n"
438+
" static help { \".help\" }\n"
439+
" static save { \".save\" }\n"
440+
"\n"
441+
" static isCommand(str) {\n"
442+
" return str.trim().startsWith(\".\")\n"
443+
" }\n"
444+
"\n"
445+
" static split(str) {\n"
446+
" return str.trim().split(\" \").where {|part| !part.isEmpty }.toList\n"
447+
" }\n"
448+
"\n"
449+
" static help() {\n"
450+
" var max = Fn.new {|a, b| a > b ? a : b }\n"
451+
" var width = COMMANDS.map {|entry|\n"
452+
" var command = entry[0]\n"
453+
" return command.count\n"
454+
" }.reduce(max)\n"
455+
" return COMMANDS.map {|entry|\n"
456+
" var command = entry[0]\n"
457+
" var description = entry[1]\n"
458+
" while (command.count < width) command = command + \" \"\n"
459+
" return command + \" \" + description\n"
460+
" }.join(\"\n\")\n"
461+
" }\n"
462+
"}\n"
463+
"\n"
464+
"var COMMANDS = [\n"
465+
" [Command.clear, \"Clear screen\"],\n"
466+
" [Command.exit, \"Exit the repl\"],\n"
467+
" [Command.help, \"Print this help message\"],\n"
468+
" [Command.save, \"Save all evaluated commands in this REPL session to a file\"]\n"
469+
"]\n"
470+
"\n"
392471
"/// ANSI color escape sequences.\n"
393472
"class Color {\n"
394473
" static none { \"\x1b[0m\" }\n"

0 commit comments

Comments
 (0)