Add support for printable strings and 'pr', 'prn' functionalities.

Add 'pr' and 'prn' functions to print out the representation of an
string and 'print' and 'println' to print out the string.
This commit is contained in:
Sameer Rahmani 2020-12-24 16:28:12 +00:00
parent fb6c1b3ba3
commit 699483e249
12 changed files with 189 additions and 40 deletions

View File

@ -1,3 +1,9 @@
(ns examples.hello-world)
(def hello-world (fn () 'helloworld))
(def hello-world
(fn ()
"helloworld"))
(def main
(fn (&args)
(println "sameer")))

View File

@ -18,27 +18,35 @@ 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{
"pr": MakeNativeFn("pr", PrNativeFn),
"prn": MakeNativeFn("pr", PrnNativeFn),
"print": MakeNativeFn("print", PrintNativeFn),
"println": MakeNativeFn("println", PrintlnNativeFn),
"require": MakeNativeFn("print", RequireNativeFn),
"hash": MakeNativeFn("hash", HashNativeFn),
}
func PrNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
Pr(rt, toRepresentables(args.Rest().(IColl))...)
return &Nil, nil
}
func PrnNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
Prn(rt, toRepresentables(args.Rest().(IColl))...)
return &Nil, nil
}
func PrintNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
var params []string
Print(rt, toRepresentables(args.Rest().(IColl))...)
return &Nil, nil
}
for _, expr := range args.Rest().(*List).ToSlice() {
params = append(params, expr.String())
}
fmt.Print(strings.Join(params, " "))
func PrintlnNativeFn(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
Println(rt, toRepresentables(args.Rest().(IColl))...)
return &Nil, nil
}

View File

@ -46,7 +46,7 @@ type IError interface {
ast.ILocatable
// We want errors to be printable by the `print` family
IPrintable
IRepresentable
IDebuggable
// To wrap Golan rrrrors

View File

@ -36,6 +36,9 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
case ast.Number:
return form, nil
case ast.Fn:
return form, nil
case ast.String:
return form, nil
@ -117,10 +120,9 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
}
return MakeList(result), nil
}
panic("Asd")
// Default case
return nil, MakeError(rt, "not implemented")
return nil, MakeError(rt, fmt.Sprintf("support for '%d' is not implemented", form.GetType()))
}
// EvalForms evaluates the given expr `expressions` (it can be a list, block, symbol or anything else)
@ -294,7 +296,7 @@ tco:
// the symbol name binded to the value
case "def":
ret, err = Def(rt, scope, list.Rest().(*List))
break tco // return
continue body
// `defmacro` evaluation rules:
// * The first argument has to be a symbol
@ -303,7 +305,7 @@ tco:
// body of the macro.
case "defmacro":
ret, err = DefMacro(rt, scope, list.Rest().(*List))
break tco // return
continue body
// `macroexpand` evaluation rules:
// * It has to have only one argument
@ -327,7 +329,7 @@ tco:
// * Defines an anonymous function.
case "fn":
ret, err = Fn(rt, scope, list.Rest().(*List))
break tco // return
continue body
// `if` evaluation rules:
// * It has to get only 3 arguments: PRED THEN ELSE

View File

