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:
parent
21d15787c9
commit
42058568c7
|
@ -1,3 +1,3 @@
|
|||
(ns examples.hello-world)
|
||||
|
||||
(def hello-world (fn () 'helloworld))
|
||||
(def hello-world (fn () 'helloworld))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
// }
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue