diff --git a/bootstrap/cmd/repl.go b/bootstrap/cmd/repl.go new file mode 100644 index 0000000..83a1330 --- /dev/null +++ b/bootstrap/cmd/repl.go @@ -0,0 +1,38 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "github.com/spf13/cobra" + "serene-lang.org/bootstrap/pkg/core" +) + +// replCmd represents the base command when called without any subcommands +var replCmd = &cobra.Command{ + Use: "repl", + Short: "Runs the local Serene's REPL", + Long: `Runs the local Serene's REPL to interact with Serene`, + Run: func(cmd *cobra.Command, args []string) { + // TODO: Get the debug value from a CLI flag + core.REPL(false) + }, +} + +func init() { + rootCmd.AddCommand(replCmd) +} diff --git a/bootstrap/cmd/root.go b/bootstrap/cmd/root.go index c831230..7448365 100644 --- a/bootstrap/cmd/root.go +++ b/bootstrap/cmd/root.go @@ -22,12 +22,8 @@ import ( "os" "github.com/spf13/cobra" - "serene-lang.org/bootstrap/pkg/parser" - "serene-lang.org/bootstrap/pkg/reader" ) -var cfgFile string - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "Serene", @@ -43,9 +39,7 @@ to redistribute it under certain conditions; for details take a look at the LICENSE file. `, Run: func(cmd *cobra.Command, args []string) { - reader.ReadString("sameer mary") - ast, _ := parser.ParseToAST("(asd 'mary '(1 2 3.4 -4 -0.3) `(asd ~asd ~@zxc))") - fmt.Printf("%s\n", ast.String()) + fmt.Println("Fix me!!!! I don't do anything !!!") }, } diff --git a/bootstrap/go.mod b/bootstrap/go.mod index c159128..c3bde3c 100644 --- a/bootstrap/go.mod +++ b/bootstrap/go.mod @@ -3,6 +3,7 @@ module serene-lang.org/bootstrap go 1.15 require ( + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 ) diff --git a/bootstrap/go.sum b/bootstrap/go.sum index ade8563..0d5d587 100644 --- a/bootstrap/go.sum +++ b/bootstrap/go.sum @@ -26,6 +26,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= diff --git a/bootstrap/pkg/core/core.go b/bootstrap/pkg/core/core.go new file mode 100644 index 0000000..7bc8a49 --- /dev/null +++ b/bootstrap/pkg/core/core.go @@ -0,0 +1,72 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package core contains the high level internal function of Serene +package core + +import ( + "fmt" + + "github.com/chzyer/readline" + "serene-lang.org/bootstrap/pkg/printer" + "serene-lang.org/bootstrap/pkg/reader" + "serene-lang.org/bootstrap/pkg/runtime" +) + +func rep(rt *runtime.Runtime, line string) { + ast, err := reader.ReadString(line) + if err != nil { + fmt.Printf("Error: %s", err) + return + } + //eval.Eval(rt, ast) + printer.Print(rt, ast) +} + +/** TODO: +Replace the readline implementation with go-prompt. +*/ + +func REPL(debug bool) { + rt := runtime.MakeRuntime(debug) + + rt.CreateNS("user", "REPL", true) + rl, err := readline.New("> ") + if err != nil { + panic(err) + } + defer rl.Close() + + fmt.Println(`Serene's bootstrap interpreter is used to +bootstrap the Serene's compiler.' + +It comes with ABSOLUTELY NO WARRANTY; +This is free software, and you are welcome +to redistribute it under certain conditions; +for details take a look at the LICENSE file. +`) + for { + rl.SetPrompt(fmt.Sprintf("%s> ", rt.CurrentNS().GetName())) + line, err := rl.Readline() + if err != nil { // io.EOF + break + } + rep(rt, line) + } + +} diff --git a/bootstrap/pkg/eval/eval.go b/bootstrap/pkg/eval/eval.go new file mode 100644 index 0000000..05f16c9 --- /dev/null +++ b/bootstrap/pkg/eval/eval.go @@ -0,0 +1,39 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package eval provides all the necessary functions to eval expressions +package eval + +import ( + "serene-lang.org/bootstrap/pkg/runtime" + "serene-lang.org/bootstrap/pkg/types" +) + +func eval(rt *runtime.Runtime, forms types.ASTree) types.IExpr { + if len(forms) == 0 { + return &types.Nil + } + + var ret types.IExpr + + for _, form := range forms { + ret = eval_form(rt, rt.CurrentNS().GetRootScope(), form) + } + + ret +} diff --git a/bootstrap/pkg/namespace/namespace.go b/bootstrap/pkg/namespace/namespace.go new file mode 100644 index 0000000..523d095 --- /dev/null +++ b/bootstrap/pkg/namespace/namespace.go @@ -0,0 +1,59 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package namespace provides an INamespace interface and one implementation +// of it to be used as the basic blocks of Serene's namespaces. +package namespace + +import "serene-lang.org/bootstrap/pkg/scope" + +type INamespace interface { + DefineGlobal() + LookupGlobal() + GetRootScope() scope.IScope + // return the fully qualified name of the namespace + GetName() string +} + +type Namespace struct { + name string + rootScope scope.Scope + source string + externals map[string]Namespace +} + +func (n *Namespace) DefineGlobal() {} + +func (n *Namespace) LookupGlobal() {} + +func (n *Namespace) GetRootScope() scope.IScope { + return &n.rootScope +} + +func (n *Namespace) GetName() string { + return n.name +} + +func MakeNS(name string, source string) Namespace { + return Namespace{ + name: name, + rootScope: scope.MakeScope(nil), + source: source, + externals: map[string]Namespace{}, + } +} diff --git a/bootstrap/pkg/parser/parser.go b/bootstrap/pkg/parser/parser.go index 4535c25..a0816af 100644 --- a/bootstrap/pkg/parser/parser.go +++ b/bootstrap/pkg/parser/parser.go @@ -95,14 +95,15 @@ func readRawSymbol(parser IParsable) (types.IExpr, error) { var symbol string if c == nil { - return nil, errors.New("Unexpected EOF while parsing a symbol") + return nil, errors.New("unexpected EOF while parsing a symbol") } if isValidForSymbol(*c) { + parser.next(false) symbol = *c } else { - return nil, fmt.Errorf("Unexpected character: got '%s', expected a symbol at %s", + return nil, fmt.Errorf("unexpected character: got '%s', expected a symbol at %s", *c, parser.GetLocation(), ) @@ -143,7 +144,6 @@ func readNumber(parser IParsable, neg bool) (types.IExpr, error) { } if *c == "." && isDouble { - fmt.Println(result) return nil, errors.New("a double with more that one '.' ???") } @@ -163,7 +163,7 @@ func readNumber(parser IParsable, neg bool) (types.IExpr, error) { break } } - fmt.Println(result) + return types.MakeNumberFromStr(result, isDouble) } diff --git a/bootstrap/pkg/printer/printer.go b/bootstrap/pkg/printer/printer.go new file mode 100644 index 0000000..cca79f9 --- /dev/null +++ b/bootstrap/pkg/printer/printer.go @@ -0,0 +1,31 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package printer contains helper functions to printout AST and exprs +package printer + +import ( + "fmt" + + "serene-lang.org/bootstrap/pkg/runtime" + "serene-lang.org/bootstrap/pkg/types" +) + +func Print(rt *runtime.Runtime, ast types.ASTree) { + fmt.Println(ast.String()) +} diff --git a/bootstrap/pkg/reader/reader.go b/bootstrap/pkg/reader/reader.go index 62a968a..1ebc174 100644 --- a/bootstrap/pkg/reader/reader.go +++ b/bootstrap/pkg/reader/reader.go @@ -15,7 +15,16 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + +// Package reader provides a set of functions to read forms from several +// different mediums package reader -func ReadString(input string) { +import ( + "serene-lang.org/bootstrap/pkg/parser" + "serene-lang.org/bootstrap/pkg/types" +) + +func ReadString(input string) (types.ASTree, error) { + return parser.ParseToAST(input) } diff --git a/bootstrap/pkg/runtime/runtime.go b/bootstrap/pkg/runtime/runtime.go new file mode 100644 index 0000000..8038670 --- /dev/null +++ b/bootstrap/pkg/runtime/runtime.go @@ -0,0 +1,73 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package runtime provides all the necessary functionality and data +// structures of Serene at runtime. You can think of the run time as +// Serene's state. +package runtime + +import ( + "fmt" + + "serene-lang.org/bootstrap/pkg/namespace" +) + +/** TODO: +Create an IRuntime interface to avoid using INamespace directly +*/ + +/** TODO: +Handle concurrency on the runtime level +*/ + +type Runtime struct { + namespaces map[string]namespace.Namespace + currentNS string + debugMode bool +} + +func (r *Runtime) CurrentNS() *namespace.Namespace { + if r.currentNS == "" { + panic("current ns is not set on the runtime.") + } + + ns, ok := r.namespaces[r.currentNS] + + if !ok { + panic(fmt.Sprintf("namespace '%s' doesn't exist in the runtime.", r.currentNS)) + } + + return &ns +} + +func (r *Runtime) CreateNS(name string, source string, setAsCurrent bool) { + ns := namespace.MakeNS(name, source) + + if setAsCurrent { + r.currentNS = name + } + r.namespaces[name] = ns +} + +func MakeRuntime(debug bool) *Runtime { + return &Runtime{ + namespaces: map[string]namespace.Namespace{}, + currentNS: "", + debugMode: debug, + } +} diff --git a/bootstrap/pkg/scope/scope.go b/bootstrap/pkg/scope/scope.go new file mode 100644 index 0000000..12ba761 --- /dev/null +++ b/bootstrap/pkg/scope/scope.go @@ -0,0 +1,66 @@ +/* + Serene --- Yet an other Lisp + +Copyright (c) 2020 Sameer Rahmani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Package scope provides several interfaces and their implementations +// such `Scope` which acts as the environment in the Lisp literature. +package scope + +import ( + "fmt" + + "serene-lang.org/bootstrap/pkg/types" +) + +type IScope interface { + Lookup(k string) (*Binding, error) + Insert(k string, v types.IExpr, public bool) +} + +type Binding struct { + value types.IExpr + public bool +} + +type Scope struct { + bindings map[string]*Binding + parent IScope +} + +func (s *Scope) Lookup(k string) (*Binding, error) { + v, ok := s.bindings[k] + if ok { + return v, nil + } + + if s.parent != nil { + return s.parent.Lookup(k) + } else { + return nil, fmt.Errorf("can't resolve symbol '%s'", k) + } +} + +func (s *Scope) Insert(k string, v types.IExpr, public bool) { + s.bindings[k] = &Binding{value: v, public: public} +} + +func MakeScope(parent *Scope) Scope { + return Scope{ + parent: parent, + bindings: map[string]*Binding{}, + } +}