Add support for native function, multi ns require fn and 'builtins'

* Implemented Native function as IExprs so we can refer to them
  like any other function. Checkout `NativeFunction`.

* `require` has been implemented as a native functions which
  accepts one or more namespace descriptions and load them.
  A namespace description is either a symbol with the ns name
  or an `IColl` in the following form `(some.ns.name alias).

* A hashmap of strings to native functions has been added to the Runtime
  which is used to resolve builtin functions.
This commit is contained in:
Sameer Rahmani 2020-12-15 19:08:51 +00:00
parent 21d15787c9
commit 42058568c7
11 changed files with 276 additions and 77 deletions

View File

@ -1,3 +1,3 @@
(ns examples.hello-world)
(def hello-world (fn () 'helloworld))
(def hello-world (fn () 'helloworld))

View File

@ -28,12 +28,14 @@ const (
True
False
Symbol
Keyword
Number
List
Fn
NativeFn
Namespace
Block // Dont' mistake it with block from other programming languages
// 10
)
type Location struct {

View File

@ -0,0 +1,64 @@
/*
Serene --- Yet an other Lisp
Copyright (c) 2020 Sameer Rahmani <lxsameer@gnu.org>
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 <http://www.gnu.org/licenses/>.
*/
package core
import (
"fmt"
"strings"
)
// BUILTINS is used in the Runtime to support builtin functions of
// the language which are implemented in Go
var BUILTINS = map[string]NativeFunction{
"print": MakeNativeFn("print", PrintNativeFn),
"require": MakeNativeFn("print", RequireNativeFn),
}
func PrintNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
var params []string
for _, expr := range args.Rest().(*List).ToSlice() {
params = append(params, expr.String())
}
fmt.Print(strings.Join(params, " "))
return &Nil, nil
}
func RequireNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
switch args.Count() {
case 0:
return nil, MakeErrorFor(rt, args, "'require' special form is missing")
case 1:
return nil, MakeErrorFor(rt, args.First(), "'require' special form needs at least one argument")
default:
}
var result IExpr
var err IError
for _, ns := range args.Rest().(*List).ToSlice() {
result, err = RequireNamespace(rt, ns)
if err != nil {
return nil, err
}
}
return result, nil
}

View File

@ -62,10 +62,10 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
)
}
expr = rt.CurrentNS().LookupGlobal(sym)
expr = rt.CurrentNS().LookupGlobal(rt, sym)
nsName = sym.GetNSPart()
} else {
expr = scope.Lookup(symbolName)
expr = scope.Lookup(rt, symbolName)
nsName = rt.CurrentNS().GetName()
}
@ -198,10 +198,6 @@ tco:
ret, err = NSForm(rt, scope, list)
continue // return
case "require":
ret, err = RequireForm(rt, scope, list)
continue // return
// `quote` evaluation rules:
// * Only takes one argument
// * Returns the argument without evaluating it
@ -468,6 +464,13 @@ tco:
expressions = fn.GetBody()
continue tco
case ast.NativeFn:
fn := f.(*NativeFunction)
return fn.Apply(
rt,
scope,
MakeNodeFromExpr(fn),
exprs.(*List))
default:
err = MakeError(rt, "don't know how to execute anything beside function")
ret = nil

View File

@ -24,6 +24,12 @@ import (
"serene-lang.org/bootstrap/pkg/ast"
)
type nativeFnHandler = func(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError)
type INativeFn interface {
Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError)
}
// Function struct represent a user defined function.
type Function struct {
// Node struct holds the necessary functions to make
@ -48,6 +54,14 @@ type Function struct {
isMacro bool
}
type NativeFunction struct {
// Node struct holds the necessary functions to make
// Functions locatable
Node
name string
fn nativeFnHandler
}
func (f *Function) GetType() ast.NodeType {
return ast.Fn
}
@ -129,3 +143,26 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco
return scope, nil
}
func (f *NativeFunction) GetType() ast.NodeType {
return ast.NativeFn
}
func (f *NativeFunction) String() string {
return fmt.Sprintf("<NativeFn: %s>", f.name)
}
func (f *NativeFunction) ToDebugStr() string {
return fmt.Sprintf("<NativeFn: %s>", f.name)
}
func (f *NativeFunction) Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
return f.fn(rt, scope, n, args)
}
func MakeNativeFn(name string, f nativeFnHandler) NativeFunction {
return NativeFunction{
name: name,
fn: f,
}
}

View File

@ -0,0 +1,35 @@
/*
Serene --- Yet an other Lisp
Copyright (c) 2020 Sameer Rahmani <lxsameer@gnu.org>
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 <http://www.gnu.org/licenses/>.
*/
package core
// type Keyword struct{
// Node
// }
// func (n *Keyword) GetType() ast.NodeType {
// return ast.Keyword
// }
// func (n NilType) String() string {
// return ""
// }
// func (n NilType) ToDebugStr() string {
// return "nil"
// }

View File

@ -50,7 +50,7 @@ func isMacroCall(rt *Runtime, scope IScope, form IExpr) (*Function, bool) {
var macro IExpr = nil
if first.GetType() == ast.Symbol {
binding := scope.Lookup(first.(*Symbol).GetName())
binding := scope.Lookup(rt, first.(*Symbol).GetName())
if binding != nil && binding.Public {
macro = binding.Value
}

View File

@ -62,7 +62,7 @@ func (n *Namespace) DefineGlobal(k string, v IExpr, public bool) {
n.rootScope.Insert(k, v, public)
}
func (n *Namespace) LookupGlobal(sym *Symbol) *Binding {
func (n *Namespace) LookupGlobal(rt *Runtime, sym *Symbol) *Binding {
if !sym.IsNSQualified() {
return nil
}
@ -74,7 +74,7 @@ func (n *Namespace) LookupGlobal(sym *Symbol) *Binding {
}
externalScope := externalNS.GetRootScope()
return externalScope.Lookup(sym.GetName())
return externalScope.Lookup(rt, sym.GetName())
}
func (n *Namespace) GetRootScope() IScope {
@ -102,6 +102,99 @@ func (n *Namespace) getForms() *Block {
return &n.forms
}
func requireNS(rt *Runtime, ns string) (*Namespace, IError) {
// TODO: use a hashing algorithm to avoid reloading an unchanged namespace
loadedForms, err := rt.LoadNS(ns)
if err != nil {
return nil, err
}
body := loadedForms.forms
source := loadedForms.source
if body.Count() == 0 {
return nil, MakeError(
rt,
fmt.Sprintf("The '%s' ns source code doesn't start with an 'ns' form.", ns),
)
}
namespace := MakeNS(ns, source)
namespace.setForms(body)
return &namespace, nil
}
func RequireNamespace(rt *Runtime, namespace IExpr) (IExpr, IError) {
var alias string
var ns *Symbol
switch namespace.GetType() {
case ast.Symbol:
ns = namespace.(*Symbol)
alias = ns.GetName()
case ast.List:
list := namespace.(*List)
first := list.First()
if first.GetType() != ast.Symbol {
return nil, MakeErrorFor(rt, first, "The first element has to be a symbol")
}
second := list.Rest().First()
if second.GetType() != ast.Symbol {
return nil, MakeErrorFor(rt, first, "The second element has to be a symbol")
}
ns = first.(*Symbol)
alias = second.(*Symbol).GetName()
default:
return nil, MakeErrorFor(rt, ns, "Don't know how to load the given namespace")
}
loadedNS, err := requireNS(rt, ns.GetName())
if err != nil {
return nil, err
}
prevNS := rt.CurrentNS()
rt.InsertNS(ns.GetName(), loadedNS)
inserted := rt.setCurrentNS(loadedNS.GetName())
if !inserted {
return nil, MakeError(
rt,
fmt.Sprintf(
"the namespace '%s' didn't get inserted in the runtime.",
loadedNS.GetName()),
)
}
loadedNS, e := EvalNSBody(rt, loadedNS)
inserted = rt.setCurrentNS(prevNS.GetName())
if !inserted {
return nil, MakeError(
rt,
fmt.Sprintf(
"can't set the current ns back to '%s' from '%s'.",
prevNS.GetName(),
loadedNS.GetName()),
)
}
if e != nil {
return nil, e
}
prevNS.setExternal(alias, loadedNS)
return loadedNS, nil
}
func MakeNS(name string, source string) Namespace {
s := MakeScope(nil)
return Namespace{

View File

@ -40,6 +40,7 @@ type loadedForms struct {
type Runtime struct {
namespaces map[string]Namespace
builtins map[string]NativeFunction
currentNS string
paths []string
debugMode bool
@ -163,11 +164,24 @@ func (r *Runtime) InsertNS(nsName string, ns *Namespace) {
r.namespaces[nsName] = *ns
}
func (r *Runtime) LookupBuiltin(k string) IExpr {
builtinfn, ok := r.builtins[k]
if ok {
return &builtinfn
}
return nil
}
func MakeRuntime(paths []string, debug bool) *Runtime {
return &Runtime{
rt := Runtime{
namespaces: map[string]Namespace{},
currentNS: "",
debugMode: debug,
paths: paths,
}
rt.builtins = BUILTINS
return &rt
}

View File

@ -18,8 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import "fmt"
type IScope interface {
Lookup(k string) *Binding
Lookup(rt *Runtime, k string) *Binding
Insert(k string, v IExpr, public bool)
}
@ -33,14 +35,25 @@ type Scope struct {
parent *Scope
}
func (s *Scope) Lookup(k string) *Binding {
func (s *Scope) Lookup(rt *Runtime, k string) *Binding {
if rt.IsDebugMode() {
fmt.Println(s.parent)
fmt.Printf("[DEBUG] Looking up '%s'\n", k)
}
v, ok := s.bindings[k]
if ok {
return &v
}
if s.parent != nil {
return s.parent.Lookup(k)
return s.parent.Lookup(rt, k)
}
builtin := rt.LookupBuiltin(k)
if builtin != nil {
return &Binding{builtin, true}
}
return nil

View File

@ -156,65 +156,3 @@ func NSForm(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
// params := args.Rest().Rest()
}
func RequireForm(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
switch args.Count() {
case 0:
return nil, MakeErrorFor(rt, args, "'require' special form is missing")
case 2:
default:
return nil, MakeErrorFor(rt, args.First(), "'require' special form needs exactly one argument")
}
ns, err := EvalForms(rt, scope, args.Rest().First())
if err != nil {
return nil, err
}
switch ns.GetType() {
case ast.Symbol:
loadedNS, err := rt.RequireNS(ns.(*Symbol).GetName())
if err != nil {
return nil, err
}
prevNS := rt.CurrentNS()
rt.InsertNS(ns.(*Symbol).GetName(), loadedNS)
inserted := rt.setCurrentNS(loadedNS.GetName())
if !inserted {
return nil, MakeError(
rt,
fmt.Sprintf(
"the namespace '%s' didn't get inserted in the runtime.",
loadedNS.GetName()),
)
}
loadedNS, e := EvalNSBody(rt, loadedNS)
inserted = rt.setCurrentNS(prevNS.GetName())
if !inserted {
return nil, MakeError(
rt,
fmt.Sprintf(
"can't set the current ns back to '%s' from '%s'.",
prevNS.GetName(),
loadedNS.GetName()),
)
}
if e != nil {
return nil, e
}
prevNS.setExternal(ns.(*Symbol).GetName(), loadedNS)
return loadedNS, nil
case ast.List:
default:
}
return nil, MakeError(rt, "NotImplemented")
}