[Bootstrap] Add the support for keywords
Both normal and ns qualified keywords with support for ns aliased keywords.
This commit is contained in:
parent
4530f35e9c
commit
3f6b9b6e29
|
@ -39,6 +39,18 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
|
|||
case ast.String:
|
||||
return form, nil
|
||||
|
||||
// Keyword evaluation rules:
|
||||
// * Keywords evaluates to themselves with respect to a
|
||||
// possible namespace alias. For example `::core/xyz`
|
||||
// will evaluates to `:serene.core/xyz` only if the ns
|
||||
// `serene.core` is loaded in the current ns with the
|
||||
// `core` alias. Also `::xyz` will evaluete to
|
||||
// `:<CURRENT_NS>/xyz`
|
||||
case ast.Keyword:
|
||||
// Eval initialize the keyword and MUTATES the state of the keyword
|
||||
// and returns the updated keyword which would be the same
|
||||
return form.(*Keyword).Eval(rt, scope)
|
||||
|
||||
// Symbol evaluation rules:
|
||||
// * If it's a NS qualified symbol (NSQS), Look it up in the external symbol table of
|
||||
// the current namespace.
|
||||
|
|
|
@ -18,18 +18,200 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
package core
|
||||
|
||||
// type Keyword struct{
|
||||
// Node
|
||||
// }
|
||||
// Keyword implementation:
|
||||
// IMPORTANT NOTE: This implementation keyword is not decent at all
|
||||
// it lacks many aspects of keywords which makes them great. But
|
||||
// it is good enough for our use case. So we'll leave it as it is.
|
||||
//
|
||||
// Keywords are simple names and just names and nothing more. They
|
||||
// match the following grammar:
|
||||
//
|
||||
// ```
|
||||
// KEYWORD = COLON [COLON] SYMBOL
|
||||
// ```
|
||||
// Normally keywords doesn't have any namespace for example `:xyz`
|
||||
// is just a name, but in order to avoid any name collision Serene
|
||||
// supports namespace qualified keywords which basically put a keyword
|
||||
// under a namespace. But it doesn't mean that you need to load a
|
||||
// namespace in order to use any keyword that lives under that ns.
|
||||
// because keywords are just names remember? There is two ways to
|
||||
// use namespace qualified keywords:
|
||||
//
|
||||
// 1. Using full namespace name. For example, `:serene.core/xyz`
|
||||
// To use this keyword you don't need the namespace `serene.core`
|
||||
// to be loaded. It's just a name after all.
|
||||
//
|
||||
// 2. Using aliased namespaces. For example `::xyz` or `::core/xyz`.
|
||||
// Using two colons instructs Serene to use aliased namespaces
|
||||
// with the keyword. In the `::xyz` since the ns part is missing
|
||||
// Serene will use the current namespace. For instance:
|
||||
//
|
||||
// ```
|
||||
// user> ::xyz
|
||||
// :user/xyz
|
||||
// ```
|
||||
// As you can see `::xyz` and `:user/xyz` (`user` being the ns name)
|
||||
// are literally the same.
|
||||
//
|
||||
// But if we provide the ns part (`::core/xyz` example), a namespace
|
||||
// with that alias has to be loaded and present in the current
|
||||
// namespace. For example:
|
||||
//
|
||||
// ```
|
||||
// user> (require '(examples.hello-world hello))
|
||||
// <ns: examples.hello-world at /home/lxsameer/src/serene/serene/bootstrap/examples/hello-world.srn>
|
||||
// user> ::hello/xyz
|
||||
// :examples.hello-world/xyz
|
||||
// ```
|
||||
// As you can see we had to load the ns with the `hello` alias to be
|
||||
// able to use the alias in a keyword.
|
||||
//
|
||||
// TODO: Cache the keywords in the runtime on the first eval so we
|
||||
// done have to evaluate them over and over agian. It can be achieved
|
||||
// by caching the `hash` value in the keyword itself and maintain a
|
||||
// hashmap in the runtime from hash codes to a pointer to the keyword.
|
||||
// But garbage collecting it would be an issue since Golang doesn't support
|
||||
// weak pointer, but since bootstrap version of Serene is used only to
|
||||
// bootstrap the compiler it's ok to ignore that for now
|
||||
|
||||
// func (n *Keyword) GetType() ast.NodeType {
|
||||
// return ast.Keyword
|
||||
// }
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// func (n NilType) String() string {
|
||||
// return ""
|
||||
// }
|
||||
"serene-lang.org/bootstrap/pkg/ast"
|
||||
"serene-lang.org/bootstrap/pkg/hash"
|
||||
)
|
||||
|
||||
// func (n NilType) ToDebugStr() string {
|
||||
// return "nil"
|
||||
// }
|
||||
type Keyword struct {
|
||||
Node
|
||||
name string
|
||||
// nsName is the string that is used as the namespace name. It
|
||||
// might be an ns alias in the current ns or the full namespace
|
||||
// as well. The first time that this keyword gets evaluated the
|
||||
// `ns` field will be populated by a pointer to the actual
|
||||
// namespace which is referrenced to via `nsName` and after that
|
||||
// nsName will be pretty much useless.
|
||||
nsName string
|
||||
// It will be populated after the first evaluation of this keyword
|
||||
ns *Namespace
|
||||
|
||||
// Is it like :serene.core/something
|
||||
nsQualified bool
|
||||
|
||||
// Is it like ::s/something ?
|
||||
aliased bool
|
||||
}
|
||||
|
||||
func (k *Keyword) GetType() ast.NodeType {
|
||||
return ast.Keyword
|
||||
}
|
||||
|
||||
func (k *Keyword) String() string {
|
||||
if k.nsQualified {
|
||||
if k.ns == nil {
|
||||
return ":" + k.nsName + "/" + k.name
|
||||
}
|
||||
return ":" + k.ns.GetName() + "/" + k.name
|
||||
}
|
||||
return ":" + k.name
|
||||
}
|
||||
|
||||
func (k *Keyword) ToDebugStr() string {
|
||||
var ns string
|
||||
if k.nsQualified {
|
||||
ns = k.ns.GetName() + "/"
|
||||
} else {
|
||||
ns = ""
|
||||
}
|
||||
return fmt.Sprintf("<keword :%s%s at %p>", ns, k.name, k)
|
||||
}
|
||||
|
||||
func (k *Keyword) Hash() uint32 {
|
||||
bytes := []byte(k.name)
|
||||
nameHash := hash.HashOf(append([]byte{byte(ast.Keyword)}, bytes...))
|
||||
|
||||
if k.nsQualified {
|
||||
if k.ns != nil {
|
||||
return hash.CombineHashes(hash.HashOf([]byte(k.ns.GetName())), nameHash)
|
||||
}
|
||||
}
|
||||
|
||||
return nameHash
|
||||
}
|
||||
|
||||
func (k *Keyword) SetNS(ns *Namespace) {
|
||||
k.ns = ns
|
||||
}
|
||||
|
||||
func (k *Keyword) IsNSQualified() bool {
|
||||
return k.nsQualified
|
||||
}
|
||||
|
||||
// Eval initializes the keyword by looking up the possible
|
||||
// alias name and set it in the keyword.
|
||||
func (k *Keyword) Eval(rt *Runtime, scope IScope) (*Keyword, IError) {
|
||||
if k.nsQualified && k.aliased {
|
||||
aliasedNS := rt.CurrentNS()
|
||||
|
||||
if k.nsName != "" {
|
||||
aliasedNS = rt.CurrentNS().LookupExternal(k.nsName)
|
||||
}
|
||||
|
||||
if aliasedNS == nil {
|
||||
return nil, MakeErrorFor(rt, k, fmt.Sprintf("can't find the alias '%s' in the current namespace.", k.nsName))
|
||||
}
|
||||
k.ns = aliasedNS
|
||||
return k, nil
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// Extracts the different parts of the keyword
|
||||
func extractParts(s string) (string, string) {
|
||||
parts := strings.Split(s, "/")
|
||||
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
||||
return "", parts[0]
|
||||
}
|
||||
|
||||
func MakeKeyword(n Node, name string) (*Keyword, IError) {
|
||||
if strings.Count(name, ":") > 2 {
|
||||
return nil, MakeParsetimeErrorf(n, "can't parse the keyword with more that two colons: '%s'", name)
|
||||
}
|
||||
|
||||
if strings.Count(name, "/") > 1 {
|
||||
return nil, MakeParsetimeErrorf(n, "illegal namespace path for the given keyword: '%s'", name)
|
||||
}
|
||||
|
||||
var nsName string
|
||||
var kwName string
|
||||
keyword := name
|
||||
|
||||
nsQualified := false
|
||||
aliased := false
|
||||
|
||||
if strings.HasPrefix(name, "::") {
|
||||
nsQualified = true
|
||||
aliased = true
|
||||
keyword = name[2:]
|
||||
} else if strings.HasPrefix(name, ":") && strings.Count(name, "/") == 1 {
|
||||
nsQualified = true
|
||||
keyword = name[1:]
|
||||
} else if strings.HasPrefix(name, ":") {
|
||||
keyword = name[1:]
|
||||
}
|
||||
|
||||
nsName, kwName = extractParts(keyword)
|
||||
|
||||
return &Keyword{
|
||||
Node: n,
|
||||
name: kwName,
|
||||
nsName: nsName,
|
||||
nsQualified: nsQualified,
|
||||
aliased: aliased,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -111,6 +111,16 @@ func (n *Namespace) hasExternal(nsName string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// LookupExternal looks up the given `alias` in the `externals` table
|
||||
// of the namespace.
|
||||
func (n *Namespace) LookupExternal(alias string) *Namespace {
|
||||
if n.hasExternal(alias) {
|
||||
return n.externals[alias]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Namespace) setExternal(name string, ns *Namespace) {
|
||||
n.externals[name] = ns
|
||||
}
|
||||
|
|
|
@ -160,6 +160,16 @@ func isValidForSymbol(char string) bool {
|
|||
return contains(validChars, c) || unicode.IsLetter(c) || unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
func readKeyword(parser IParsable) (IExpr, IError) {
|
||||
symbol, err := readRawSymbol(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := MakeNodeFromExpr(symbol)
|
||||
return MakeKeyword(node, ":"+symbol.(*Symbol).String())
|
||||
}
|
||||
|
||||
//readRawSymbol reads a symbol from the current position forward
|
||||
func readRawSymbol(parser IParsable) (IExpr, IError) {
|
||||
c := parser.peek(false)
|
||||
|
@ -197,7 +207,6 @@ func readRawSymbol(parser IParsable) (IExpr, IError) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Add support for ns qualified symbols
|
||||
node := MakeNode(parser.Buffer(), parser.GetLocation()-len(symbol), parser.GetLocation())
|
||||
sym, err := MakeSymbol(node, symbol)
|
||||
|
||||
|
@ -466,12 +475,17 @@ loop:
|
|||
readComment(parser)
|
||||
goto loop
|
||||
}
|
||||
// case '[':
|
||||
|
||||
if *c == ":" {
|
||||
return readKeyword(parser)
|
||||
}
|
||||
// if *c == "[" {
|
||||
// readVector(parser)
|
||||
// }
|
||||
|
||||
// case '{':
|
||||
// if *c == "{" {
|
||||
// readMap(parser)
|
||||
|
||||
// }
|
||||
parser.back()
|
||||
return readSymbol(parser)
|
||||
|
||||
|
|
|
@ -136,9 +136,11 @@ func (r *Runtime) IsQQSimplificationEnabled() bool {
|
|||
func nsNameToPath(ns string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
".", "/",
|
||||
// TODO: checkout the different OSs for character supports in
|
||||
// the filesystem level
|
||||
//"-", "_",
|
||||
)
|
||||
return replacer.Replace(ns) + "srn"
|
||||
return replacer.Replace(ns) + ".srn"
|
||||
}
|
||||
|
||||
// LoadNS looks up the namespace specified by the given name `ns`
|
||||
|
@ -149,6 +151,10 @@ func (r *Runtime) LoadNS(ns string) (*loadedForms, IError) {
|
|||
for _, loadPath := range r.paths {
|
||||
possibleFile := path.Join(loadPath, nsFile)
|
||||
|
||||
if r.debugMode {
|
||||
fmt.Printf("[DEBUG] Looking for '%s' in '%s'", possibleFile, loadPath)
|
||||
}
|
||||
|
||||
_, err := os.Stat(possibleFile)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -37,7 +37,6 @@ type Scope struct {
|
|||
|
||||
func (s *Scope) Lookup(rt *Runtime, k string) *Binding {
|
||||
if rt.IsDebugMode() {
|
||||
fmt.Println(s.parent)
|
||||
fmt.Printf("[DEBUG] Looking up '%s'\n", k)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,3 +36,13 @@ type IHashable interface {
|
|||
func HashOf(in []byte) uint32 {
|
||||
return crc32.Checksum(in, hashTable)
|
||||
}
|
||||
|
||||
// CombineHashes combines two hashes and return a new one
|
||||
func CombineHashes(hash1, hash2 uint32) uint32 {
|
||||
// This way of composing hashes is used in libboost and almost everyone
|
||||
// is using it. The 0x9e3779b9 is the integral part of the Golden Ratio's
|
||||
// fractional part 0.61803398875… (sqrt(5)-1)/2, multiplied by 2^32.
|
||||
// For more info: https://lkml.org/lkml/2016/4/29/838
|
||||
hash1 ^= hash2 + 0x9e3779b9 + (hash1 << 6) + (hash2 >> 2)
|
||||
return hash1
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue