1// Copyright 2017 The Bazel Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// The starlark command interprets a Starlark file.
6// With no arguments, it starts a read-eval-print loop (REPL).
7package main // import "go.starlark.net/cmd/starlark"
8
9import (
10	"flag"
11	"fmt"
12	"log"
13	"os"
14	"runtime"
15	"runtime/pprof"
16	"strings"
17
18	"go.starlark.net/internal/compile"
19	"go.starlark.net/lib/time"
20	"go.starlark.net/repl"
21	"go.starlark.net/resolve"
22	"go.starlark.net/starlark"
23	"go.starlark.net/starlarkjson"
24)
25
26// flags
27var (
28	cpuprofile = flag.String("cpuprofile", "", "gather Go CPU profile in this file")
29	memprofile = flag.String("memprofile", "", "gather Go memory profile in this file")
30	profile    = flag.String("profile", "", "gather Starlark time profile in this file")
31	showenv    = flag.Bool("showenv", false, "on success, print final global environment")
32	execprog   = flag.String("c", "", "execute program `prog`")
33)
34
35func init() {
36	flag.BoolVar(&compile.Disassemble, "disassemble", compile.Disassemble, "show disassembly during compilation of each function")
37
38	// non-standard dialect flags
39	flag.BoolVar(&resolve.AllowFloat, "float", resolve.AllowFloat, "obsolete; no effect")
40	flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
41	flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
42	flag.BoolVar(&resolve.AllowRecursion, "recursion", resolve.AllowRecursion, "allow while statements and recursive functions")
43	flag.BoolVar(&resolve.AllowGlobalReassign, "globalreassign", resolve.AllowGlobalReassign, "allow reassignment of globals, and if/for/while statements at top level")
44}
45
46func main() {
47	os.Exit(doMain())
48}
49
50func doMain() int {
51	log.SetPrefix("starlark: ")
52	log.SetFlags(0)
53	flag.Parse()
54
55	if *cpuprofile != "" {
56		f, err := os.Create(*cpuprofile)
57		check(err)
58		err = pprof.StartCPUProfile(f)
59		check(err)
60		defer func() {
61			pprof.StopCPUProfile()
62			err := f.Close()
63			check(err)
64		}()
65	}
66	if *memprofile != "" {
67		f, err := os.Create(*memprofile)
68		check(err)
69		defer func() {
70			runtime.GC()
71			err := pprof.Lookup("heap").WriteTo(f, 0)
72			check(err)
73			err = f.Close()
74			check(err)
75		}()
76	}
77
78	if *profile != "" {
79		f, err := os.Create(*profile)
80		check(err)
81		err = starlark.StartProfile(f)
82		check(err)
83		defer func() {
84			err := starlark.StopProfile()
85			check(err)
86		}()
87	}
88
89	thread := &starlark.Thread{Load: repl.MakeLoad()}
90	globals := make(starlark.StringDict)
91
92	// Ideally this statement would update the predeclared environment.
93	// TODO(adonovan): plumb predeclared env through to the REPL.
94	starlark.Universe["json"] = starlarkjson.Module
95	starlark.Universe["time"] = time.Module
96
97	switch {
98	case flag.NArg() == 1 || *execprog != "":
99		var (
100			filename string
101			src      interface{}
102			err      error
103		)
104		if *execprog != "" {
105			// Execute provided program.
106			filename = "cmdline"
107			src = *execprog
108		} else {
109			// Execute specified file.
110			filename = flag.Arg(0)
111		}
112		thread.Name = "exec " + filename
113		globals, err = starlark.ExecFile(thread, filename, src, nil)
114		if err != nil {
115			repl.PrintError(err)
116			return 1
117		}
118	case flag.NArg() == 0:
119		fmt.Println("Welcome to Starlark (go.starlark.net)")
120		thread.Name = "REPL"
121		repl.REPL(thread, globals)
122	default:
123		log.Print("want at most one Starlark file name")
124		return 1
125	}
126
127	// Print the global environment.
128	if *showenv {
129		for _, name := range globals.Keys() {
130			if !strings.HasPrefix(name, "_") {
131				fmt.Fprintf(os.Stderr, "%s = %s\n", name, globals[name])
132			}
133		}
134	}
135
136	return 0
137}
138
139func check(err error) {
140	if err != nil {
141		log.Fatal(err)
142	}
143}
144