-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
198 lines (187 loc) · 6.2 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Gorepl is a simple interpreted language with a syntax similar to Go.
package main
import (
"context"
"flag"
"fmt"
"io"
"math"
"os"
"path/filepath"
"runtime/debug"
"strings"
"fortio.org/cli"
"fortio.org/log"
"fortio.org/struct2env"
"fortio.org/terminal"
"grol.io/grol/eval"
"grol.io/grol/extensions" // register extensions
"grol.io/grol/repl"
)
func main() {
os.Exit(Main())
}
type Config struct {
HistoryFile string
}
var config = Config{}
func EnvHelp(w io.Writer) {
res, _ := struct2env.StructToEnvVars(config)
str := struct2env.ToShellWithPrefix("GROL_", res, true)
fmt.Fprintln(w, "# Grol environment variables:")
fmt.Fprint(w, str)
}
var hookBefore, hookAfter func() int
func Main() (retcode int) { //nolint:funlen // we do have quite a lot of flags and variants.
commandFlag := flag.String("c", "", "command/inline script to run instead of interactive mode")
showParse := flag.Bool("parse", false, "show parse tree")
allParens := flag.Bool("parse-debug", false, "show all parenthesis in parse tree (default is to simplify using precedence)")
format := flag.Bool("format", false, "don't execute, just parse and reformat the input")
compact := flag.Bool("compact", false, "When printing code, use no indentation and most compact form")
showEval := flag.Bool("eval", true, "show eval results")
sharedState := flag.Bool("shared-state", false, "All files share same interpreter state (default is new state for each)")
const historyDefault = "~/.grol_history" // virtual/token filename, will be replaced by actual home dir if not changed.
cli.EnvHelpFuncs = append(cli.EnvHelpFuncs, EnvHelp)
defaultHistoryFile := historyDefault
errs := struct2env.SetFromEnv("GROL_", &config)
if len(errs) > 0 {
log.Errf("Error setting config from env: %v", errs)
}
if config.HistoryFile != "" {
defaultHistoryFile = config.HistoryFile
}
historyFile := flag.String("history", defaultHistoryFile, "history `file` to use")
maxHistory := flag.Int("max-history", terminal.DefaultHistoryCapacity, "max history `size`, use 0 to disable.")
disableLoadSave := flag.Bool("no-load-save", false, "disable load/save of history")
restrictIOs := flag.Bool("restrict-io", false, "restrict IOs (safe mode)")
emptyOnly := flag.Bool("empty-only", false, "only allow load()/save() to ./.gr")
noAuto := flag.Bool("no-auto", false, "don't auto load/save the state to ./.gr")
maxDepth := flag.Int("max-depth", eval.DefaultMaxDepth-1, "Maximum interpreter depth")
maxLen := flag.Int("max-save-len", 4000, "Maximum len of saved identifiers, use 0 for unlimited")
panicOk := flag.Bool("panic", false, "Don't catch panic - only for development/debugging")
// Use 0 (unlimited) as default now that you can ^C to stop a script.
maxDuration := flag.Duration("max-duration", 0, "Maximum duration for a script to run. 0 for unlimited.")
shebangMode := flag.Bool("s", false, "#! script mode: next argument is a script file to run, rest are args to the script")
cli.ArgsHelp = "*.gr files to interpret or `-` for stdin without prompt or no arguments for stdin repl..."
cli.MaxArgs = -1
cli.Main()
var histFile string
if !*shebangMode { //nolint:nestif // shebang mode skips a few things like history, memory and welcome message.
histFile = *historyFile
if histFile == historyDefault {
homeDir, err := os.UserHomeDir()
histFile = filepath.Join(homeDir, ".grol_history")
if err != nil {
log.Warnf("Couldn't get user home dir: %v", err)
histFile = ""
}
}
log.Infof("grol %s - welcome!", cli.LongVersion)
memlimit := debug.SetMemoryLimit(-1)
if memlimit == math.MaxInt64 {
log.Warnf("Memory limit not set, please set the GOMEMLIMIT env var; e.g. GOMEMLIMIT=1GiB")
}
}
options := repl.Options{
ShowParse: *showParse || *allParens,
ShowEval: *showEval,
FormatOnly: *format,
Compact: *compact,
HistoryFile: histFile,
MaxHistory: *maxHistory,
AutoLoad: !*noAuto,
AutoSave: !*noAuto,
MaxDepth: *maxDepth + 1,
MaxValueLen: *maxLen,
PanicOk: *panicOk,
AllParens: *allParens,
MaxDuration: *maxDuration,
ShebangMode: *shebangMode,
}
if hookBefore != nil {
retcode = hookBefore()
if retcode != 0 {
return retcode
}
}
defer func() {
if hookAfter != nil {
retcode += hookAfter()
}
log.Infof("All done - retcode: %d", retcode)
}()
c := extensions.Config{
HasLoad: !*disableLoadSave,
HasSave: !*disableLoadSave,
UnrestrictedIOs: !*restrictIOs,
LoadSaveEmptyOnly: *emptyOnly,
}
err := extensions.Init(&c)
if err != nil {
return log.FErrf("Error initializing extensions: %v", err)
}
if *commandFlag != "" {
res, errs, _ := repl.EvalStringWithOption(context.Background(), options, *commandFlag)
// Only parsing errors are already logged, eval errors aren't, we (re)log everything:
numErrs := len(errs)
if numErrs > 0 {
log.Errf("Total %d %s:\n%s", numErrs, cli.Plural(numErrs, "error"), strings.Join(errs, "\n"))
}
fmt.Print(res)
return numErrs
}
if len(flag.Args()) == 0 {
return repl.Interactive(options)
}
options.All = true
s := eval.NewState()
if options.ShebangMode {
script := flag.Arg(0)
// remaining := flag.Args()[1:] // actually let's also pass the name of the script as arg[0]
options.AutoLoad = false
args := s.SetArgs(flag.Args())
log.Infof("Running #! %s with args %s", script, args.Inspect())
return processOneFile(script, s, options)
}
for _, file := range flag.Args() {
ret := processOneFile(file, s, options)
if ret != 0 {
return ret
}
if !*sharedState {
s = eval.NewState()
}
}
return 0
}
func processOneStream(s *eval.State, in io.Reader, options repl.Options) int {
errs := repl.EvalAll(s, in, os.Stdout, options)
if len(errs) > 0 {
log.Errf("Errors: %v", errs)
}
return len(errs)
}
func processOneFile(file string, s *eval.State, options repl.Options) int {
if file == "-" {
if options.FormatOnly {
log.Infof("Formatting stdin")
} else {
log.Infof("Running on stdin")
}
return processOneStream(s, os.Stdin, options)
}
f, err := os.Open(file)
if err != nil {
log.Fatalf("%v", err)
}
verb := "Running"
if options.FormatOnly {
verb = "Formatting"
}
if !options.ShebangMode {
log.Infof("%s %s", verb, file)
}
code := processOneStream(s, f, options)
f.Close()
return code
}