-
Notifications
You must be signed in to change notification settings - Fork 49
/
main.go
116 lines (107 loc) · 3.19 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2020 Rob Pike. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
// Lisp is an implementation of the language defined, with sublime concision, in
// the first few pages of the LISP 1.5 Programmer's Manual by McCarthy, Abrahams,
// Edwards, Hart, and Levin, from MIT in 1962.
//
// It is a pedagogical experiment to see just how well the interpreter (actually
// EVALQUOTE/APPLY) defined on page 13 of that book really works. The answer is:
// perfectly, of course.
//
// This program's purpose was fun and education, and in no way to create a modern
// or even realistic Lisp implementation. The goal was to turn that marvelous page
// 13 into a working interpreter using clean, direct Go code.
//
// The program therefore has several profound shortcomings, even with respect to
// the Lisp 1.5 book:
//
// - No `SET` or `SETQ`.
// - No `PROG`. The interpreter, by calling `APPLY`, can evaluate only a single
// expression, a possibly recursive function invocation. But this is Lisp, and
// that's still a lot.
// - No character handling.
// - No I/O. Interactive only, although it can start by reading a file specified
// on the command line.
//
// It is slow and of course the language is very, very far from Common Lisp or
// Scheme.
package main // import "robpike.io/lisp"
import (
"bufio"
"flag"
"fmt"
"os"
"robpike.io/lisp/lisp1_5"
)
var (
printSExpr = flag.Bool("sexpr", false, "always print S-expressions")
doPrompt = flag.Bool("doprompt", true, "show interactive prompt")
prompt = flag.String("prompt", "> ", "interactive prompt")
stackDepth = flag.Int("depth", 1e5, "maximum call depth; 0 means no limit")
)
var loading bool
func main() {
flag.Parse()
lisp1_5.Config(*printSExpr)
context := lisp1_5.NewContext(*stackDepth)
loading = true
for _, file := range flag.Args() {
load(context, file)
}
loading = false
parser := lisp1_5.NewParser(bufio.NewReader(os.Stdin))
for {
input(context, parser, *prompt)
}
}
// load reads the named source file and parses it within the context.
func load(context *lisp1_5.Context, file string) {
fd, err := os.Open(file)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer fd.Close()
parser := lisp1_5.NewParser(bufio.NewReader(fd))
input(context, parser, "")
}
// input runs the parser to EOF.
func input(context *lisp1_5.Context, parser *lisp1_5.Parser, prompt string) {
defer handler(context, parser)
for {
if prompt != "" && *doPrompt {
fmt.Print(prompt)
}
switch parser.SkipSpace() {
case '\n':
continue
case lisp1_5.EofRune:
if !loading {
os.Exit(0)
}
return
}
expr := context.Eval(parser.List())
fmt.Println(expr)
parser.SkipSpace() // Grab the newline.
}
}
// handler handles panics from the interpreter. These are part
// of normal operation, signaling parsing and execution errors.
func handler(context *lisp1_5.Context, parser *lisp1_5.Parser) {
e := recover()
if e != nil {
switch e := e.(type) {
case lisp1_5.EOF:
os.Exit(0)
case lisp1_5.Error:
fmt.Fprintln(os.Stderr, e)
parser.SkipToEndOfLine()
fmt.Fprint(os.Stderr, context.StackTrace())
context.PopStack()
default:
panic(e)
}
}
}