@ -109,6 +109,10 @@ func (f *Function) GetName() string {
return f.name
}
func (f *Function) SetName(name string) {
f.name = name
}
func (f *Function) GetScope() IScope {
return f.scope
}
@ -129,6 +133,11 @@ func (f *Function) GetBody() *Block {
return f.body
}
func (f *Function) Apply(rt *Runtime, scope IScope, n Node, args *List) (IExpr, IError) {
application := args.Cons(f)
return EvalForms(rt, scope, application)
}
// MakeFunction Create a function with the given `params` and `body` in
// the given `scope`.
func MakeFunction(scope IScope, params IColl, body *Block) *Function {
@ -144,15 +153,24 @@ func MakeFunction(scope IScope, params IColl, body *Block) *Function {
// to the given `values`.
func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Scope, IError) {
scope := MakeScope(parent.(*Scope))
// TODO: Implement destructuring
if bindings.Count() > values.Count() {
return nil, MakeError(rt, "'binding' and 'valuse' size don't match")
}
binds := bindings.ToSlice()
exprs := values.ToSlice()
numberOfBindings := len(binds)
lastBinding := binds[len(binds)-1]
if lastBinding.GetType() == ast.Symbol && lastBinding.(*Symbol).IsRestable() {
numberOfBindings = len(binds) - 1
}
if numberOfBindings > len(exprs) {
if rt.IsDebugMode() {
fmt.Printf("[DEBUG] Mismatch on bindings and values: Bindings: %s, Values: %s\n", bindings, values)
}
return nil, MakeError(rt,
fmt.Sprintf("'bindings' and 'values' size don't match. '%d' and '%d'", bindings.Count(), values.Count()))
}
for i := 0; i < len(binds); i += 1 {
// If an argument started with char `&` use it to represent
@ -160,7 +178,25 @@ func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Sco
//
// for example: `(fn (x y &z) ...)`
if binds[i].GetType() == ast.Symbol && binds[i].(*Symbol).IsRestable() {
scope.Insert(binds[i+1].(*Symbol).GetName(), MakeList(exprs[i:]), false)
if i != len(binds)-1 {
return nil, MakeErrorFor(rt, binds[i], "The function argument with '&' has to be the last argument.")
}
// if the number of values are one less than the number of bindings
// but the last binding is a Restable (e.g &x) the the last bindings
// has to be an empty list. Note the check for number of vlaues comes
// next.
rest := MakeEmptyList()
if i == len(exprs)-1 {
// If the number of values matches the number of bindings
// or it is more than that create a list from them
// to pass it to the last argument that has to be Restable (e.g &x)
rest = MakeList(exprs[i:])
}
scope.Insert(binds[i].(*Symbol).GetName()[1:], rest, false)
break
} else {
scope.Insert(binds[i].(*Symbol).GetName(), exprs[i], false)

View File

@ -152,6 +152,7 @@ func requireNS(rt *Runtime, ns string) (*Namespace, IError) {
fmt.Sprintf("The '%s' ns source code doesn't start with an 'ns' form.", ns),
)
}
namespace := MakeNS(ns, source)
namespace.setForms(body)

View File

@ -217,6 +217,7 @@ func readRawSymbol(parser IParsable) (IExpr, IError) {
return sym, nil
}
func readString(parser IParsable) (IExpr, IError) {
str := ""
@ -230,7 +231,26 @@ func readString(parser IParsable) (IExpr, IError) {
node := MakeNode(parser.Buffer(), parser.GetLocation()-len(str), parser.GetLocation())
return MakeString(node, str), nil
}
str = str + *c
if *c == "\\" {
c = parser.next(false)
switch *c {
case "n":
str = str + "\n"
case "t":
str = str + "\t"
case "r":
str = str + "\r"
case "\\":
str = str + "\\"
case "\"":
str = str + "\""
default:
return nil, makeErrorAtPoint(parser, "Unsupported escape character: \\%s", *c)
}
} else {
str = str + *c
}
}
}

View File

@ -20,14 +20,49 @@ package core
import (
"fmt"
"strings"
)
func Print(rt *Runtime, ast IPrintable) {
fmt.Println(ast.String())
func toRepresanbleString(ast ...IRepresentable) string {
var results []string
for _, x := range ast {
results = append(results, x.String())
}
return strings.Join(results, " ")
}
func toPrintableString(ast ...IRepresentable) string {
var results []string
for _, x := range ast {
if printable, ok := x.(IPrintable); ok {
results = append(results, printable.PrintToString())
continue
}
results = append(results, x.String())
}
return strings.Join(results, " ")
}
func Pr(rt *Runtime, ast ...IRepresentable) {
fmt.Print(toRepresanbleString(ast...))
}
func Prn(rt *Runtime, ast ...IRepresentable) {
fmt.Println(toRepresanbleString(ast...))
}
func Print(rt *Runtime, ast ...IRepresentable) {
fmt.Print(toPrintableString(ast...))
}
func Println(rt *Runtime, ast ...IRepresentable) {
fmt.Println(toPrintableString(ast...))
}
func PrintError(rt *Runtime, err IError) {
loc := err.GetLocation()
fmt.Printf("Error: %s\nAt: %d to %d\n", err.String(), loc.GetStart(), loc.GetEnd())
}

View File

@ -28,7 +28,6 @@ import (
// arguments in `args` has to be a symbol ( none ns qualified ) and
// the second param should be the value of the binding
func Def(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
// TODO: Add support for docstrings and meta
switch args.Count() {
case 2:
@ -47,6 +46,10 @@ func Def(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
return nil, err
}
if value.GetType() == ast.Fn {
value.(*Function).SetName(sym.GetName())
}
ns := rt.CurrentNS()
ns.DefineGlobal(sym.GetName(), value, true)
return sym, nil

View File

@ -20,6 +20,7 @@ package core
import (
"fmt"
"strings"
"serene-lang.org/bootstrap/pkg/ast"
"serene-lang.org/bootstrap/pkg/hash"
@ -35,6 +36,10 @@ func (s *String) GetType() ast.NodeType {
}
func (s *String) String() string {
return "\"" + s.Escape() + "\""
}
func (s *String) PrintToString() string {
return s.content
}
@ -47,6 +52,17 @@ func (s *String) Hash() uint32 {
return hash.HashOf(append([]byte{byte(ast.String)}, bytes...))
}
func (s *String) Escape() string {
replacer := strings.NewReplacer(
"\n", "\\n",
"\t", "\\t",
"\r", "\\r",
"\\", "\\\\",
"\"", "\\\"",
)
return replacer.Replace(s.content)
}
func MakeString(n Node, s string) *String {
return &String{n, s}
}

View File

@ -67,10 +67,7 @@ func (s *Symbol) IsRestable() bool {
}
func (s *Symbol) IsNSQualified() bool {
if s.nsPart == "" {
return false
}
return true
return s.nsPart != ""
}
func MakeSymbol(n Node, s string) (*Symbol, IError) {

View File

@ -25,13 +25,22 @@ import (
"serene-lang.org/bootstrap/pkg/hash"
)
// IPrintable is the interface which any value that wants to have a string
// representation has to implement. The `print` family functions will use
// this interface to convert forms to string
type IPrintable interface {
// IRepresentable is the interface which any value that wants to have a string
// representation has to implement. Serene will use this string where ever
// it needs to present a value as string.
type IRepresentable interface {
fmt.Stringer
}
// IPrintable is the interface which any value that wants to have a string
// representation for printing has to implement. The `print` family functions will use
// this interface to convert forms to string first and if the value doesn't
// implement this interface they will resort to `IRepresentable`
type IPrintable interface {
IRepresentable
PrintToString() string
}
// IDebuggable is the interface designed for converting forms to a string
// form which are meant to be used as debug data
type IDebuggable interface {
@ -45,7 +54,7 @@ type IExpr interface {
ast.ILocatable
ast.ITypable
hash.IHashable
IPrintable
IRepresentable
IDebuggable
}
@ -60,6 +69,22 @@ func (n Node) GetLocation() ast.Location {
return n.location
}
// Helper functions ===========================================================
// toRepresentables converts the given collection of IExprs to an array of
// IRepresentable. Since golangs type system is weird ( if A is an interface
// that embeds interface B you []A should be usable as []B but that's not the
// case in Golang), we need this convertor helper
func toRepresentables(ast IColl) []IRepresentable {
var params []IRepresentable
for _, x := range ast.ToSlice() {
params = append(params, x.(IRepresentable))
}
return params
}
// MakeNodeFromLocation creates a new Node for the given Location `loc`
func MakeNodeFromLocation(loc ast.Location) Node {
return Node{