Skip to content

Commit

Permalink
feat: fuzzy macro
Browse files Browse the repository at this point in the history
  • Loading branch information
qjpcpu committed Oct 22, 2022
1 parent 17ec3cd commit 09ef10d
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 11 deletions.
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var (
docMap = make(map[string]string)
)

func getBuiltinDoc(funcName string) string {
func QueryBuiltinDoc(funcName string) string {
return docMap[funcName]
}

Expand Down
21 changes: 21 additions & 0 deletions documentation.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
========== defmac ==========
Usage: (defmac name [args] body)

Theres two format macro in glisp:
1. normal macro matched by name extractly
e.g. (defmac my-m1 [] (println "my-m1"))
(my-m1) would hit the macro
(my-m2) or (my-mx) would not hit.
Besides, the macro name must be symbol.

2. fuzzy macro matched by regexp match
e.g. (defmac #`:[a-zA-Z]` [name & other-args] (println name))
Both (:name h) and (:age x) would hit the macro.

The special form macro has two important limitations:
First: fuzzy macro function must conatin at least one argument, and the first arguemnt would
be set to macro name(string) when calling.

Second: the macro name must be string when `defmac`, thus this string would be compiled as
a regular expression for matching at generating time.

========== cons ==========
Usage: (cons x seq)

Expand Down
26 changes: 22 additions & 4 deletions environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func New() *Environment {

for key, function := range BuiltinFunctions() {
sym := env.MakeSymbol(key)
env.builtins[sym.number] = MakeUserFunction(key, function, WithDoc(getBuiltinDoc(key)))
env.AddFunction(key, function, WithDoc(getBuiltinDoc(key)))
env.builtins[sym.number] = MakeUserFunction(key, function, WithDoc(QueryBuiltinDoc(key)))
env.AddFunction(key, function, WithDoc(QueryBuiltinDoc(key)))
}

env.mainfunc = MakeFunction("__main", 0, false, make([]Instruction, 0))
Expand Down Expand Up @@ -393,9 +393,15 @@ func (env *Environment) AddNamedFunction(name string, function NamedUserFunction
env.BindGlobal(name, MakeUserFunction(name, function(name), opts...))
}

func (env *Environment) AddMacro(name string, function UserFunction) {
func (env *Environment) AddMacro(name string, function UserFunction, opts ...FuntionOption) {
sym := env.MakeSymbol(name)
env.macros[sym.number] = MakeUserFunction(name, function)
env.macros[sym.number] = MakeUserFunction(name, function, opts...)
}

func (env *Environment) AddFuzzyMacro(name string, function UserFunction, opts ...FuntionOption) {
sym := env.MakeSymbol(name)
opts = append(opts, withNameRegexp(name))
env.macros[sym.number] = MakeUserFunction(name, function, opts...)
}

func (env *Environment) ImportEval() error {
Expand Down Expand Up @@ -579,6 +585,18 @@ func (env *Environment) OverrideFunction(name string, f OverrideFunction, opts .
return nil
}

func (env *Environment) searchMacro(sym SexpSymbol) (*SexpFunction, bool) {
if macro, found := env.macros[sym.number]; found {
return macro, true
}
for _, macro := range env.macros {
if macro.nameRegexp != nil && macro.nameRegexp.MatchString(sym.name) {
return macro, true
}
}
return nil, false
}

type nextSymbol struct{ counter int64 }

func (g *nextSymbol) Incr() int64 {
Expand Down
2 changes: 2 additions & 0 deletions extensions/core_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ func GetDocFunction(name string) glisp.UserFunction {
doc = expr.(*glisp.SexpFunction).Doc()
} else if mac, ok := env.FindMacro(name); ok {
doc = mac.Doc()
} else {
doc = glisp.QueryBuiltinDoc(name)
}
if doc == `` {
doc = `No document found.`
Expand Down
17 changes: 16 additions & 1 deletion functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math"
"math/big"
"os"
"regexp"
"strings"
"unicode/utf8"
)
Expand Down Expand Up @@ -722,7 +723,13 @@ func GetSourceFileFunction(name string) UserFunction {
}
}

var MissingFunction = &SexpFunction{"__missing", true, 0, false, nil, nil, nil, ``}
var MissingFunction = &SexpFunction{
name: "__missing",
user: true,
nargs: 0,
varargs: false,
fun: nil,
}

type FuntionOption func(*SexpFunction)

Expand All @@ -734,6 +741,14 @@ func WithDoc(doc string) FuntionOption {
}
}

func withNameRegexp(exp string) FuntionOption {
return func(f *SexpFunction) {
if exp != "" {
f.nameRegexp = regexp.MustCompile(exp)
}
}
}

func MakeFunction(name string, nargs int, varargs bool, fun Function, opts ...FuntionOption) *SexpFunction {
var sfun = &SexpFunction{}
sfun.name = name
Expand Down
27 changes: 23 additions & 4 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package glisp
import (
"errors"
"fmt"
"regexp"
)

type Generator struct {
Expand Down Expand Up @@ -225,9 +226,17 @@ func (gen *Generator) GenerateDefmac(args []Sexp) error {
}

var sym SexpSymbol
var regName *regexp.Regexp
switch expr := args[0].(type) {
case SexpSymbol:
sym = expr
case SexpStr:
if r, err := regexp.Compile(string(expr)); err != nil {
return err
} else {
regName = r
}
sym = gen.env.GenSymbol("__annoy")
default:
return errors.New("Definition name must by symbol")
}
Expand All @@ -236,6 +245,9 @@ func (gen *Generator) GenerateDefmac(args []Sexp) error {
if err != nil {
return err
}
if regName != nil {
sfun.nameRegexp = regName
}

gen.env.macros[sym.number] = sfun
gen.AddInstruction(PushInstr{SexpNull})
Expand Down Expand Up @@ -266,7 +278,7 @@ func (gen *Generator) GenerateMacexpand(args []Sexp) error {
if islist {
switch t := list.head.(type) {
case SexpSymbol:
macro, ismacrocall = gen.env.macros[t.number]
macro, ismacrocall = gen.env.searchMacro(t)
default:
ismacrocall = false
}
Expand All @@ -283,7 +295,7 @@ func (gen *Generator) GenerateMacexpand(args []Sexp) error {
}

env := gen.env.Duplicate()
expr, err := env.Apply(macro, macargs)
expr, err := env.Apply(macro, prependCallName(macro, list.head.(SexpSymbol), macargs))
if err != nil {
return err
}
Expand Down Expand Up @@ -574,12 +586,12 @@ func (gen *Generator) GenerateCallBySymbol(sym SexpSymbol, args []Sexp) error {
return gen.GenerateSharpQuote(args)
}

macro, found := gen.env.macros[sym.number]
macro, found := gen.env.searchMacro(sym)
if found {
// calling Apply on the current environment will screw up
// the stack, creating a duplicate environment is safer
env := gen.env.Duplicate()
expr, err := env.Apply(macro, args)
expr, err := env.Apply(macro, prependCallName(macro, sym, args))
if err != nil {
return err
}
Expand Down Expand Up @@ -831,3 +843,10 @@ func (gen *Generator) GenerateSharpQuote(args []Sexp) error {
return fmt.Errorf("sharp-quote resolve fail, unexpected s-expr %s", arg.SexpString())
}
}

func prependCallName(macro *SexpFunction, sym SexpSymbol, args []Sexp) []Sexp {
if macro.nameRegexp != nil {
return append([]Sexp{SexpStr(sym.Name())}, args...)
}
return args
}
3 changes: 3 additions & 0 deletions s-function.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package glisp

import "regexp"

type SexpFunction struct {
name string
user bool
Expand All @@ -9,6 +11,7 @@ type SexpFunction struct {
userfun UserFunction
closeScope *Stack
doc string
nameRegexp *regexp.Regexp
}

func (sf *SexpFunction) SexpString() string {
Expand Down
1 change: 0 additions & 1 deletion tests/codcov.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions tests/glisp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,33 @@ func TestListBuilder(t *testing.T) {
t.Fatal("should get list")
}
}

func TestListFuzzyMacro(t *testing.T) {
testMacro := func(script string) {
vm := loadAllExtensions(glisp.New())
vm.AddFuzzyMacro(`^fuzzy.+$`, func(env *glisp.Environment, args []glisp.Sexp) (glisp.Sexp, error) {
/* always return 1024 */
return glisp.NewSexpInt(1024), nil
})
ret, err := vm.EvalString(script)
ExpectSuccess(t, err)
ExpectEqInteger(t, 1024, ret)
}
testMacro(`(fuzzy-x 1 2 3)`)
testMacro(`(fuzzyAny 1 2 3)`)
}

func TestListFuzzyMacroName(t *testing.T) {
testMacro := func(script, docstr string) {
vm := loadAllExtensions(glisp.New())
vm.AddFuzzyMacro(`^:fuzzy-\d+$`, func(env *glisp.Environment, args []glisp.Sexp) (glisp.Sexp, error) {
num := strings.TrimPrefix(string(args[0].(glisp.SexpStr)), ":fuzzy-")
return glisp.SexpStr("number:" + num), nil
})
ret, err := vm.EvalString(script)
ExpectSuccess(t, err)
ExpectEqStr(t, docstr, ret)
}
testMacro(`(:fuzzy-123)`, `number:123`)
testMacro(`(:fuzzy-456)`, `number:456`)
}
22 changes: 22 additions & 0 deletions tests/macros.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,25 @@
(assert (= "(def + (let [+ +] (fn [a b] (+ a b))))" (sexp-str (macexpand (override + (fn [a b] (+ a b)))))))
(assert (= "(* (+ 0 1) 2)" (sexp-str (macexpand (-> 0 (+ 1) (* 2))))))
(assert (= "(* 2 (+ 1 0))" (sexp-str (macexpand (->> 0 (+ 1) (* 2))))))

;; fuzzy macro
(defmac #`always-return-\d+` [& args] 1024)
(assert (= 1024 (always-return-1024)))
(assert (= 1024 (always-return-1 "useless")))


(defmac #`^:return-number-\d+$` [name]
(let [arr (str/split (string name) "-")]
(int (aget arr 2))))
(assert (= 1024 (:return-number-1024)))
(assert (= 100 (:return-number-100)))

(defmac #`^:[a-zA-Z]+$` [name h]
(let [f (-> name (str/trim-prefix ":"))]
`(hget ~h ~f nil)))

(def ash {"a" 1 "b" 2 "c" 3 "d" 4})
(assert (= 1 (:a ash)))
(assert (= 2 (:b ash)))
(assert (nil? (:x ash)))
(assert (= 1 (-> ash (:a))))

0 comments on commit 09ef10d

Please sign in to comment.