diff --git a/example/blog.sx b/example/blog.sx
index 97dfae0..a580524 100644
--- a/example/blog.sx
+++ b/example/blog.sx
@@ -16,27 +16,34 @@ type (
)
var (
- posts = make(table[string]Post)
- a *api.API
+ Posts = make(table[int]Post)
+ ByDate = Posts.sort(publish_date)
+ LongTitle = Posts.select(long_title)
)
+// API Endpoints:
+// GET blog/posts
+var API *api.API
+
func init() {
- runtime.Void(posts.select(func(p Post) bool {
- if len(p.Title) > len("Hello World (10)") {
- return true
- }
- /*panic("hello")*/
- return false
- }))
-
- //runtime.Dump(posts)
-
- a = api.New(runtime.Env, "blog")
- a.RegisterTable(posts)
- a.RegisterView(posts.sort(publish_date), "posts")
+ runtime.Void(LongTitle)
+
+ //runtime.Dump(ByDate)
+
+ API = api.New(runtime.Env, "blog")
+ API.RegisterTable(Posts)
+ API.RegisterView(ByDate, "posts")
}
-func publish_date(m Post) int64 {
+func publish_date(m Post) uint64 {
i := m.PublishedAt.Unix()
- return 9223372036854775807 - i
+ return 18446744073709551615 - (uint64(9223372036854775808) + uint64(i))
+}
+
+func long_title(p Post) bool {
+ if len(p.Title) > len("Hello World (10)") {
+ return true
+ }
+ /*panic("hello")*/
+ return false
}
diff --git a/example/cms_posts.sx.html b/example/cms_posts.sx.html
new file mode 100644
index 0000000..ab668be
--- /dev/null
+++ b/example/cms_posts.sx.html
@@ -0,0 +1,51 @@
+
+document PostIndex(posts view[]Post) {
+ route: /blog
+
+
+ {{# posts.collect(frag(post Post)<<) }}
+ -
+ {{ post.Title }}
+
+ {{/ collect }}
+
+}
+
+document PostShow(post Post) {
+ route: blog/{{ post.Id }}
+ route: /blog/{{ UrlFormattedDate(post.PublishedAt) }}/{{ Slug(post.Title) }}
+ route: ://example.com/blog-post-{{ Slug(post.Title) }}
+ X-Feed-Published: {{ post.PublishedAt }}
+
+ {{# Layout("Blog - " + post.Title, frag()<<) }}
+
+ {{ post.Title }}
+ {{ post.Author }}
+ {{ post.Body }}
+
+ {{# if post.AcceptsComments }}
+ {{ PostComments(CommentsForPosts[post.Id].select(accepted_comment)) }}
+ {{/ if }}
+
+ {{/ end }}
+}
+
+frag PostComments(comments view[]Comment) {
+
+ {{# comments.collect(frag(c Comment)<<) }}
+ -
+
{{ c.Title }}
+ {{ c.Body }}
+
+ {{/ end }}
+
+}
+
+frag Layout(title string, content frag()) {
+
+
+ {{ title }}
+
+ {{ content() }}
+
+}
diff --git a/example/example.sx b/example/example.sx
deleted file mode 100644
index 41588d7..0000000
--- a/example/example.sx
+++ /dev/null
@@ -1,42 +0,0 @@
-package main
-
-type (
- Location struct {
- name string
- website string
- gps GPS
- }
-
- GPS struct {
- Lat, Lng float64
- }
-
- LocationsView view[]Location
-)
-
-var (
- locations = make(table[string]Location)
- Locations = locations.select(has_website).sort(locations_gps_ne)
- LocationsWithWebsite = LocationsView(locations).WithWebsite()
- Coordinates = Locations.collect(gps).sort(gps_ne)
-)
-
-func has_website(loc Location) bool {
- return loc.website != ""
-}
-
-func gps(loc Location) GPS {
- return loc.gps
-}
-
-func locations_gps_ne(loc Location) []float64 {
- return gps_ne(loc.gps)
-}
-
-func gps_ne(gps GPS) []float64 {
- return []float64{gps.Lat, gps.Lng}
-}
-
-func (v LocationsView) WithWebsite() view[]Location {
- return v.select(has_website)
-}
diff --git a/example/main.sx b/example/main.sx
index 431cf37..a22e62b 100644
--- a/example/main.sx
+++ b/example/main.sx
@@ -2,23 +2,27 @@ package main
import (
"fmt"
+ "os"
"simplex.sh/cas/redis"
"simplex.sh/net/http"
"simplex.sh/runtime"
"time"
)
-func init() {
-}
-
func main() {
+ fmt.Printf("Version: %s\n", SxVersion)
- // add redis store
- runtime.Env.Store = redis.New("", 0, "")
+ { // add redis store
+ addr := ""
+ if port := os.Getenv("BOXEN_REDIS_PORT"); port != "" {
+ addr = "tcp:localhost:" + port
+ }
+ runtime.Env.Store = redis.New(addr, 0, "")
+ }
// register blog api
h := http.New(":3000")
- h.Handle("/blog/", a)
+ h.Handle("/blog/", API)
runtime.Env.RegisterService(h)
go func() {
@@ -27,7 +31,7 @@ func main() {
txn := runtime.NewTransaction(runtime.Env)
now := time.Now()
for i := 0; i < 5000; i++ {
- txn.Set(posts, i, Post{
+ txn.Set(Posts, i, Post{
Title: fmt.Sprintf("Hello world (%d)", i),
PublishedAt: now,
})
diff --git a/lang/ast/ast.go b/lang/ast/ast.go
index a79b8be..5a0560e 100644
--- a/lang/ast/ast.go
+++ b/lang/ast/ast.go
@@ -991,3 +991,31 @@ type Package struct {
func (p *Package) Pos() token.Pos { return token.NoPos }
func (p *Package) End() token.Pos { return token.NoPos }
+
+// ----------------------------------------------------------------------------
+// Simplex Views and Tables
+
+type (
+
+ // A ViewType node represents a view type.
+ ViewType struct {
+ View token.Pos // position of "view" keyword
+ Key Expr // primary key type or nil
+ Value Expr
+ }
+
+ // A TableType node represents a table type.
+ TableType struct {
+ Table token.Pos // position of "table" keyword
+ Key Expr // primary key type
+ Value Expr
+ }
+)
+
+func (x *ViewType) Pos() token.Pos { return x.View }
+func (x *ViewType) End() token.Pos { return x.Value.End() }
+func (*ViewType) exprNode() {}
+
+func (x *TableType) Pos() token.Pos { return x.Table }
+func (x *TableType) End() token.Pos { return x.Value.End() }
+func (*TableType) exprNode() {}
diff --git a/lang/ast/ast_sx.go b/lang/ast/ast_sx.go
deleted file mode 100644
index 01e6d70..0000000
--- a/lang/ast/ast_sx.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package ast
-
-import (
- "simplex.sh/lang/token"
-)
-
-type (
-
- // A ViewType node represents a view type.
- ViewType struct {
- View token.Pos // position of "view" keyword
- Key Expr // primary key type or nil
- Value Expr
- }
-
- // A TableType node represents a table type.
- TableType struct {
- Table token.Pos // position of "table" keyword
- Key Expr // primary key type
- Value Expr
- }
-
- StepType int
-)
-
-func (x *ViewType) Pos() token.Pos { return x.View }
-func (x *ViewType) End() token.Pos { return x.Value.End() }
-func (*ViewType) exprNode() {}
-
-func (x *TableType) Pos() token.Pos { return x.Table }
-func (x *TableType) End() token.Pos { return x.Value.End() }
-func (*TableType) exprNode() {}
diff --git a/lang/ast/filter.go b/lang/ast/filter.go
index 3af8160..1bbf68d 100644
--- a/lang/ast/filter.go
+++ b/lang/ast/filter.go
@@ -123,7 +123,6 @@ func filterParamList(fields *FieldList, filter Filter, export bool) bool {
return b
}
-/*Simplex
func filterType(typ Expr, f Filter, export bool) bool {
switch t := typ.(type) {
case *Ident:
@@ -152,10 +151,24 @@ func filterType(typ Expr, f Filter, export bool) bool {
return b1 || b2
case *ChanType:
return filterType(t.Value, f, export)
+
+ //=== start Simplex
+ case *ViewType:
+ if t.Key == nil {
+ return filterType(t.Value, f, export)
+ }
+ b1 := filterType(t.Key, f, export)
+ b2 := filterType(t.Value, f, export)
+ return b1 || b2
+ case *TableType:
+ b1 := filterType(t.Key, f, export)
+ b2 := filterType(t.Value, f, export)
+ return b1 || b2
+ //=== end Simplex
+
}
return false
}
-*/
func filterSpec(spec Spec, f Filter, export bool) bool {
switch s := spec.(type) {
diff --git a/lang/ast/filter_sx.go b/lang/ast/filter_sx.go
deleted file mode 100644
index 3ba9c2c..0000000
--- a/lang/ast/filter_sx.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package ast
-
-// see filter.go:127
-func filterType(typ Expr, f Filter, export bool) bool {
- switch t := typ.(type) {
- case *Ident:
- return f(t.Name)
- case *ParenExpr:
- return filterType(t.X, f, export)
- case *ArrayType:
- return filterType(t.Elt, f, export)
- case *StructType:
- if filterFieldList(t.Fields, f, export) {
- t.Incomplete = true
- }
- return len(t.Fields.List) > 0
- case *FuncType:
- b1 := filterParamList(t.Params, f, export)
- b2 := filterParamList(t.Results, f, export)
- return b1 || b2
- case *InterfaceType:
- if filterFieldList(t.Methods, f, export) {
- t.Incomplete = true
- }
- return len(t.Methods.List) > 0
- case *MapType:
- b1 := filterType(t.Key, f, export)
- b2 := filterType(t.Value, f, export)
- return b1 || b2
- case *ChanType:
- return filterType(t.Value, f, export)
-
- //=== start custom
- case *ViewType:
- if t.Key == nil {
- return filterType(t.Value, f, export)
- }
- b1 := filterType(t.Key, f, export)
- b2 := filterType(t.Value, f, export)
- return b1 || b2
- case *TableType:
- b1 := filterType(t.Key, f, export)
- b2 := filterType(t.Value, f, export)
- return b1 || b2
- //=== end custom
-
- }
- return false
-}
diff --git a/lang/cmd/sxdoc/appinit.go b/lang/cmd/sxdoc/appinit.go
index 996b2b8..70da001 100644
--- a/lang/cmd/sxdoc/appinit.go
+++ b/lang/cmd/sxdoc/appinit.go
@@ -17,12 +17,9 @@ import (
)
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
+ contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path!
w.WriteHeader(http.StatusNotFound)
- servePage(w, Page{
- Title: "File " + relpath,
- Subtitle: relpath,
- Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path!
- })
+ servePage(w, relpath, "File "+relpath, "", "", contents)
}
func init() {
@@ -37,7 +34,6 @@ func init() {
*indexFiles = indexFilenames
*maxResults = 100 // reduce latency by limiting the number of fulltext search results
*indexThrottle = 0.3 // in case *indexFiles is empty (and thus the indexer is run)
- *showPlayground = true
// read .zip file and set up file systems
const zipfile = zipFilename
@@ -52,7 +48,6 @@ func init() {
readTemplates()
initHandlers()
registerPublicHandlers(http.DefaultServeMux)
- registerPlaygroundHandlers(http.DefaultServeMux)
// initialize default directory tree with corresponding timestamp.
initFSTree()
diff --git a/lang/cmd/sxdoc/codewalk.go b/lang/cmd/sxdoc/codewalk.go
index e68c0fa..f7f51d0 100644
--- a/lang/cmd/sxdoc/codewalk.go
+++ b/lang/cmd/sxdoc/codewalk.go
@@ -68,11 +68,8 @@ func codewalk(w http.ResponseWriter, r *http.Request) {
return
}
- servePage(w, Page{
- Title: "Codewalk: " + cw.Title,
- Tabtitle: cw.Title,
- Body: applyTemplate(codewalkHTML, "codewalk", cw),
- })
+ b := applyTemplate(codewalkHTML, "codewalk", cw)
+ servePage(w, cw.Title, "Codewalk: "+cw.Title, "", "", b)
}
// A Codewalk represents a single codewalk read from an XML file.
@@ -202,10 +199,8 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string
}
}
- servePage(w, Page{
- Title: "Codewalks",
- Body: applyTemplate(codewalkdirHTML, "codewalkdir", v),
- })
+ b := applyTemplate(codewalkdirHTML, "codewalkdir", v)
+ servePage(w, "", "Codewalks", "", "", b)
}
// codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi.
diff --git a/lang/cmd/sxdoc/dirtrees.go b/lang/cmd/sxdoc/dirtrees.go
index fffbf0b..91dd7ac 100644
--- a/lang/cmd/sxdoc/dirtrees.go
+++ b/lang/cmd/sxdoc/dirtrees.go
@@ -38,8 +38,15 @@ func isGoFile(fi os.FileInfo) bool {
pathpkg.Ext(name) == ".go"
}
+func isSxFile(fi os.FileInfo) bool {
+ name := fi.Name()
+ return !fi.IsDir() &&
+ len(name) > 0 && name[0] != '.' && // ignore .files
+ pathpkg.Ext(name) == ".sx"
+}
+
func isPkgFile(fi os.FileInfo) bool {
- return isGoFile(fi) &&
+ return (isGoFile(fi) || isSxFile(fi)) &&
!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
}
diff --git a/lang/cmd/sxdoc/doc.go b/lang/cmd/sxdoc/doc.go
index 956ec0b..39ecc6e 100644
--- a/lang/cmd/sxdoc/doc.go
+++ b/lang/cmd/sxdoc/doc.go
@@ -67,6 +67,8 @@ The flags are:
-maxresults=10000
maximum number of full text search results shown
(no full text index is built if maxresults <= 0)
+ -path=""
+ additional package directories (colon-separated)
-html
print HTML in command-line mode
-goroot=$GOROOT
@@ -86,8 +88,20 @@ The flags are:
zip file providing the file system to serve; disabled if empty
By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set).
-This behavior can be altered by providing an alternative $GOROOT with the -goroot
-flag.
+Additional directories may be specified via the -path flag which accepts a list
+of colon-separated paths; unrooted paths are relative to the current working
+directory. Each path is considered as an additional root for packages in order
+of appearance. The last (absolute) path element is the prefix for the package
+path. For instance, given the flag value:
+
+ path=".:/home/bar:/public"
+
+for a godoc started in /home/user/godoc, absolute paths are mapped to package paths
+as follows:
+
+ /home/user/godoc/x -> godoc/x
+ /home/bar/x -> bar/x
+ /public/x -> public/x
When godoc runs as a web server and -index is set, a search index is maintained.
The index is created at startup.
diff --git a/lang/cmd/sxdoc/format.go b/lang/cmd/sxdoc/format.go
index 80e52e4..34d290c 100644
--- a/lang/cmd/sxdoc/format.go
+++ b/lang/cmd/sxdoc/format.go
@@ -54,8 +54,6 @@ type SegmentWriter func(w io.Writer, text []byte, selections int)
// Selection is ignored.
//
func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) {
- // If we have a link writer, make the links
- // selection the last entry in selections
if lw != nil {
selections = append(selections, links)
}
@@ -110,8 +108,8 @@ func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection,
break
}
// determine the kind of segment change
- if lw != nil && index == len(selections)-1 {
- // we have a link segment change (see start of this function):
+ if index == len(selections)-1 {
+ // we have a link segment change:
// format the previous selection segment, write the
// link tag and start a new selection segment
segment(offs)
diff --git a/lang/cmd/sxdoc/godoc.go b/lang/cmd/sxdoc/godoc.go
index a9b2df5..444bae2 100644
--- a/lang/cmd/sxdoc/godoc.go
+++ b/lang/cmd/sxdoc/godoc.go
@@ -22,7 +22,6 @@ import (
"simplex.sh/lang/ast"
"simplex.sh/lang/build"
"simplex.sh/lang/doc"
- "simplex.sh/lang/format"
"simplex.sh/lang/printer"
"simplex.sh/lang/token"
"sort"
@@ -58,12 +57,12 @@ var (
// TODO(gri) consider the invariant that goroot always end in '/'
goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
+ pkgPath = flag.String("path", "", "additional package directories (colon-separated)")
// layout control
tabwidth = flag.Int("tabwidth", 4, "tab width")
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "directory containing alternate template files")
- showPlayground = flag.Bool("play", false, "enable playground in web interface")
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
@@ -84,6 +83,16 @@ var (
)
func initHandlers() {
+ // Add named directories in -path argument as
+ // subdirectories of src/pkg.
+ for _, p := range filepath.SplitList(*pkgPath) {
+ _, elem := filepath.Split(p)
+ if elem == "" {
+ log.Fatalf("invalid -path argument: %q has no final element", p)
+ }
+ fs.Bind("/src/pkg/"+elem, OS(p), "/", bindReplace)
+ }
+
fileServer = http.FileServer(&httpFS{fs})
cmdHandler = docServer{"/cmd/", "/src/cmd", false}
pkgHandler = docServer{"/pkg/", "/src/pkg", true}
@@ -317,21 +326,18 @@ func startsWithUppercase(s string) bool {
var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
-// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
-// while keeping uppercase Braz in Foo_Braz.
-func stripExampleSuffix(name string) string {
- if i := strings.LastIndex(name, "_"); i != -1 {
- if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
- name = name[:i]
- }
- }
- return name
-}
-
func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string {
var buf bytes.Buffer
for _, eg := range examples {
- name := stripExampleSuffix(eg.Name)
+ name := eg.Name
+
+ // strip lowercase braz in Foo_braz or Foo_Bar_braz from name
+ // while keeping uppercase Braz in Foo_Braz
+ if i := strings.LastIndex(name, "_"); i != -1 {
+ if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
+ name = name[:i]
+ }
+ }
if name != funcName {
continue
@@ -341,11 +347,9 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
code := node_htmlFunc(cnode, fset)
out := eg.Output
- wholeFile := true
- // Additional formatting if this is a function body.
+ // additional formatting if this is a function body
if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
- wholeFile = false
// remove surrounding braces
code = code[1 : n-1]
// unindent
@@ -354,28 +358,14 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
code = strings.TrimSpace(code[:loc[0]])
}
- }
-
- // Write out the playground code in standard Go style
- // (use tabs, no comment highlight, etc).
- play := ""
- if eg.Play != nil && *showPlayground {
- var buf bytes.Buffer
- if err := format.Node(&buf, fset, eg.Play); err != nil {
- log.Print(err)
- } else {
- play = buf.String()
- }
- }
-
- // Drop output, as the output comment will appear in the code.
- if wholeFile && play == "" {
+ } else {
+ // drop output, as the output comment will appear in the code
out = ""
}
err := exampleHTML.Execute(&buf, struct {
- Name, Doc, Code, Play, Output string
- }{eg.Name, eg.Doc, code, play, out})
+ Name, Doc, Code, Output string
+ }{eg.Name, eg.Doc, code, out})
if err != nil {
log.Print(err)
}
@@ -548,28 +538,31 @@ func readTemplates() {
// ----------------------------------------------------------------------------
// Generic HTML wrapper
-// Page describes the contents of the top-level godoc webpage.
-type Page struct {
- Title string
- Tabtitle string
- Subtitle string
- Query string
- Body []byte
-
- // filled in by servePage
- SearchBox bool
- Playground bool
- Version string
-}
-
-func servePage(w http.ResponseWriter, page Page) {
- if page.Tabtitle == "" {
- page.Tabtitle = page.Title
- }
- page.SearchBox = *indexEnabled
- page.Playground = *showPlayground
- page.Version = runtime.Version()
- if err := godocHTML.Execute(w, page); err != nil {
+func servePage(w http.ResponseWriter, tabtitle, title, subtitle, query string, content []byte) {
+ if tabtitle == "" {
+ tabtitle = title
+ }
+ d := struct {
+ Tabtitle string
+ Title string
+ Subtitle string
+ SearchBox bool
+ Query string
+ Version string
+ Menu []byte
+ Content []byte
+ }{
+ tabtitle,
+ title,
+ subtitle,
+ *indexEnabled,
+ query,
+ runtime.Version(),
+ nil,
+ content,
+ }
+
+ if err := godocHTML.Execute(w, &d); err != nil {
log.Printf("godocHTML.Execute: %s", err)
}
}
@@ -634,11 +627,7 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin
src = buf.Bytes()
}
- servePage(w, Page{
- Title: meta.Title,
- Subtitle: meta.Subtitle,
- Body: src,
- })
+ servePage(w, "", meta.Title, meta.Subtitle, "", src)
}
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
@@ -661,18 +650,6 @@ func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
return
}
-func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
- c := pathpkg.Clean(r.URL.Path)
- for strings.HasSuffix("/", c) {
- c = c[:len(c)-1]
- }
- if r.URL.Path != c {
- http.Redirect(w, r, c, http.StatusMovedPermanently)
- redirected = true
- }
- return
-}
-
func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
src, err := ReadFile(fs, abspath)
if err != nil {
@@ -686,11 +663,7 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit
FormatText(&buf, src, 1, pathpkg.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
buf.WriteString("")
- servePage(w, Page{
- Title: title + " " + relpath,
- Tabtitle: relpath,
- Body: buf.Bytes(),
- })
+ servePage(w, relpath, title+" "+relpath, "", "", buf.Bytes())
}
func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
@@ -704,11 +677,8 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
return
}
- servePage(w, Page{
- Title: "Directory " + relpath,
- Tabtitle: relpath,
- Body: applyTemplate(dirlistHTML, "dirlistHTML", list),
- })
+ contents := applyTemplate(dirlistHTML, "dirlistHTML", list)
+ servePage(w, relpath, "Directory "+relpath, "", "", contents)
}
func serveFile(w http.ResponseWriter, r *http.Request) {
@@ -764,9 +734,6 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
}
if isTextFile(abspath) {
- if redirectFile(w, r) {
- return
- }
serveTextFile(w, r, abspath, relpath, "Text file")
return
}
@@ -848,6 +815,7 @@ func remoteSearchURL(query string, html bool) string {
type PageInfo struct {
Dirname string // directory containing the package
+ PList []string // list of package names found
FSet *token.FileSet // corresponding file set
PAst *ast.File // nil if no single AST with package exports
PDoc *doc.Package // nil if no single package documentation
@@ -871,12 +839,12 @@ type docServer struct {
// fsReadDir implements ReadDir for the go/build package.
func fsReadDir(dir string) ([]os.FileInfo, error) {
- return fs.ReadDir(filepath.ToSlash(dir))
+ return fs.ReadDir(dir)
}
// fsOpenFile implements OpenFile for the go/build package.
func fsOpenFile(name string) (r io.ReadCloser, err error) {
- data, err := ReadFile(fs, filepath.ToSlash(name))
+ data, err := ReadFile(fs, name)
if err != nil {
return nil, err
}
@@ -892,95 +860,6 @@ func inList(name string, list []string) bool {
return false
}
-// packageExports is a local implementation of ast.PackageExports
-// which correctly updates each package file's comment list.
-// (The ast.PackageExports signature is frozen, hence the local
-// implementation).
-//
-func packageExports(fset *token.FileSet, pkg *ast.Package) {
- for _, src := range pkg.Files {
- cmap := ast.NewCommentMap(fset, src, src.Comments)
- ast.FileExports(src)
- src.Comments = cmap.Filter(src).Comments()
- }
-}
-
-// declNames returns the names declared by decl.
-// Method names are returned in the form Receiver_Method.
-func declNames(decl ast.Decl) (names []string) {
- switch d := decl.(type) {
- case *ast.FuncDecl:
- name := d.Name.Name
- if d.Recv != nil {
- var typeName string
- switch r := d.Recv.List[0].Type.(type) {
- case *ast.StarExpr:
- typeName = r.X.(*ast.Ident).Name
- case *ast.Ident:
- typeName = r.Name
- }
- name = typeName + "_" + name
- }
- names = []string{name}
- case *ast.GenDecl:
- for _, spec := range d.Specs {
- switch s := spec.(type) {
- case *ast.TypeSpec:
- names = append(names, s.Name.Name)
- case *ast.ValueSpec:
- for _, id := range s.Names {
- names = append(names, id.Name)
- }
- }
- }
- }
- return
-}
-
-// globalNames finds all top-level declarations in pkgs and returns a map
-// with the identifier names as keys.
-func globalNames(pkgs map[string]*ast.Package) map[string]bool {
- names := make(map[string]bool)
- for _, pkg := range pkgs {
- for _, file := range pkg.Files {
- for _, decl := range file.Decls {
- for _, name := range declNames(decl) {
- names[name] = true
- }
- }
- }
- }
- return names
-}
-
-// parseExamples gets examples for packages in pkgs from *_test.go files in dir.
-func parseExamples(fset *token.FileSet, pkgs map[string]*ast.Package, dir string) ([]*doc.Example, error) {
- var examples []*doc.Example
- filter := func(d os.FileInfo) bool {
- return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go")
- }
- testpkgs, err := parseDir(fset, dir, filter)
- if err != nil {
- return nil, err
- }
- globals := globalNames(pkgs)
- for _, testpkg := range testpkgs {
- var files []*ast.File
- for _, f := range testpkg.Files {
- files = append(files, f)
- }
- for _, e := range doc.Examples(files...) {
- name := stripExampleSuffix(e.Name)
- if name == "" || globals[name] {
- examples = append(examples, e)
- } else {
- log.Printf("skipping example Example%s: refers to unknown function or type", e.Name)
- }
- }
- }
- return examples, nil
-}
-
// getPageInfo returns the PageInfo for a package directory abspath. If the
// parameter genAST is set, an AST containing only the package exports is
// computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
@@ -989,24 +868,27 @@ func parseExamples(fset *token.FileSet, pkgs map[string]*ast.Package, dir string
// directories, PageInfo.Dirs is nil. If a directory read error occurred,
// PageInfo.Err is set to the respective error but the error is not logged.
//
-func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) PageInfo {
+func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo {
var pkgFiles []string
- // Restrict to the package files
+ // If we're showing the default package, restrict to the ones
// that would be used when building the package on this
// system. This makes sure that if there are separate
// implementations for, say, Windows vs Unix, we don't
// jumble them all together.
- // Note: Uses current binary's GOOS/GOARCH.
- // To use different pair, such as if we allowed the user
- // to choose, set ctxt.GOOS and ctxt.GOARCH before
- // calling ctxt.ScanDir.
- ctxt := build.Default
- ctxt.IsAbsPath = pathpkg.IsAbs
- ctxt.ReadDir = fsReadDir
- ctxt.OpenFile = fsOpenFile
- if dir, err := ctxt.ImportDir(abspath, 0); err == nil {
- pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
+ if pkgname == "" {
+ // Note: Uses current binary's GOOS/GOARCH.
+ // To use different pair, such as if we allowed the user
+ // to choose, set ctxt.GOOS and ctxt.GOARCH before
+ // calling ctxt.ScanDir.
+ ctxt := build.Default
+ ctxt.IsAbsPath = pathpkg.IsAbs
+ ctxt.ReadDir = fsReadDir
+ ctxt.OpenFile = fsOpenFile
+ dir, err := ctxt.ImportDir(abspath, 0)
+ if err == nil {
+ pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
+ }
}
// filter function to select the desired .go files
@@ -1027,12 +909,15 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) Page
// get package ASTs
fset := token.NewFileSet()
pkgs, err := parseDir(fset, abspath, filter)
- if err != nil {
+ if err != nil && pkgs == nil {
+ // only report directory read errors, ignore parse errors
+ // (may be able to extract partial package information)
return PageInfo{Dirname: abspath, Err: err}
}
// select package
var pkg *ast.Package // selected package
+ var plist []string // list of other package (names), if any
if len(pkgs) == 1 {
// Exactly one package - select it.
for _, p := range pkgs {
@@ -1040,23 +925,66 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) Page
}
} else if len(pkgs) > 1 {
- // More than one package - report an error.
- var buf bytes.Buffer
+ // Multiple packages - select the best matching package: The
+ // 1st choice is the package with pkgname, the 2nd choice is
+ // the package with dirname, and the 3rd choice is a package
+ // that is not called "main" if there is exactly one such
+ // package. Otherwise, don't select a package.
+ dirpath, dirname := pathpkg.Split(abspath)
+
+ // If the dirname is "go" we might be in a sub-directory for
+ // .go files - use the outer directory name instead for better
+ // results.
+ if dirname == "go" {
+ _, dirname = pathpkg.Split(pathpkg.Clean(dirpath))
+ }
+
+ var choice3 *ast.Package
+ loop:
for _, p := range pkgs {
- if buf.Len() > 0 {
- fmt.Fprintf(&buf, ", ")
+ switch {
+ case p.Name == pkgname:
+ pkg = p
+ break loop // 1st choice; we are done
+ case p.Name == dirname:
+ pkg = p // 2nd choice
+ case p.Name != "main":
+ choice3 = p
}
- fmt.Fprintf(&buf, p.Name)
}
- return PageInfo{
- Dirname: abspath,
- Err: fmt.Errorf("%s contains more than one package: %s", abspath, buf.Bytes()),
+ if pkg == nil && len(pkgs) == 2 {
+ pkg = choice3
+ }
+
+ // Compute the list of other packages
+ // (excluding the selected package, if any).
+ plist = make([]string, len(pkgs))
+ i := 0
+ for name := range pkgs {
+ if pkg == nil || name != pkg.Name {
+ plist[i] = name
+ i++
+ }
}
+ plist = plist[0:i]
+ sort.Strings(plist)
}
- examples, err := parseExamples(fset, pkgs, abspath)
- if err != nil {
- log.Println("parsing examples:", err)
+ // get examples from *_test.go files
+ var examples []*doc.Example
+ filter = func(d os.FileInfo) bool {
+ return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go")
+ }
+ if testpkgs, err := parseDir(fset, abspath, filter); err != nil {
+ log.Println("parsing test files:", err)
+ } else {
+ for _, testpkg := range testpkgs {
+ var files []*ast.File
+ for _, f := range testpkg.Files {
+ files = append(files, f)
+ }
+ examples = append(examples, doc.Examples(files...)...)
+ }
}
// compute package documentation
@@ -1078,9 +1006,9 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) Page
// TODO(gri) Consider eliminating export filtering in this mode,
// or perhaps eliminating the mode altogether.
if mode&noFiltering == 0 {
- packageExports(fset, pkg)
+ ast.PackageExports(pkg)
}
- past = ast.MergePackageFiles(pkg, 0)
+ past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments)
}
}
@@ -1105,6 +1033,7 @@ func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) Page
return PageInfo{
Dirname: abspath,
+ PList: plist,
FSet: fset,
PAst: past,
PDoc: pdoc,
@@ -1128,7 +1057,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if relpath == builtinPkgPath {
mode = noFiltering
}
- info := h.getPageInfo(abspath, relpath, mode)
+ info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode)
if info.Err != nil {
log.Print(info.Err)
serveError(w, r, relpath, info.Err)
@@ -1136,7 +1065,8 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if mode&noHtml != 0 {
- serveText(w, applyTemplate(packageText, "packageText", info))
+ contents := applyTemplate(packageText, "packageText", info)
+ serveText(w, contents)
return
}
@@ -1173,12 +1103,8 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tabtitle = "Commands"
}
- servePage(w, Page{
- Title: title,
- Tabtitle: tabtitle,
- Subtitle: subtitle,
- Body: applyTemplate(packageHTML, "packageHTML", info),
- })
+ contents := applyTemplate(packageHTML, "packageHTML", info)
+ servePage(w, tabtitle, title, subtitle, "", contents)
}
// ----------------------------------------------------------------------------
@@ -1255,7 +1181,8 @@ func search(w http.ResponseWriter, r *http.Request) {
result := lookup(query)
if getPageInfoMode(r)&noHtml != 0 {
- serveText(w, applyTemplate(searchText, "searchText", result))
+ contents := applyTemplate(searchText, "searchText", result)
+ serveText(w, contents)
return
}
@@ -1266,12 +1193,8 @@ func search(w http.ResponseWriter, r *http.Request) {
title = fmt.Sprintf(`No results found for query %q`, query)
}
- servePage(w, Page{
- Title: title,
- Tabtitle: query,
- Query: query,
- Body: applyTemplate(searchHTML, "searchHTML", result),
- })
+ contents := applyTemplate(searchHTML, "searchHTML", result)
+ servePage(w, query, title, "", query, contents)
}
// ----------------------------------------------------------------------------
diff --git a/lang/cmd/sxdoc/index.go b/lang/cmd/sxdoc/index.go
index 85e6807..6c3f7b9 100644
--- a/lang/cmd/sxdoc/index.go
+++ b/lang/cmd/sxdoc/index.go
@@ -148,7 +148,7 @@ func init() {
// sanity check: if nKinds is too large, the SpotInfo
// accessor functions may need to be updated
if nKinds > 8 {
- panic("internal error: nKinds > 8")
+ panic("nKinds > 8")
}
}
@@ -457,6 +457,12 @@ func (x *Indexer) addSnippet(s *Snippet) int {
return index
}
+func (x *Indexer) visitComment(c *ast.CommentGroup) {
+ if c != nil {
+ ast.Walk(x, c)
+ }
+}
+
func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
if id != nil {
lists, found := x.words[id.Name]
@@ -480,24 +486,20 @@ func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
}
}
-func (x *Indexer) visitFieldList(kind SpotKind, list *ast.FieldList) {
- for _, f := range list.List {
- x.decl = nil // no snippets for fields
- for _, name := range f.Names {
- x.visitIdent(kind, name)
- }
- ast.Walk(x, f.Type)
- // ignore tag - not indexed at the moment
- }
-}
-
-func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) {
+func (x *Indexer) visitSpec(spec ast.Spec, isVarDecl bool) {
switch n := spec.(type) {
case *ast.ImportSpec:
+ x.visitComment(n.Doc)
x.visitIdent(ImportDecl, n.Name)
- // ignore path - not indexed at the moment
+ ast.Walk(x, n.Path)
+ x.visitComment(n.Comment)
case *ast.ValueSpec:
+ x.visitComment(n.Doc)
+ kind := ConstDecl
+ if isVarDecl {
+ kind = VarDecl
+ }
for _, n := range n.Names {
x.visitIdent(kind, n)
}
@@ -505,51 +507,57 @@ func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) {
for _, v := range n.Values {
ast.Walk(x, v)
}
+ x.visitComment(n.Comment)
case *ast.TypeSpec:
+ x.visitComment(n.Doc)
x.visitIdent(TypeDecl, n.Name)
ast.Walk(x, n.Type)
- }
-}
-
-func (x *Indexer) visitGenDecl(decl *ast.GenDecl) {
- kind := VarDecl
- if decl.Tok == token.CONST {
- kind = ConstDecl
- }
- x.decl = decl
- for _, s := range decl.Specs {
- x.visitSpec(kind, s)
+ x.visitComment(n.Comment)
}
}
func (x *Indexer) Visit(node ast.Node) ast.Visitor {
+ // TODO(gri): methods in interface types are categorized as VarDecl
switch n := node.(type) {
case nil:
- // nothing to do
+ return nil
case *ast.Ident:
x.visitIdent(Use, n)
- case *ast.FieldList:
- x.visitFieldList(VarDecl, n)
-
- case *ast.InterfaceType:
- x.visitFieldList(MethodDecl, n.Methods)
+ case *ast.Field:
+ x.decl = nil // no snippets for fields
+ x.visitComment(n.Doc)
+ for _, m := range n.Names {
+ x.visitIdent(VarDecl, m)
+ }
+ ast.Walk(x, n.Type)
+ ast.Walk(x, n.Tag)
+ x.visitComment(n.Comment)
case *ast.DeclStmt:
- // local declarations should only be *ast.GenDecls;
- // ignore incorrect ASTs
if decl, ok := n.Decl.(*ast.GenDecl); ok {
+ // local declarations can only be *ast.GenDecls
x.decl = nil // no snippets for local declarations
- x.visitGenDecl(decl)
+ x.visitComment(decl.Doc)
+ for _, s := range decl.Specs {
+ x.visitSpec(s, decl.Tok == token.VAR)
+ }
+ } else {
+ // handle error case gracefully
+ ast.Walk(x, n.Decl)
}
case *ast.GenDecl:
x.decl = n
- x.visitGenDecl(n)
+ x.visitComment(n.Doc)
+ for _, s := range n.Specs {
+ x.visitSpec(s, n.Tok == token.VAR)
+ }
case *ast.FuncDecl:
+ x.visitComment(n.Doc)
kind := FuncDecl
if n.Recv != nil {
kind = MethodDecl
@@ -563,11 +571,15 @@ func (x *Indexer) Visit(node ast.Node) ast.Visitor {
}
case *ast.File:
+ x.visitComment(n.Doc)
x.decl = nil
x.visitIdent(PackageClause, n.Name)
for _, d := range n.Decls {
ast.Walk(x, d)
}
+ // don't visit package level comments for now
+ // to avoid duplicate visiting from individual
+ // nodes
default:
return x
@@ -610,7 +622,7 @@ func (x *Indexer) addFile(filename string, goFile bool) (file *token.File, ast *
// the file set implementation changed or we have another error.
base := x.fset.Base()
if x.sources.Len() != base {
- panic("internal error: file base incorrect")
+ panic("internal error - file base incorrect")
}
// append file contents (src) to x.sources
diff --git a/lang/cmd/sxdoc/main.go b/lang/cmd/sxdoc/main.go
index 39a4fcb..6057243 100644
--- a/lang/cmd/sxdoc/main.go
+++ b/lang/cmd/sxdoc/main.go
@@ -46,7 +46,6 @@ import (
"runtime"
"simplex.sh/lang/ast"
"simplex.sh/lang/build"
- "simplex.sh/lang/printer"
"strings"
)
@@ -74,12 +73,9 @@ var (
)
func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
+ contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path!
w.WriteHeader(http.StatusNotFound)
- servePage(w, Page{
- Title: "File " + relpath,
- Subtitle: relpath,
- Body: applyTemplate(errorHTML, "errorHTML", err), // err may contain an absolute path!
- })
+ servePage(w, relpath, "File "+relpath, "", "", contents)
}
func usage() {
@@ -225,8 +221,6 @@ func main() {
// Print content that would be served at the URL *urlFlag.
if *urlFlag != "" {
registerPublicHandlers(http.DefaultServeMux)
- initFSTree()
- updateMetadata()
// Try up to 10 fetches, following redirects.
urlstr := *urlFlag
for i := 0; i < 10; i++ {
@@ -284,7 +278,10 @@ func main() {
}
registerPublicHandlers(http.DefaultServeMux)
- registerPlaygroundHandlers(http.DefaultServeMux)
+
+ // Playground handlers are not available in local godoc.
+ http.HandleFunc("/compile", disabledHandler)
+ http.HandleFunc("/share", disabledHandler)
// Initialize default directory tree with corresponding timestamp.
// (Do it in a goroutine so that launch is quick.)
@@ -372,11 +369,13 @@ func main() {
}
mode |= showSource
}
+ // TODO(gri): Provide a mechanism (flag?) to select a package
+ // if there are multiple packages in a directory.
// first, try as package unless forced as command
var info PageInfo
if !forceCmd {
- info = pkgHandler.getPageInfo(abspath, relpath, mode)
+ info = pkgHandler.getPageInfo(abspath, relpath, "", mode)
}
// second, try as command unless the path is absolute
@@ -384,7 +383,7 @@ func main() {
var cinfo PageInfo
if !filepath.IsAbs(path) {
abspath = pathpkg.Join(cmdHandler.fsRoot, path)
- cinfo = cmdHandler.getPageInfo(abspath, relpath, mode)
+ cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode)
}
// determine what to use
@@ -422,24 +421,20 @@ func main() {
filter := func(s string) bool { return rx.MatchString(s) }
switch {
case info.PAst != nil:
- cmap := ast.NewCommentMap(info.FSet, info.PAst, info.PAst.Comments)
ast.FilterFile(info.PAst, filter)
// Special case: Don't use templates for printing
// so we only get the filtered declarations without
// package clause or extra whitespace.
for i, d := range info.PAst.Decls {
- // determine the comments associated with d only
- comments := cmap.Filter(d).Comments()
- cn := &printer.CommentedNode{Node: d, Comments: comments}
if i > 0 {
fmt.Println()
}
if *html {
var buf bytes.Buffer
- writeNode(&buf, info.FSet, cn)
+ writeNode(&buf, info.FSet, d)
FormatText(os.Stdout, buf.Bytes(), -1, true, "", nil)
} else {
- writeNode(os.Stdout, info.FSet, cn)
+ writeNode(os.Stdout, info.FSet, d)
}
fmt.Println()
}
@@ -464,3 +459,9 @@ type httpWriter struct {
func (w *httpWriter) Header() http.Header { return w.h }
func (w *httpWriter) WriteHeader(code int) { w.code = code }
+
+// disabledHandler serves a 501 "Not Implemented" response.
+func disabledHandler(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotImplemented)
+ fmt.Fprint(w, "This functionality is not available via local godoc.")
+}
diff --git a/lang/cmd/sxdoc/parser.go b/lang/cmd/sxdoc/parser.go
index c1500d0..eee28ea 100644
--- a/lang/cmd/sxdoc/parser.go
+++ b/lang/cmd/sxdoc/parser.go
@@ -15,6 +15,7 @@ import (
"simplex.sh/lang/ast"
"simplex.sh/lang/parser"
"simplex.sh/lang/token"
+ "strings"
)
func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
@@ -28,7 +29,17 @@ func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.Fil
func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.Package, first error) {
pkgs = make(map[string]*ast.Package)
for _, filename := range filenames {
- file, err := parseFile(fset, filename, parser.ParseComments)
+ var (
+ file *ast.File
+ err error
+ )
+
+ if strings.HasSuffix(filename, ".sx") {
+ file, err = parseFile(fset, filename, parser.ParseComments|parser.SimplexExtentions)
+ } else {
+ file, err = parseFile(fset, filename, parser.ParseComments)
+ }
+
if err != nil {
if first == nil {
first = err
diff --git a/lang/cmd/sxdoc/play-appengine.go b/lang/cmd/sxdoc/play-appengine.go
deleted file mode 100644
index 1d093e9..0000000
--- a/lang/cmd/sxdoc/play-appengine.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// App Engine godoc Playground functionality.
-
-// +build appengine
-
-package main
-
-import (
- "io"
- "net/http"
-
- "appengine"
- "appengine/urlfetch"
-)
-
-func bounceToPlayground(w http.ResponseWriter, req *http.Request) {
- c := appengine.NewContext(req)
- client := urlfetch.Client(c)
- url := playgroundBaseURL + req.URL.Path
- defer req.Body.Close()
- resp, err := client.Post(url, req.Header.Get("Content-type"), req.Body)
- if err != nil {
- http.Error(w, "Internal Server Error", 500)
- c.Errorf("making POST request:", err)
- return
- }
- defer resp.Body.Close()
- if _, err := io.Copy(w, resp.Body); err != nil {
- http.Error(w, "Internal Server Error", 500)
- c.Errorf("making POST request:", err)
- }
-}
diff --git a/lang/cmd/sxdoc/play-local.go b/lang/cmd/sxdoc/play-local.go
deleted file mode 100644
index 637ce5e..0000000
--- a/lang/cmd/sxdoc/play-local.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Stand-alone godoc Playground functionality.
-
-// +build !appengine
-
-package main
-
-import (
- "io"
- "net/http"
- "net/url"
-)
-
-var playgroundScheme, playgroundHost string
-
-func init() {
- u, err := url.Parse(playgroundBaseURL)
- if err != nil {
- panic(err)
- }
- playgroundScheme = u.Scheme
- playgroundHost = u.Host
-}
-
-// bounceToPlayground forwards the request to play.golang.org.
-func bounceToPlayground(w http.ResponseWriter, req *http.Request) {
- defer req.Body.Close()
- req.URL.Scheme = playgroundScheme
- req.URL.Host = playgroundHost
- resp, err := http.Post(req.URL.String(), req.Header.Get("Content-type"), req.Body)
- if err != nil {
- http.Error(w, err.Error(), 500)
- return
- }
- w.WriteHeader(resp.StatusCode)
- io.Copy(w, resp.Body)
- resp.Body.Close()
-}
diff --git a/lang/cmd/sxdoc/play.go b/lang/cmd/sxdoc/play.go
deleted file mode 100644
index ff7aafd..0000000
--- a/lang/cmd/sxdoc/play.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2012 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Common Playground functionality.
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "simplex.sh/lang/format"
-)
-
-// The server that will service compile and share requests.
-const playgroundBaseURL = "http://play.golang.org"
-
-func registerPlaygroundHandlers(mux *http.ServeMux) {
- if *showPlayground {
- mux.HandleFunc("/compile", bounceToPlayground)
- mux.HandleFunc("/share", bounceToPlayground)
- } else {
- mux.HandleFunc("/compile", disabledHandler)
- mux.HandleFunc("/share", disabledHandler)
- }
- http.HandleFunc("/fmt", fmtHandler)
-}
-
-type fmtResponse struct {
- Body string
- Error string
-}
-
-// fmtHandler takes a Go program in its "body" form value, formats it with
-// standard gofmt formatting, and writes a fmtResponse as a JSON object.
-func fmtHandler(w http.ResponseWriter, r *http.Request) {
- resp := new(fmtResponse)
- body, err := format.Source([]byte(r.FormValue("body")))
- if err != nil {
- resp.Error = err.Error()
- } else {
- resp.Body = string(body)
- }
- json.NewEncoder(w).Encode(resp)
-}
-
-// disabledHandler serves a 501 "Not Implemented" response.
-func disabledHandler(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotImplemented)
- fmt.Fprint(w, "This functionality is not available via local godoc.")
-}
diff --git a/lang/cmd/sxdoc/setup-godoc-app.bash b/lang/cmd/sxdoc/setup-godoc-app.bash
index 792e0d4..b8dc4dc 100644
--- a/lang/cmd/sxdoc/setup-godoc-app.bash
+++ b/lang/cmd/sxdoc/setup-godoc-app.bash
@@ -4,14 +4,13 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-# This script creates a complete godoc app in $APPDIR.
-# It copies the cmd/godoc and src/pkg/go/... sources from GOROOT,
-# synthesizes an app.yaml file, and creates the .zip, index, and
-# configuration files.
+# This script creates the .zip, index, and configuration files for running
+# godoc on app-engine.
#
# If an argument is provided it is assumed to be the app-engine godoc directory.
-# Without an argument, $APPDIR is used instead. If GOROOT is not set, "go env"
-# is consulted to find the $GOROOT.
+# Without an argument, $APPDIR is used instead. If GOROOT is not set, the
+# current working directory is assumed to be $GOROOT. Various sanity checks
+# prevent accidents.
#
# The script creates a .zip file representing the $GOROOT file system
# and computes the correspondig search index files. These files are then
@@ -30,8 +29,8 @@ error() {
getArgs() {
if [ -z $GOROOT ]; then
- GOROOT=$(go env GOROOT)
- echo "GOROOT not set explicitly, using $GOROOT instead"
+ GOROOT=$(pwd)
+ echo "GOROOT not set, using cwd instead"
fi
if [ -z $APPDIR ]; then
if [ $# == 0 ]; then
@@ -48,8 +47,14 @@ getArgs() {
if [ ! -x $GOROOT/bin/godoc ]; then
error "$GOROOT/bin/godoc does not exist or is not executable"
fi
- if [ -e $APPDIR ]; then
- error "$APPDIR exists; check and remove it before trying again"
+ if [ ! -d $APPDIR ]; then
+ error "$APPDIR is not a directory"
+ fi
+ if [ ! -e $APPDIR/app.yaml ]; then
+ error "$APPDIR is not an app-engine directory; missing file app.yaml"
+ fi
+ if [ ! -d $APPDIR/godoc ]; then
+ error "$APPDIR is missing directory godoc"
fi
# reporting
@@ -57,32 +62,12 @@ getArgs() {
echo "APPDIR = $APPDIR"
}
-copyGodoc() {
- echo "*** copy $GOROOT/src/cmd/godoc to $APPDIR/godoc"
- cp -r $GOROOT/src/cmd/godoc $APPDIR/godoc
-}
-
-copyGoPackages() {
- echo "*** copy $GOROOT/src/pkg/go to $APPDIR/newgo and rewrite imports"
- cp -r $GOROOT/src/pkg/go $APPDIR/newgo
- find $APPDIR/newgo -type d -name testdata | xargs rm -r
- gofiles=$(find $APPDIR -name '*.go')
- sed -i '' 's_^\(."\)\(go/[a-z]*\)"$_\1new\2"_' $gofiles
- sed -i '' 's_^\(import "\)\(go/[a-z]*\)"$_\1new\2"_' $gofiles
-}
-
-makeAppYaml() {
- echo "*** make $APPDIR/app.yaml"
- cat > $APPDIR/app.yaml < 0 {
+ switch p.tok {
+ case token.VIEW:
+ return p.parseViewType()
+ case token.TABLE:
+ return p.parseTableType()
+ }
+ }
+ //=== end custom
+
// no type found
return nil
}
-*/
func (p *parser) tryType() ast.Expr {
typ := p.tryIdentOrType()
@@ -1313,7 +1322,6 @@ func isTypeName(x ast.Expr) bool {
return true
}
-/*Simplex
// isLiteralType returns true iff x is a legal composite literal type.
func isLiteralType(x ast.Expr) bool {
switch t := x.(type) {
@@ -1325,12 +1333,17 @@ func isLiteralType(x ast.Expr) bool {
case *ast.ArrayType:
case *ast.StructType:
case *ast.MapType:
+
+ //=== start custom
+ case *ast.ViewType:
+ case *ast.TableType:
+ //=== end custom
+
default:
return false // all other nodes are not legal composite literal types
}
return true
}
-*/
// If x is of the form *T, deref returns T, otherwise it returns x.
func deref(x ast.Expr) ast.Expr {
@@ -1367,7 +1380,6 @@ func (p *parser) checkExprOrType(x ast.Expr) ast.Expr {
return x
}
-/*Simplex
// If lhs is set and the result is an identifier, it is not resolved.
func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr {
if p.trace {
@@ -1386,6 +1398,19 @@ L:
switch p.tok {
case token.IDENT:
x = p.parseSelector(p.checkExpr(x))
+
+ //=== start custom
+ case token.SELECT:
+ if p.mode&SimplexExtentions > 0 {
+ pos := p.pos
+ sel := &ast.Ident{NamePos: pos, Name: "select"}
+ p.next()
+ x = &ast.SelectorExpr{X: p.checkExpr(x), Sel: sel}
+ } else {
+ break L
+ }
+ //=== end custom
+
case token.LPAREN:
x = p.parseTypeAssertion(p.checkExpr(x))
default:
@@ -1421,7 +1446,6 @@ L:
return x
}
-*/
// If lhs is set and the result is an identifier, it is not resolved.
func (p *parser) parseUnaryExpr(lhs bool) ast.Expr {
@@ -2396,3 +2420,34 @@ func (p *parser) parseFile() *ast.File {
Comments: p.comments,
}
}
+
+// ----------------------------------------------------------------------------
+// Simplex Views and Tables
+
+func (p *parser) parseViewType() *ast.ViewType {
+ if p.trace {
+ defer un(trace(p, "ViewType"))
+ }
+
+ pos := p.expect(token.VIEW)
+ p.expect(token.LBRACK)
+ key := p.tryType()
+ p.expect(token.RBRACK)
+ value := p.parseType()
+
+ return &ast.ViewType{View: pos, Key: key, Value: value}
+}
+
+func (p *parser) parseTableType() *ast.TableType {
+ if p.trace {
+ defer un(trace(p, "TableType"))
+ }
+
+ pos := p.expect(token.TABLE)
+ p.expect(token.LBRACK)
+ key := p.parseType()
+ p.expect(token.RBRACK)
+ value := p.parseType()
+
+ return &ast.TableType{Table: pos, Key: key, Value: value}
+}
diff --git a/lang/parser/parser_sx.go b/lang/parser/parser_sx.go
deleted file mode 100644
index dbc7552..0000000
--- a/lang/parser/parser_sx.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package parser
-
-import (
- "simplex.sh/lang/ast"
- "simplex.sh/lang/token"
-)
-
-func (p *parser) parseViewType() *ast.ViewType {
- if p.trace {
- defer un(trace(p, "ViewType"))
- }
-
- pos := p.expect(token.VIEW)
- p.expect(token.LBRACK)
- key := p.tryType()
- p.expect(token.RBRACK)
- value := p.parseType()
-
- return &ast.ViewType{View: pos, Key: key, Value: value}
-}
-
-func (p *parser) parseTableType() *ast.TableType {
- if p.trace {
- defer un(trace(p, "TableType"))
- }
-
- pos := p.expect(token.TABLE)
- p.expect(token.LBRACK)
- key := p.parseType()
- p.expect(token.RBRACK)
- value := p.parseType()
-
- return &ast.TableType{Table: pos, Key: key, Value: value}
-}
-
-// If the result is an identifier, it is not resolved.
-//
-// NOTE(fd) see parser.go:959
-func (p *parser) tryIdentOrType() ast.Expr {
- switch p.tok {
- case token.IDENT:
- return p.parseTypeName()
- case token.LBRACK:
- return p.parseArrayType()
- case token.STRUCT:
- return p.parseStructType()
- case token.MUL:
- return p.parsePointerType()
- case token.FUNC:
- typ, _ := p.parseFuncType()
- return typ
- case token.INTERFACE:
- return p.parseInterfaceType()
- case token.MAP:
- return p.parseMapType()
- case token.CHAN, token.ARROW:
- return p.parseChanType()
- case token.LPAREN:
- lparen := p.pos
- p.next()
- typ := p.parseType()
- rparen := p.expect(token.RPAREN)
- return &ast.ParenExpr{Lparen: lparen, X: typ, Rparen: rparen}
- }
-
- //=== start custom
- if p.mode&SimplexExtentions > 0 {
- switch p.tok {
- case token.VIEW:
- return p.parseViewType()
- case token.TABLE:
- return p.parseTableType()
- }
- }
- //=== end custom
-
- // no type found
- return nil
-}
-
-// isLiteralType returns true iff x is a legal composite literal type.
-//
-// NOTE(fd) see parser.go:1322
-func isLiteralType(x ast.Expr) bool {
- switch t := x.(type) {
- case *ast.BadExpr:
- case *ast.Ident:
- case *ast.SelectorExpr:
- _, isIdent := t.X.(*ast.Ident)
- return isIdent
- case *ast.ArrayType:
- case *ast.StructType:
- case *ast.MapType:
-
- //=== start custom
- case *ast.ViewType:
- case *ast.TableType:
- //=== end custom
-
- default:
- return false // all other nodes are not legal composite literal types
- }
- return true
-}
-
-// If lhs is set and the result is an identifier, it is not resolved.
-//
-// NOTE(fd) see parser.go:1376
-func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr {
- if p.trace {
- defer un(trace(p, "PrimaryExpr"))
- }
-
- x := p.parseOperand(lhs)
-L:
- for {
- switch p.tok {
- case token.PERIOD:
- p.next()
- if lhs {
- p.resolve(x)
- }
- switch p.tok {
- case token.IDENT:
- x = p.parseSelector(p.checkExpr(x))
-
- //=== start custom
- case token.SELECT:
- if p.mode&SimplexExtentions > 0 {
- pos := p.pos
- sel := &ast.Ident{NamePos: pos, Name: "select"}
- p.next()
- x = &ast.SelectorExpr{X: p.checkExpr(x), Sel: sel}
- } else {
- break L
- }
- //=== end custom
-
- case token.LPAREN:
- x = p.parseTypeAssertion(p.checkExpr(x))
- default:
- pos := p.pos
- p.errorExpected(pos, "selector or type assertion")
- p.next() // make progress
- x = &ast.BadExpr{From: pos, To: p.pos}
- }
- case token.LBRACK:
- if lhs {
- p.resolve(x)
- }
- x = p.parseIndexOrSlice(p.checkExpr(x))
- case token.LPAREN:
- if lhs {
- p.resolve(x)
- }
- x = p.parseCallOrConversion(p.checkExprOrType(x))
- case token.LBRACE:
- if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
- if lhs {
- p.resolve(x)
- }
- x = p.parseLiteralValue(x)
- } else {
- break L
- }
- default:
- break L
- }
- lhs = false // no need to try to resolve again
- }
-
- return x
-}
diff --git a/lang/token/token.go b/lang/token/token.go
index ebd879e..8418c9a 100644
--- a/lang/token/token.go
+++ b/lang/token/token.go
@@ -12,220 +12,226 @@ import "strconv"
// Token is the set of lexical tokens of the Go programming language.
type Token int
-/*Simplex
// The list of tokens.
const (
- // Special tokens
- ILLEGAL Token = iota
- EOF
- COMMENT
-
- literal_beg
- // Identifiers and basic type literals
- // (these tokens stand for classes of literals)
- IDENT // main
- INT // 12345
- FLOAT // 123.45
- IMAG // 123.45i
- CHAR // 'a'
- STRING // "abc"
- literal_end
-
- operator_beg
- // Operators and delimiters
- ADD // +
- SUB // -
- MUL // *
- QUO // /
- REM // %
-
- AND // &
- OR // |
- XOR // ^
- SHL // <<
- SHR // >>
- AND_NOT // &^
-
- ADD_ASSIGN // +=
- SUB_ASSIGN // -=
- MUL_ASSIGN // *=
- QUO_ASSIGN // /=
- REM_ASSIGN // %=
-
- AND_ASSIGN // &=
- OR_ASSIGN // |=
- XOR_ASSIGN // ^=
- SHL_ASSIGN // <<=
- SHR_ASSIGN // >>=
- AND_NOT_ASSIGN // &^=
-
- LAND // &&
- LOR // ||
- ARROW // <-
- INC // ++
- DEC // --
-
- EQL // ==
- LSS // <
- GTR // >
- ASSIGN // =
- NOT // !
-
- NEQ // !=
- LEQ // <=
- GEQ // >=
- DEFINE // :=
- ELLIPSIS // ...
-
- LPAREN // (
- LBRACK // [
- LBRACE // {
- COMMA // ,
- PERIOD // .
-
- RPAREN // )
- RBRACK // ]
- RBRACE // }
- SEMICOLON // ;
- COLON // :
- operator_end
-
- keyword_beg
- // Keywords
- BREAK
- CASE
- CHAN
- CONST
- CONTINUE
-
- DEFAULT
- DEFER
- ELSE
- FALLTHROUGH
- FOR
-
- FUNC
- GO
- GOTO
- IF
- IMPORT
-
- INTERFACE
- MAP
- PACKAGE
- RANGE
- RETURN
-
- SELECT
- STRUCT
- SWITCH
- TYPE
- VAR
- keyword_end
+ // Special tokens
+ ILLEGAL Token = iota
+ EOF
+ COMMENT
+
+ literal_beg
+ // Identifiers and basic type literals
+ // (these tokens stand for classes of literals)
+ IDENT // main
+ INT // 12345
+ FLOAT // 123.45
+ IMAG // 123.45i
+ CHAR // 'a'
+ STRING // "abc"
+ literal_end
+
+ operator_beg
+ // Operators and delimiters
+ ADD // +
+ SUB // -
+ MUL // *
+ QUO // /
+ REM // %
+
+ AND // &
+ OR // |
+ XOR // ^
+ SHL // <<
+ SHR // >>
+ AND_NOT // &^
+
+ ADD_ASSIGN // +=
+ SUB_ASSIGN // -=
+ MUL_ASSIGN // *=
+ QUO_ASSIGN // /=
+ REM_ASSIGN // %=
+
+ AND_ASSIGN // &=
+ OR_ASSIGN // |=
+ XOR_ASSIGN // ^=
+ SHL_ASSIGN // <<=
+ SHR_ASSIGN // >>=
+ AND_NOT_ASSIGN // &^=
+
+ LAND // &&
+ LOR // ||
+ ARROW // <-
+ INC // ++
+ DEC // --
+
+ EQL // ==
+ LSS // <
+ GTR // >
+ ASSIGN // =
+ NOT // !
+
+ NEQ // !=
+ LEQ // <=
+ GEQ // >=
+ DEFINE // :=
+ ELLIPSIS // ...
+
+ LPAREN // (
+ LBRACK // [
+ LBRACE // {
+ COMMA // ,
+ PERIOD // .
+
+ RPAREN // )
+ RBRACK // ]
+ RBRACE // }
+ SEMICOLON // ;
+ COLON // :
+ operator_end
+
+ keyword_beg
+ // Keywords
+ BREAK
+ CASE
+ CHAN
+ CONST
+ CONTINUE
+
+ DEFAULT
+ DEFER
+ ELSE
+ FALLTHROUGH
+ FOR
+
+ FUNC
+ GO
+ GOTO
+ IF
+ IMPORT
+
+ INTERFACE
+ MAP
+ PACKAGE
+ RANGE
+ RETURN
+
+ SELECT
+ STRUCT
+ SWITCH
+ TYPE
+ VAR
+
+ //=== start custom
+ VIEW
+ TABLE
+ //=== end custom
+ keyword_end
)
-*/
-/*Simplex
var tokens = [...]string{
- ILLEGAL: "ILLEGAL",
-
- EOF: "EOF",
- COMMENT: "COMMENT",
-
- IDENT: "IDENT",
- INT: "INT",
- FLOAT: "FLOAT",
- IMAG: "IMAG",
- CHAR: "CHAR",
- STRING: "STRING",
-
- ADD: "+",
- SUB: "-",
- MUL: "*",
- QUO: "/",
- REM: "%",
-
- AND: "&",
- OR: "|",
- XOR: "^",
- SHL: "<<",
- SHR: ">>",
- AND_NOT: "&^",
-
- ADD_ASSIGN: "+=",
- SUB_ASSIGN: "-=",
- MUL_ASSIGN: "*=",
- QUO_ASSIGN: "/=",
- REM_ASSIGN: "%=",
-
- AND_ASSIGN: "&=",
- OR_ASSIGN: "|=",
- XOR_ASSIGN: "^=",
- SHL_ASSIGN: "<<=",
- SHR_ASSIGN: ">>=",
- AND_NOT_ASSIGN: "&^=",
-
- LAND: "&&",
- LOR: "||",
- ARROW: "<-",
- INC: "++",
- DEC: "--",
-
- EQL: "==",
- LSS: "<",
- GTR: ">",
- ASSIGN: "=",
- NOT: "!",
-
- NEQ: "!=",
- LEQ: "<=",
- GEQ: ">=",
- DEFINE: ":=",
- ELLIPSIS: "...",
-
- LPAREN: "(",
- LBRACK: "[",
- LBRACE: "{",
- COMMA: ",",
- PERIOD: ".",
-
- RPAREN: ")",
- RBRACK: "]",
- RBRACE: "}",
- SEMICOLON: ";",
- COLON: ":",
-
- BREAK: "break",
- CASE: "case",
- CHAN: "chan",
- CONST: "const",
- CONTINUE: "continue",
-
- DEFAULT: "default",
- DEFER: "defer",
- ELSE: "else",
- FALLTHROUGH: "fallthrough",
- FOR: "for",
-
- FUNC: "func",
- GO: "go",
- GOTO: "goto",
- IF: "if",
- IMPORT: "import",
-
- INTERFACE: "interface",
- MAP: "map",
- PACKAGE: "package",
- RANGE: "range",
- RETURN: "return",
-
- SELECT: "select",
- STRUCT: "struct",
- SWITCH: "switch",
- TYPE: "type",
- VAR: "var",
+ ILLEGAL: "ILLEGAL",
+
+ EOF: "EOF",
+ COMMENT: "COMMENT",
+
+ IDENT: "IDENT",
+ INT: "INT",
+ FLOAT: "FLOAT",
+ IMAG: "IMAG",
+ CHAR: "CHAR",
+ STRING: "STRING",
+
+ ADD: "+",
+ SUB: "-",
+ MUL: "*",
+ QUO: "/",
+ REM: "%",
+
+ AND: "&",
+ OR: "|",
+ XOR: "^",
+ SHL: "<<",
+ SHR: ">>",
+ AND_NOT: "&^",
+
+ ADD_ASSIGN: "+=",
+ SUB_ASSIGN: "-=",
+ MUL_ASSIGN: "*=",
+ QUO_ASSIGN: "/=",
+ REM_ASSIGN: "%=",
+
+ AND_ASSIGN: "&=",
+ OR_ASSIGN: "|=",
+ XOR_ASSIGN: "^=",
+ SHL_ASSIGN: "<<=",
+ SHR_ASSIGN: ">>=",
+ AND_NOT_ASSIGN: "&^=",
+
+ LAND: "&&",
+ LOR: "||",
+ ARROW: "<-",
+ INC: "++",
+ DEC: "--",
+
+ EQL: "==",
+ LSS: "<",
+ GTR: ">",
+ ASSIGN: "=",
+ NOT: "!",
+
+ NEQ: "!=",
+ LEQ: "<=",
+ GEQ: ">=",
+ DEFINE: ":=",
+ ELLIPSIS: "...",
+
+ LPAREN: "(",
+ LBRACK: "[",
+ LBRACE: "{",
+ COMMA: ",",
+ PERIOD: ".",
+
+ RPAREN: ")",
+ RBRACK: "]",
+ RBRACE: "}",
+ SEMICOLON: ";",
+ COLON: ":",
+
+ BREAK: "break",
+ CASE: "case",
+ CHAN: "chan",
+ CONST: "const",
+ CONTINUE: "continue",
+
+ DEFAULT: "default",
+ DEFER: "defer",
+ ELSE: "else",
+ FALLTHROUGH: "fallthrough",
+ FOR: "for",
+
+ FUNC: "func",
+ GO: "go",
+ GOTO: "goto",
+ IF: "if",
+ IMPORT: "import",
+
+ INTERFACE: "interface",
+ MAP: "map",
+ PACKAGE: "package",
+ RANGE: "range",
+ RETURN: "return",
+
+ SELECT: "select",
+ STRUCT: "struct",
+ SWITCH: "switch",
+ TYPE: "type",
+ VAR: "var",
+
+ //=== start custom
+ VIEW: "view",
+ TABLE: "table",
+ //=== end custom
}
-*/
// String returns the string corresponding to the token tok.
// For operators, delimiters, and keywords the string is the actual
diff --git a/lang/token/token_sx.go b/lang/token/token_sx.go
deleted file mode 100644
index 114ef89..0000000
--- a/lang/token/token_sx.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package token
-
-// The list of tokens.
-//
-// see token.go:17
-const (
- // Special tokens
- ILLEGAL Token = iota
- EOF
- COMMENT
-
- literal_beg
- // Identifiers and basic type literals
- // (these tokens stand for classes of literals)
- IDENT // main
- INT // 12345
- FLOAT // 123.45
- IMAG // 123.45i
- CHAR // 'a'
- STRING // "abc"
- literal_end
-
- operator_beg
- // Operators and delimiters
- ADD // +
- SUB // -
- MUL // *
- QUO // /
- REM // %
-
- AND // &
- OR // |
- XOR // ^
- SHL // <<
- SHR // >>
- AND_NOT // &^
-
- ADD_ASSIGN // +=
- SUB_ASSIGN // -=
- MUL_ASSIGN // *=
- QUO_ASSIGN // /=
- REM_ASSIGN // %=
-
- AND_ASSIGN // &=
- OR_ASSIGN // |=
- XOR_ASSIGN // ^=
- SHL_ASSIGN // <<=
- SHR_ASSIGN // >>=
- AND_NOT_ASSIGN // &^=
-
- LAND // &&
- LOR // ||
- ARROW // <-
- INC // ++
- DEC // --
-
- EQL // ==
- LSS // <
- GTR // >
- ASSIGN // =
- NOT // !
-
- NEQ // !=
- LEQ // <=
- GEQ // >=
- DEFINE // :=
- ELLIPSIS // ...
-
- LPAREN // (
- LBRACK // [
- LBRACE // {
- COMMA // ,
- PERIOD // .
-
- RPAREN // )
- RBRACK // ]
- RBRACE // }
- SEMICOLON // ;
- COLON // :
- operator_end
-
- keyword_beg
- // Keywords
- BREAK
- CASE
- CHAN
- CONST
- CONTINUE
-
- DEFAULT
- DEFER
- ELSE
- FALLTHROUGH
- FOR
-
- FUNC
- GO
- GOTO
- IF
- IMPORT
-
- INTERFACE
- MAP
- PACKAGE
- RANGE
- RETURN
-
- SELECT
- STRUCT
- SWITCH
- TYPE
- VAR
-
- //=== start custom
- VIEW
- TABLE
- //=== end custom
- keyword_end
-)
-
-// see token.go:129
-var tokens = [...]string{
- ILLEGAL: "ILLEGAL",
-
- EOF: "EOF",
- COMMENT: "COMMENT",
-
- IDENT: "IDENT",
- INT: "INT",
- FLOAT: "FLOAT",
- IMAG: "IMAG",
- CHAR: "CHAR",
- STRING: "STRING",
-
- ADD: "+",
- SUB: "-",
- MUL: "*",
- QUO: "/",
- REM: "%",
-
- AND: "&",
- OR: "|",
- XOR: "^",
- SHL: "<<",
- SHR: ">>",
- AND_NOT: "&^",
-
- ADD_ASSIGN: "+=",
- SUB_ASSIGN: "-=",
- MUL_ASSIGN: "*=",
- QUO_ASSIGN: "/=",
- REM_ASSIGN: "%=",
-
- AND_ASSIGN: "&=",
- OR_ASSIGN: "|=",
- XOR_ASSIGN: "^=",
- SHL_ASSIGN: "<<=",
- SHR_ASSIGN: ">>=",
- AND_NOT_ASSIGN: "&^=",
-
- LAND: "&&",
- LOR: "||",
- ARROW: "<-",
- INC: "++",
- DEC: "--",
-
- EQL: "==",
- LSS: "<",
- GTR: ">",
- ASSIGN: "=",
- NOT: "!",
-
- NEQ: "!=",
- LEQ: "<=",
- GEQ: ">=",
- DEFINE: ":=",
- ELLIPSIS: "...",
-
- LPAREN: "(",
- LBRACK: "[",
- LBRACE: "{",
- COMMA: ",",
- PERIOD: ".",
-
- RPAREN: ")",
- RBRACK: "]",
- RBRACE: "}",
- SEMICOLON: ";",
- COLON: ":",
-
- BREAK: "break",
- CASE: "case",
- CHAN: "chan",
- CONST: "const",
- CONTINUE: "continue",
-
- DEFAULT: "default",
- DEFER: "defer",
- ELSE: "else",
- FALLTHROUGH: "fallthrough",
- FOR: "for",
-
- FUNC: "func",
- GO: "go",
- GOTO: "goto",
- IF: "if",
- IMPORT: "import",
-
- INTERFACE: "interface",
- MAP: "map",
- PACKAGE: "package",
- RANGE: "range",
- RETURN: "return",
-
- SELECT: "select",
- STRUCT: "struct",
- SWITCH: "switch",
- TYPE: "type",
- VAR: "var",
-
- //=== start custom
- VIEW: "view",
- TABLE: "table",
- //=== end custom
-}
diff --git a/lang/types/builtins.go b/lang/types/builtins.go
index 0c9c7aa..01a5cb0 100644
--- a/lang/types/builtins.go
+++ b/lang/types/builtins.go
@@ -11,378 +11,384 @@ import (
"simplex.sh/lang/token"
)
-/*Simplex
// builtin typechecks a built-in call. The built-in type is bin, and iota is the current
// value of iota or -1 if iota doesn't have a value in the current context. The result
// of the call is returned via x. If the call has type errors, the returned x is marked
// as invalid (x.mode == invalid).
//
func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
- args := call.Args
- id := bin.id
-
- // declare before goto's
- var arg0 ast.Expr
- var typ0 Type
-
- // check argument count
- n := len(args)
- msg := ""
- if n < bin.nargs {
- msg = "not enough"
- } else if !bin.isVariadic && n > bin.nargs {
- msg = "too many"
- }
- if msg != "" {
- check.invalidOp(call.Pos(), msg+" arguments for %s (expected %d, found %d)", call, bin.nargs, n)
- goto Error
- }
-
- // common case: evaluate first argument if present;
- // if it is an expression, x has the expression value
- if n > 0 {
- arg0 = args[0]
- switch id {
- case _Make, _New:
- // argument must be a type
- typ0 = check.typ(arg0, false)
- if typ0 == Typ[Invalid] {
- goto Error
- }
- case _Trace:
- // _Trace implementation does the work
- default:
- // argument must be an expression
- check.expr(x, arg0, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- typ0 = underlying(x.typ)
- }
- }
-
- switch id {
- case _Append:
- s, ok := typ0.(*Slice)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a typed slice", x)
- goto Error
- }
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- // TODO(gri) check assignability
- }
- x.mode = value
- x.typ = s
-
- case _Cap, _Len:
- mode := invalid
- var val interface{}
- switch typ := implicitDeref(typ0).(type) {
- case *Basic:
- if isString(typ) && id == _Len {
- if x.mode == constant {
- mode = constant
- val = int64(len(x.val.(string)))
- } else {
- mode = value
- }
- }
-
- case *Array:
- mode = value
- if !containsCallsOrReceives(arg0) {
- mode = constant
- val = typ.Len
- }
-
- case *Slice, *Chan:
- mode = value
-
- case *Map:
- if id == _Len {
- mode = value
- }
- }
-
- if mode == invalid {
- check.invalidArg(x.pos(), "%s for %s", x, bin.name)
- goto Error
- }
- x.mode = mode
- x.typ = Typ[Int]
- x.val = val
-
- case _Close:
- ch, ok := typ0.(*Chan)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a channel", x)
- goto Error
- }
- if ch.Dir&ast.SEND == 0 {
- check.invalidArg(x.pos(), "%s must not be a receive-only channel", x)
- goto Error
- }
- x.mode = novalue
-
- case _Complex:
- if !check.complexArg(x) {
- goto Error
- }
-
- var y operand
- check.expr(&y, args[1], nil, iota)
- if y.mode == invalid {
- goto Error
- }
- if !check.complexArg(&y) {
- goto Error
- }
-
- check.convertUntyped(x, y.typ)
- if x.mode == invalid {
- goto Error
- }
- check.convertUntyped(&y, x.typ)
- if y.mode == invalid {
- goto Error
- }
-
- if !isIdentical(x.typ, y.typ) {
- check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
- goto Error
- }
-
- typ := underlying(x.typ).(*Basic)
- if x.mode == constant && y.mode == constant {
- x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, typ)
- } else {
- x.mode = value
- }
-
- switch typ.Kind {
- case Float32:
- x.typ = Typ[Complex64]
- case Float64:
- x.typ = Typ[Complex128]
- case UntypedInt, UntypedRune, UntypedFloat:
- x.typ = Typ[UntypedComplex]
- default:
- check.invalidArg(x.pos(), "float32 or float64 arguments expected")
- goto Error
- }
-
- case _Copy:
- var y operand
- check.expr(&y, args[1], nil, iota)
- if y.mode == invalid {
- goto Error
- }
-
- var dst, src Type
- if t, ok := typ0.(*Slice); ok {
- dst = t.Elt
- }
- switch t := underlying(y.typ).(type) {
- case *Basic:
- if isString(y.typ) {
- src = Typ[Byte]
- }
- case *Slice:
- src = t.Elt
- }
-
- if dst == nil || src == nil {
- check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y)
- goto Error
- }
-
- if !isIdentical(dst, src) {
- check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
- goto Error
- }
-
- x.mode = value
- x.typ = Typ[Int]
-
- case _Delete:
- m, ok := typ0.(*Map)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a map", x)
- goto Error
- }
- check.expr(x, args[1], nil, iota)
- if x.mode == invalid {
- goto Error
- }
- if !x.isAssignable(m.Key) {
- check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key)
- goto Error
- }
- x.mode = novalue
-
- case _Imag, _Real:
- if !isComplex(typ0) {
- check.invalidArg(x.pos(), "%s must be a complex number", x)
- goto Error
- }
- if x.mode == constant {
- // nothing to do for x.val == 0
- if !isZeroConst(x.val) {
- c := x.val.(Complex)
- if id == _Real {
- x.val = c.Re
- } else {
- x.val = c.Im
- }
- }
- } else {
- x.mode = value
- }
- k := Invalid
- switch typ0.(*Basic).Kind {
- case Complex64:
- k = Float32
- case Complex128:
- k = Float64
- case UntypedComplex:
- k = UntypedFloat
- default:
- unreachable()
- }
- x.typ = Typ[k]
-
- case _Make:
- var min int // minimum number of arguments
- switch underlying(typ0).(type) {
- case *Slice:
- min = 2
- case *Map, *Chan:
- min = 1
- default:
- check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0)
- goto Error
- }
- if n := len(args); n < min || min+1 < n {
- check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n)
- goto Error
- }
- var sizes []interface{} // constant integer arguments, if any
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, iota)
- if x.isInteger() {
- if x.mode == constant {
- if isNegConst(x.val) {
- check.invalidArg(x.pos(), "%s must not be negative", x)
- // safe to continue
- } else {
- sizes = append(sizes, x.val) // x.val >= 0
- }
- }
- } else {
- check.invalidArg(x.pos(), "%s must be an integer", x)
- // safe to continue
- }
- }
- if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) {
- check.invalidArg(args[1].Pos(), "length and capacity swapped")
- // safe to continue
- }
- x.mode = variable
- x.typ = typ0
-
- case _New:
- x.mode = variable
- x.typ = &Pointer{Base: typ0}
-
- case _Panic, _Print, _Println:
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, -1)
- }
- x.mode = novalue
-
- case _Recover:
- x.mode = value
- x.typ = new(Interface)
-
- case _Alignof:
- x.mode = constant
- x.typ = Typ[Uintptr]
- // For now we return 1 always as it satisfies the spec's alignment guarantees.
- // TODO(gri) Extend typechecker API so that platform-specific values can be
- // provided.
- x.val = int64(1)
-
- case _Offsetof:
- if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok {
- check.invalidArg(x.pos(), "%s is not a selector", x)
- goto Error
- }
- x.mode = constant
- x.typ = Typ[Uintptr]
- // because of the size guarantees for basic types (> 0 for some),
- // returning 0 is only correct if two distinct non-zero size
- // structs can have the same address (the spec permits that)
- x.val = int64(0)
-
- case _Sizeof:
- x.mode = constant
- x.typ = Typ[Uintptr]
- x.val = sizeof(check.ctxt, typ0)
-
- case _Assert:
- // assert(pred) causes a typechecker error if pred is false.
- // The result of assert is the value of pred if there is no error.
- // Note: assert is only available in self-test mode.
- if x.mode != constant || !isBoolean(typ0) {
- check.invalidArg(x.pos(), "%s is not a boolean constant", x)
- goto Error
- }
- pred, ok := x.val.(bool)
- if !ok {
- check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x)
- goto Error
- }
- if !pred {
- check.errorf(call.Pos(), "%s failed", call)
- // compile-time assertion failure - safe to continue
- }
-
- case _Trace:
- // trace(x, y, z, ...) dumps the positions, expressions, and
- // values of its arguments. The result of trace is the value
- // of the first argument.
- // Note: trace is only available in self-test mode.
- if len(args) == 0 {
- check.dump("%s: trace() without arguments", call.Pos())
- x.mode = novalue
- x.expr = call
- return
- }
- var t operand
- x1 := x
- for _, arg := range args {
- check.rawExpr(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T))
- check.dump("%s: %s", x1.pos(), x1)
- x1 = &t // use incoming x only for first argument
- }
-
- default:
- check.invalidAST(call.Pos(), "unknown builtin id %d", id)
- goto Error
- }
-
- x.expr = call
- return
+ args := call.Args
+ id := bin.id
+
+ // declare before goto's
+ var arg0 ast.Expr
+ var typ0 Type
+
+ // check argument count
+ n := len(args)
+ msg := ""
+ if n < bin.nargs {
+ msg = "not enough"
+ } else if !bin.isVariadic && n > bin.nargs {
+ msg = "too many"
+ }
+ if msg != "" {
+ check.invalidOp(call.Pos(), msg+" arguments for %s (expected %d, found %d)", call, bin.nargs, n)
+ goto Error
+ }
+
+ // common case: evaluate first argument if present;
+ // if it is an expression, x has the expression value
+ if n > 0 {
+ arg0 = args[0]
+ switch id {
+ case _Make, _New:
+ // argument must be a type
+ typ0 = check.typ(arg0, false)
+ if typ0 == Typ[Invalid] {
+ goto Error
+ }
+ case _Trace:
+ // _Trace implementation does the work
+ default:
+ // argument must be an expression
+ check.expr(x, arg0, nil, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ typ0 = underlying(x.typ)
+ }
+ }
+
+ switch id {
+ case _Append:
+ s, ok := typ0.(*Slice)
+ if !ok {
+ check.invalidArg(x.pos(), "%s is not a typed slice", x)
+ goto Error
+ }
+ for _, arg := range args[1:] {
+ check.expr(x, arg, nil, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ // TODO(gri) check assignability
+ }
+ x.mode = value
+ x.typ = s
+
+ case _Cap, _Len:
+ mode := invalid
+ var val interface{}
+ switch typ := implicitDeref(typ0).(type) {
+ case *Basic:
+ if isString(typ) && id == _Len {
+ if x.mode == constant {
+ mode = constant
+ val = int64(len(x.val.(string)))
+ } else {
+ mode = value
+ }
+ }
+
+ case *Array:
+ mode = value
+ if !containsCallsOrReceives(arg0) {
+ mode = constant
+ val = typ.Len
+ }
+
+ case *Slice, *Chan:
+ mode = value
+
+ case *Map:
+ if id == _Len {
+ mode = value
+ }
+
+ case *View, *Table:
+ if id == _Len {
+ mode = value
+ }
+
+ }
+
+ if mode == invalid {
+ check.invalidArg(x.pos(), "%s for %s", x, bin.name)
+ goto Error
+ }
+ x.mode = mode
+ x.typ = Typ[Int]
+ x.val = val
+
+ case _Close:
+ ch, ok := typ0.(*Chan)
+ if !ok {
+ check.invalidArg(x.pos(), "%s is not a channel", x)
+ goto Error
+ }
+ if ch.Dir&ast.SEND == 0 {
+ check.invalidArg(x.pos(), "%s must not be a receive-only channel", x)
+ goto Error
+ }
+ x.mode = novalue
+
+ case _Complex:
+ if !check.complexArg(x) {
+ goto Error
+ }
+
+ var y operand
+ check.expr(&y, args[1], nil, iota)
+ if y.mode == invalid {
+ goto Error
+ }
+ if !check.complexArg(&y) {
+ goto Error
+ }
+
+ check.convertUntyped(x, y.typ)
+ if x.mode == invalid {
+ goto Error
+ }
+ check.convertUntyped(&y, x.typ)
+ if y.mode == invalid {
+ goto Error
+ }
+
+ if !isIdentical(x.typ, y.typ) {
+ check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
+ goto Error
+ }
+
+ typ := underlying(x.typ).(*Basic)
+ if x.mode == constant && y.mode == constant {
+ x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, typ)
+ } else {
+ x.mode = value
+ }
+
+ switch typ.Kind {
+ case Float32:
+ x.typ = Typ[Complex64]
+ case Float64:
+ x.typ = Typ[Complex128]
+ case UntypedInt, UntypedRune, UntypedFloat:
+ x.typ = Typ[UntypedComplex]
+ default:
+ check.invalidArg(x.pos(), "float32 or float64 arguments expected")
+ goto Error
+ }
+
+ case _Copy:
+ var y operand
+ check.expr(&y, args[1], nil, iota)
+ if y.mode == invalid {
+ goto Error
+ }
+
+ var dst, src Type
+ if t, ok := typ0.(*Slice); ok {
+ dst = t.Elt
+ }
+ switch t := underlying(y.typ).(type) {
+ case *Basic:
+ if isString(y.typ) {
+ src = Typ[Byte]
+ }
+ case *Slice:
+ src = t.Elt
+ }
+
+ if dst == nil || src == nil {
+ check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y)
+ goto Error
+ }
+
+ if !isIdentical(dst, src) {
+ check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
+ goto Error
+ }
+
+ x.mode = value
+ x.typ = Typ[Int]
+
+ case _Delete:
+ m, ok := typ0.(*Map)
+ if !ok {
+ check.invalidArg(x.pos(), "%s is not a map", x)
+ goto Error
+ }
+ check.expr(x, args[1], nil, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ if !x.isAssignable(m.Key) {
+ check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key)
+ goto Error
+ }
+ x.mode = novalue
+
+ case _Imag, _Real:
+ if !isComplex(typ0) {
+ check.invalidArg(x.pos(), "%s must be a complex number", x)
+ goto Error
+ }
+ if x.mode == constant {
+ // nothing to do for x.val == 0
+ if !isZeroConst(x.val) {
+ c := x.val.(Complex)
+ if id == _Real {
+ x.val = c.Re
+ } else {
+ x.val = c.Im
+ }
+ }
+ } else {
+ x.mode = value
+ }
+ k := Invalid
+ switch typ0.(*Basic).Kind {
+ case Complex64:
+ k = Float32
+ case Complex128:
+ k = Float64
+ case UntypedComplex:
+ k = UntypedFloat
+ default:
+ unreachable()
+ }
+ x.typ = Typ[k]
+
+ case _Make:
+ var min int // minimum number of arguments
+ switch underlying(typ0).(type) {
+ case *Slice:
+ min = 2
+ case *Map, *Chan:
+ min = 1
+ case *Table:
+ min = 1
+ default:
+ check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0)
+ goto Error
+ }
+ if n := len(args); n < min || min+1 < n {
+ check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n)
+ goto Error
+ }
+ var sizes []interface{} // constant integer arguments, if any
+ for _, arg := range args[1:] {
+ check.expr(x, arg, nil, iota)
+ if x.isInteger() {
+ if x.mode == constant {
+ if isNegConst(x.val) {
+ check.invalidArg(x.pos(), "%s must not be negative", x)
+ // safe to continue
+ } else {
+ sizes = append(sizes, x.val) // x.val >= 0
+ }
+ }
+ } else {
+ check.invalidArg(x.pos(), "%s must be an integer", x)
+ // safe to continue
+ }
+ }
+ if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) {
+ check.invalidArg(args[1].Pos(), "length and capacity swapped")
+ // safe to continue
+ }
+ x.mode = variable
+ x.typ = typ0
+
+ case _New:
+ x.mode = variable
+ x.typ = &Pointer{Base: typ0}
+
+ case _Panic, _Print, _Println:
+ for _, arg := range args[1:] {
+ check.expr(x, arg, nil, -1)
+ }
+ x.mode = novalue
+
+ case _Recover:
+ x.mode = value
+ x.typ = new(Interface)
+
+ case _Alignof:
+ x.mode = constant
+ x.typ = Typ[Uintptr]
+ // For now we return 1 always as it satisfies the spec's alignment guarantees.
+ // TODO(gri) Extend typechecker API so that platform-specific values can be
+ // provided.
+ x.val = int64(1)
+
+ case _Offsetof:
+ if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok {
+ check.invalidArg(x.pos(), "%s is not a selector", x)
+ goto Error
+ }
+ x.mode = constant
+ x.typ = Typ[Uintptr]
+ // because of the size guarantees for basic types (> 0 for some),
+ // returning 0 is only correct if two distinct non-zero size
+ // structs can have the same address (the spec permits that)
+ x.val = int64(0)
+
+ case _Sizeof:
+ x.mode = constant
+ x.typ = Typ[Uintptr]
+ x.val = sizeof(check.ctxt, typ0)
+
+ case _Assert:
+ // assert(pred) causes a typechecker error if pred is false.
+ // The result of assert is the value of pred if there is no error.
+ // Note: assert is only available in self-test mode.
+ if x.mode != constant || !isBoolean(typ0) {
+ check.invalidArg(x.pos(), "%s is not a boolean constant", x)
+ goto Error
+ }
+ pred, ok := x.val.(bool)
+ if !ok {
+ check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x)
+ goto Error
+ }
+ if !pred {
+ check.errorf(call.Pos(), "%s failed", call)
+ // compile-time assertion failure - safe to continue
+ }
+
+ case _Trace:
+ // trace(x, y, z, ...) dumps the positions, expressions, and
+ // values of its arguments. The result of trace is the value
+ // of the first argument.
+ // Note: trace is only available in self-test mode.
+ if len(args) == 0 {
+ check.dump("%s: trace() without arguments", call.Pos())
+ x.mode = novalue
+ x.expr = call
+ return
+ }
+ var t operand
+ x1 := x
+ for _, arg := range args {
+ check.rawExpr(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T))
+ check.dump("%s: %s", x1.pos(), x1)
+ x1 = &t // use incoming x only for first argument
+ }
+
+ default:
+ check.invalidAST(call.Pos(), "unknown builtin id %d", id)
+ goto Error
+ }
+
+ x.expr = call
+ return
Error:
- x.mode = invalid
- x.expr = call
+ x.mode = invalid
+ x.expr = call
}
-*/
// implicitDeref returns A if typ is of the form *A and A is an array;
// otherwise it returns typ.
diff --git a/lang/types/builtins_sx.go b/lang/types/builtins_sx.go
deleted file mode 100644
index 15a20d7..0000000
--- a/lang/types/builtins_sx.go
+++ /dev/null
@@ -1,386 +0,0 @@
-package types
-
-import (
- "simplex.sh/lang/ast"
- "simplex.sh/lang/token"
-)
-
-// builtin typechecks a built-in call. The built-in type is bin, and iota is the current
-// value of iota or -1 if iota doesn't have a value in the current context. The result
-// of the call is returned via x. If the call has type errors, the returned x is marked
-// as invalid (x.mode == invalid).
-//
-// see builtins.go:20
-func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
- args := call.Args
- id := bin.id
-
- // declare before goto's
- var arg0 ast.Expr
- var typ0 Type
-
- // check argument count
- n := len(args)
- msg := ""
- if n < bin.nargs {
- msg = "not enough"
- } else if !bin.isVariadic && n > bin.nargs {
- msg = "too many"
- }
- if msg != "" {
- check.invalidOp(call.Pos(), msg+" arguments for %s (expected %d, found %d)", call, bin.nargs, n)
- goto Error
- }
-
- // common case: evaluate first argument if present;
- // if it is an expression, x has the expression value
- if n > 0 {
- arg0 = args[0]
- switch id {
- case _Make, _New:
- // argument must be a type
- typ0 = check.typ(arg0, false)
- if typ0 == Typ[Invalid] {
- goto Error
- }
- case _Trace:
- // _Trace implementation does the work
- default:
- // argument must be an expression
- check.expr(x, arg0, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- typ0 = underlying(x.typ)
- }
- }
-
- switch id {
- case _Append:
- s, ok := typ0.(*Slice)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a typed slice", x)
- goto Error
- }
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- // TODO(gri) check assignability
- }
- x.mode = value
- x.typ = s
-
- case _Cap, _Len:
- mode := invalid
- var val interface{}
- switch typ := implicitDeref(typ0).(type) {
- case *Basic:
- if isString(typ) && id == _Len {
- if x.mode == constant {
- mode = constant
- val = int64(len(x.val.(string)))
- } else {
- mode = value
- }
- }
-
- case *Array:
- mode = value
- if !containsCallsOrReceives(arg0) {
- mode = constant
- val = typ.Len
- }
-
- case *Slice, *Chan:
- mode = value
-
- case *Map:
- if id == _Len {
- mode = value
- }
-
- case *View, *Table:
- if id == _Len {
- mode = value
- }
-
- }
-
- if mode == invalid {
- check.invalidArg(x.pos(), "%s for %s", x, bin.name)
- goto Error
- }
- x.mode = mode
- x.typ = Typ[Int]
- x.val = val
-
- case _Close:
- ch, ok := typ0.(*Chan)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a channel", x)
- goto Error
- }
- if ch.Dir&ast.SEND == 0 {
- check.invalidArg(x.pos(), "%s must not be a receive-only channel", x)
- goto Error
- }
- x.mode = novalue
-
- case _Complex:
- if !check.complexArg(x) {
- goto Error
- }
-
- var y operand
- check.expr(&y, args[1], nil, iota)
- if y.mode == invalid {
- goto Error
- }
- if !check.complexArg(&y) {
- goto Error
- }
-
- check.convertUntyped(x, y.typ)
- if x.mode == invalid {
- goto Error
- }
- check.convertUntyped(&y, x.typ)
- if y.mode == invalid {
- goto Error
- }
-
- if !isIdentical(x.typ, y.typ) {
- check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
- goto Error
- }
-
- typ := underlying(x.typ).(*Basic)
- if x.mode == constant && y.mode == constant {
- x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, typ)
- } else {
- x.mode = value
- }
-
- switch typ.Kind {
- case Float32:
- x.typ = Typ[Complex64]
- case Float64:
- x.typ = Typ[Complex128]
- case UntypedInt, UntypedRune, UntypedFloat:
- x.typ = Typ[UntypedComplex]
- default:
- check.invalidArg(x.pos(), "float32 or float64 arguments expected")
- goto Error
- }
-
- case _Copy:
- var y operand
- check.expr(&y, args[1], nil, iota)
- if y.mode == invalid {
- goto Error
- }
-
- var dst, src Type
- if t, ok := typ0.(*Slice); ok {
- dst = t.Elt
- }
- switch t := underlying(y.typ).(type) {
- case *Basic:
- if isString(y.typ) {
- src = Typ[Byte]
- }
- case *Slice:
- src = t.Elt
- }
-
- if dst == nil || src == nil {
- check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y)
- goto Error
- }
-
- if !isIdentical(dst, src) {
- check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
- goto Error
- }
-
- x.mode = value
- x.typ = Typ[Int]
-
- case _Delete:
- m, ok := typ0.(*Map)
- if !ok {
- check.invalidArg(x.pos(), "%s is not a map", x)
- goto Error
- }
- check.expr(x, args[1], nil, iota)
- if x.mode == invalid {
- goto Error
- }
- if !x.isAssignable(m.Key) {
- check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key)
- goto Error
- }
- x.mode = novalue
-
- case _Imag, _Real:
- if !isComplex(typ0) {
- check.invalidArg(x.pos(), "%s must be a complex number", x)
- goto Error
- }
- if x.mode == constant {
- // nothing to do for x.val == 0
- if !isZeroConst(x.val) {
- c := x.val.(Complex)
- if id == _Real {
- x.val = c.Re
- } else {
- x.val = c.Im
- }
- }
- } else {
- x.mode = value
- }
- k := Invalid
- switch typ0.(*Basic).Kind {
- case Complex64:
- k = Float32
- case Complex128:
- k = Float64
- case UntypedComplex:
- k = UntypedFloat
- default:
- unreachable()
- }
- x.typ = Typ[k]
-
- case _Make:
- var min int // minimum number of arguments
- switch underlying(typ0).(type) {
- case *Slice:
- min = 2
- case *Map, *Chan:
- min = 1
- case *Table:
- min = 1
- default:
- check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0)
- goto Error
- }
- if n := len(args); n < min || min+1 < n {
- check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n)
- goto Error
- }
- var sizes []interface{} // constant integer arguments, if any
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, iota)
- if x.isInteger() {
- if x.mode == constant {
- if isNegConst(x.val) {
- check.invalidArg(x.pos(), "%s must not be negative", x)
- // safe to continue
- } else {
- sizes = append(sizes, x.val) // x.val >= 0
- }
- }
- } else {
- check.invalidArg(x.pos(), "%s must be an integer", x)
- // safe to continue
- }
- }
- if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) {
- check.invalidArg(args[1].Pos(), "length and capacity swapped")
- // safe to continue
- }
- x.mode = variable
- x.typ = typ0
-
- case _New:
- x.mode = variable
- x.typ = &Pointer{Base: typ0}
-
- case _Panic, _Print, _Println:
- for _, arg := range args[1:] {
- check.expr(x, arg, nil, -1)
- }
- x.mode = novalue
-
- case _Recover:
- x.mode = value
- x.typ = new(Interface)
-
- case _Alignof:
- x.mode = constant
- x.typ = Typ[Uintptr]
- // For now we return 1 always as it satisfies the spec's alignment guarantees.
- // TODO(gri) Extend typechecker API so that platform-specific values can be
- // provided.
- x.val = int64(1)
-
- case _Offsetof:
- if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok {
- check.invalidArg(x.pos(), "%s is not a selector", x)
- goto Error
- }
- x.mode = constant
- x.typ = Typ[Uintptr]
- // because of the size guarantees for basic types (> 0 for some),
- // returning 0 is only correct if two distinct non-zero size
- // structs can have the same address (the spec permits that)
- x.val = int64(0)
-
- case _Sizeof:
- x.mode = constant
- x.typ = Typ[Uintptr]
- x.val = sizeof(check.ctxt, typ0)
-
- case _Assert:
- // assert(pred) causes a typechecker error if pred is false.
- // The result of assert is the value of pred if there is no error.
- // Note: assert is only available in self-test mode.
- if x.mode != constant || !isBoolean(typ0) {
- check.invalidArg(x.pos(), "%s is not a boolean constant", x)
- goto Error
- }
- pred, ok := x.val.(bool)
- if !ok {
- check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x)
- goto Error
- }
- if !pred {
- check.errorf(call.Pos(), "%s failed", call)
- // compile-time assertion failure - safe to continue
- }
-
- case _Trace:
- // trace(x, y, z, ...) dumps the positions, expressions, and
- // values of its arguments. The result of trace is the value
- // of the first argument.
- // Note: trace is only available in self-test mode.
- if len(args) == 0 {
- check.dump("%s: trace() without arguments", call.Pos())
- x.mode = novalue
- x.expr = call
- return
- }
- var t operand
- x1 := x
- for _, arg := range args {
- check.rawExpr(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T))
- check.dump("%s: %s", x1.pos(), x1)
- x1 = &t // use incoming x only for first argument
- }
-
- default:
- check.invalidAST(call.Pos(), "unknown builtin id %d", id)
- goto Error
- }
-
- x.expr = call
- return
-
-Error:
- x.mode = invalid
- x.expr = call
-}
diff --git a/lang/types/errors.go b/lang/types/errors.go
index 7bc8c88..e293428 100644
--- a/lang/types/errors.go
+++ b/lang/types/errors.go
@@ -228,93 +228,107 @@ func writeSignature(buf *bytes.Buffer, sig *Signature) {
writeParams(buf, sig.Results, false)
}
-/*Simplex
func writeType(buf *bytes.Buffer, typ Type) {
- switch t := typ.(type) {
- case nil:
- buf.WriteString("")
-
- case *Basic:
- buf.WriteString(t.Name)
-
- case *Array:
- fmt.Fprintf(buf, "[%d]", t.Len)
- writeType(buf, t.Elt)
-
- case *Slice:
- buf.WriteString("[]")
- writeType(buf, t.Elt)
-
- case *Struct:
- buf.WriteString("struct{")
- for i, f := range t.Fields {
- if i > 0 {
- buf.WriteString("; ")
- }
- if !f.IsAnonymous {
- buf.WriteString(f.Name)
- buf.WriteByte(' ')
- }
- writeType(buf, f.Type)
- if f.Tag != "" {
- fmt.Fprintf(buf, " %q", f.Tag)
- }
- }
- buf.WriteByte('}')
-
- case *Pointer:
- buf.WriteByte('*')
- writeType(buf, t.Base)
-
- case *Result:
- writeParams(buf, t.Values, false)
-
- case *Signature:
- buf.WriteString("func")
- writeSignature(buf, t)
-
- case *builtin:
- fmt.Fprintf(buf, "", t.name)
-
- case *Interface:
- buf.WriteString("interface{")
- for i, m := range t.Methods {
- if i > 0 {
- buf.WriteString("; ")
- }
- buf.WriteString(m.Name)
- writeSignature(buf, m.Type)
- }
- buf.WriteByte('}')
-
- case *Map:
- buf.WriteString("map[")
- writeType(buf, t.Key)
- buf.WriteByte(']')
- writeType(buf, t.Elt)
-
- case *Chan:
- var s string
- switch t.Dir {
- case ast.SEND:
- s = "chan<- "
- case ast.RECV:
- s = "<-chan "
- default:
- s = "chan "
- }
- buf.WriteString(s)
- writeType(buf, t.Elt)
-
- case *NamedType:
- s := ""
- if t.Obj != nil {
- s = t.Obj.GetName()
- }
- buf.WriteString(s)
-
- default:
- fmt.Fprintf(buf, "", t)
- }
+ switch t := typ.(type) {
+ case nil:
+ buf.WriteString("")
+
+ case *Basic:
+ buf.WriteString(t.Name)
+
+ case *Array:
+ fmt.Fprintf(buf, "[%d]", t.Len)
+ writeType(buf, t.Elt)
+
+ case *Slice:
+ buf.WriteString("[]")
+ writeType(buf, t.Elt)
+
+ case *Struct:
+ buf.WriteString("struct{")
+ for i, f := range t.Fields {
+ if i > 0 {
+ buf.WriteString("; ")
+ }
+ if !f.IsAnonymous {
+ buf.WriteString(f.Name)
+ buf.WriteByte(' ')
+ }
+ writeType(buf, f.Type)
+ if f.Tag != "" {
+ fmt.Fprintf(buf, " %q", f.Tag)
+ }
+ }
+ buf.WriteByte('}')
+
+ case *Pointer:
+ buf.WriteByte('*')
+ writeType(buf, t.Base)
+
+ case *Result:
+ writeParams(buf, t.Values, false)
+
+ case *Signature:
+ buf.WriteString("func")
+ writeSignature(buf, t)
+
+ case *builtin:
+ fmt.Fprintf(buf, "", t.name)
+
+ case *Interface:
+ buf.WriteString("interface{")
+ for i, m := range t.Methods {
+ if i > 0 {
+ buf.WriteString("; ")
+ }
+ buf.WriteString(m.Name)
+ writeSignature(buf, m.Type)
+ }
+ buf.WriteByte('}')
+
+ case *Map:
+ buf.WriteString("map[")
+ writeType(buf, t.Key)
+ buf.WriteByte(']')
+ writeType(buf, t.Elt)
+
+ case *Chan:
+ var s string
+ switch t.Dir {
+ case ast.SEND:
+ s = "chan<- "
+ case ast.RECV:
+ s = "<-chan "
+ default:
+ s = "chan "
+ }
+ buf.WriteString(s)
+ writeType(buf, t.Elt)
+
+ case *NamedType:
+ s := ""
+ if t.Obj != nil {
+ s = t.Obj.GetName()
+ }
+ buf.WriteString(s)
+
+ //=== start custom
+ case *View:
+ buf.WriteString("view[")
+ if t.Key != nil {
+ writeType(buf, t.Key)
+ }
+ buf.WriteByte(']')
+ writeType(buf, t.Elt)
+
+ case *Table:
+ buf.WriteString("table[")
+ writeType(buf, t.Key)
+ buf.WriteByte(']')
+ writeType(buf, t.Elt)
+ //=== end custom
+
+ default:
+ fmt.Fprintf(buf, "", t)
+ }
}
-*/
diff --git a/lang/types/errors_sx.go b/lang/types/errors_sx.go
deleted file mode 100644
index 6ca6967..0000000
--- a/lang/types/errors_sx.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package types
-
-import (
- "bytes"
- "fmt"
- "simplex.sh/lang/ast"
-)
-
-// see errors.go:247
-func writeType(buf *bytes.Buffer, typ Type) {
- switch t := typ.(type) {
- case nil:
- buf.WriteString("")
-
- case *Basic:
- buf.WriteString(t.Name)
-
- case *Array:
- fmt.Fprintf(buf, "[%d]", t.Len)
- writeType(buf, t.Elt)
-
- case *Slice:
- buf.WriteString("[]")
- writeType(buf, t.Elt)
-
- case *Struct:
- buf.WriteString("struct{")
- for i, f := range t.Fields {
- if i > 0 {
- buf.WriteString("; ")
- }
- if !f.IsAnonymous {
- buf.WriteString(f.Name)
- buf.WriteByte(' ')
- }
- writeType(buf, f.Type)
- if f.Tag != "" {
- fmt.Fprintf(buf, " %q", f.Tag)
- }
- }
- buf.WriteByte('}')
-
- case *Pointer:
- buf.WriteByte('*')
- writeType(buf, t.Base)
-
- case *Result:
- writeParams(buf, t.Values, false)
-
- case *Signature:
- buf.WriteString("func")
- writeSignature(buf, t)
-
- case *builtin:
- fmt.Fprintf(buf, "", t.name)
-
- case *Interface:
- buf.WriteString("interface{")
- for i, m := range t.Methods {
- if i > 0 {
- buf.WriteString("; ")
- }
- buf.WriteString(m.Name)
- writeSignature(buf, m.Type)
- }
- buf.WriteByte('}')
-
- case *Map:
- buf.WriteString("map[")
- writeType(buf, t.Key)
- buf.WriteByte(']')
- writeType(buf, t.Elt)
-
- case *Chan:
- var s string
- switch t.Dir {
- case ast.SEND:
- s = "chan<- "
- case ast.RECV:
- s = "<-chan "
- default:
- s = "chan "
- }
- buf.WriteString(s)
- writeType(buf, t.Elt)
-
- case *NamedType:
- s := ""
- if t.Obj != nil {
- s = t.Obj.GetName()
- }
- buf.WriteString(s)
-
- //=== start custom
- case *View:
- buf.WriteString("view[")
- if t.Key != nil {
- writeType(buf, t.Key)
- }
- buf.WriteByte(']')
- writeType(buf, t.Elt)
-
- case *Table:
- buf.WriteString("table[")
- writeType(buf, t.Key)
- buf.WriteByte(']')
- writeType(buf, t.Elt)
- //=== end custom
-
- default:
- fmt.Fprintf(buf, "", t)
- }
-}
diff --git a/lang/types/expr.go b/lang/types/expr.go
index 4e8ef8b..52c37be 100644
--- a/lang/types/expr.go
+++ b/lang/types/expr.go
@@ -629,7 +629,6 @@ func (check *checker) callExpr(x *operand) {
check.ctxt.Expr(x.expr, typ, val)
}
-/*Simplex
// rawExpr typechecks expression e and initializes x with the expression
// value or type. If an error occurred, x.mode is set to invalid.
// A hint != nil is used as operand type for untyped shifted operands;
@@ -637,646 +636,669 @@ func (check *checker) callExpr(x *operand) {
// cycleOk indicates whether it is ok for a type expression to refer to itself.
//
func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) {
- if trace {
- c := ""
- if cycleOk {
- c = " ⨁"
- }
- check.trace(e.Pos(), "%s (%s, %d%s)", e, typeString(hint), iota, c)
- defer check.untrace("=> %s", x)
- }
-
- if check.ctxt.Expr != nil {
- defer check.callExpr(x)
- }
-
- switch e := e.(type) {
- case *ast.BadExpr:
- goto Error // error was reported before
-
- case *ast.Ident:
- if e.Name == "_" {
- check.invalidOp(e.Pos(), "cannot use _ as value or type")
- goto Error
- }
- obj := check.lookup(e)
- if obj == nil {
- goto Error // error was reported before
- }
- check.object(obj, cycleOk)
- switch obj := obj.(type) {
- case *Package:
- check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
- goto Error
- case *Const:
- if obj.Val == nil {
- goto Error // cycle detected
- }
- x.mode = constant
- if obj == universeIota {
- if iota < 0 {
- check.invalidAST(e.Pos(), "cannot use iota outside constant declaration")
- goto Error
- }
- x.val = int64(iota)
- } else {
- x.val = obj.Val
- }
- case *TypeName:
- x.mode = typexpr
- if !cycleOk && underlying(obj.Type) == nil {
- check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name)
- x.expr = e
- x.typ = Typ[Invalid]
- return // don't goto Error - need x.mode == typexpr
- }
- case *Var:
- x.mode = variable
- case *Func:
- x.mode = value
- default:
- unreachable()
- }
- x.typ = obj.GetType()
-
- case *ast.Ellipsis:
- // ellipses are handled explicitly where they are legal
- // (array composite literals and parameter lists)
- check.errorf(e.Pos(), "invalid use of '...'")
- goto Error
-
- case *ast.BasicLit:
- x.setConst(e.Kind, e.Value)
- if x.mode == invalid {
- check.invalidAST(e.Pos(), "invalid literal %v", e.Value)
- goto Error
- }
-
- case *ast.FuncLit:
- if sig, ok := check.typ(e.Type, false).(*Signature); ok {
- x.mode = value
- x.typ = sig
- check.later(nil, sig, e.Body)
- } else {
- check.invalidAST(e.Pos(), "invalid function literal %s", e)
- goto Error
- }
-
- case *ast.CompositeLit:
- typ := hint
- openArray := false
- if e.Type != nil {
- // [...]T array types may only appear with composite literals.
- // Check for them here so we don't have to handle ... in general.
- typ = nil
- if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil {
- if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil {
- // We have an "open" [...]T array type.
- // Create a new ArrayType with unknown length (-1)
- // and finish setting it up after analyzing the literal.
- typ = &Array{Len: -1, Elt: check.typ(atyp.Elt, cycleOk)}
- openArray = true
- }
- }
- if typ == nil {
- typ = check.typ(e.Type, false)
- }
- }
- if typ == nil {
- check.errorf(e.Pos(), "missing type in composite literal")
- goto Error
- }
-
- switch utyp := underlying(deref(typ)).(type) {
- case *Struct:
- if len(e.Elts) == 0 {
- break
- }
- fields := utyp.Fields
- if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok {
- // all elements must have keys
- visited := make([]bool, len(fields))
- for _, e := range e.Elts {
- kv, _ := e.(*ast.KeyValueExpr)
- if kv == nil {
- check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal")
- continue
- }
- key, _ := kv.Key.(*ast.Ident)
- if key == nil {
- check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key)
- continue
- }
- i := utyp.fieldIndex(key.Name)
- if i < 0 {
- check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name)
- continue
- }
- // 0 <= i < len(fields)
- if visited[i] {
- check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)
- continue
- }
- visited[i] = true
- check.expr(x, kv.Value, nil, iota)
- etyp := fields[i].Type
- if !x.isAssignable(etyp) {
- check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
- continue
- }
- }
- } else {
- // no element must have a key
- for i, e := range e.Elts {
- if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
- check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal")
- continue
- }
- check.expr(x, e, nil, iota)
- if i >= len(fields) {
- check.errorf(x.pos(), "too many values in struct literal")
- break // cannot continue
- }
- // i < len(fields)
- etyp := fields[i].Type
- if !x.isAssignable(etyp) {
- check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
- continue
- }
- }
- if len(e.Elts) < len(fields) {
- check.errorf(e.Rbrace, "too few values in struct literal")
- // ok to continue
- }
- }
-
- case *Array:
- n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota)
- // if we have an "open" [...]T array, set the length now that we know it
- if openArray {
- utyp.Len = n
- }
-
- case *Slice:
- check.indexedElts(e.Elts, utyp.Elt, -1, iota)
-
- case *Map:
- visited := make(map[interface{}]bool, len(e.Elts))
- for _, e := range e.Elts {
- kv, _ := e.(*ast.KeyValueExpr)
- if kv == nil {
- check.errorf(e.Pos(), "missing key in map literal")
- continue
- }
- check.compositeLitKey(kv.Key)
- check.expr(x, kv.Key, nil, iota)
- if !x.isAssignable(utyp.Key) {
- check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.Key)
- continue
- }
- if x.mode == constant {
- if visited[x.val] {
- check.errorf(x.pos(), "duplicate key %s in map literal", x.val)
- continue
- }
- visited[x.val] = true
- }
- check.expr(x, kv.Value, utyp.Elt, iota)
- if !x.isAssignable(utyp.Elt) {
- check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.Elt)
- continue
- }
- }
-
- default:
- check.errorf(e.Pos(), "%s is not a valid composite literal type", typ)
- goto Error
- }
-
- x.mode = value
- x.typ = typ
-
- case *ast.ParenExpr:
- check.rawExpr(x, e.X, hint, iota, cycleOk)
-
- case *ast.SelectorExpr:
- sel := e.Sel.Name
- // If the identifier refers to a package, handle everything here
- // so we don't need a "package" mode for operands: package names
- // can only appear in qualified identifiers which are mapped to
- // selector expressions.
- if ident, ok := e.X.(*ast.Ident); ok {
- if pkg, ok := check.lookup(ident).(*Package); ok {
- exp := pkg.Scope.Lookup(sel)
- if exp == nil {
- check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
- goto Error
- }
- check.register(e.Sel, exp)
- // Simplified version of the code for *ast.Idents:
- // - imported packages use types.Scope and types.Objects
- // - imported objects are always fully initialized
- switch exp := exp.(type) {
- case *Const:
- assert(exp.Val != nil)
- x.mode = constant
- x.typ = exp.Type
- x.val = exp.Val
- case *TypeName:
- x.mode = typexpr
- x.typ = exp.Type
- case *Var:
- x.mode = variable
- x.typ = exp.Type
- case *Func:
- x.mode = value
- x.typ = exp.Type
- default:
- unreachable()
- }
- x.expr = e
- return
- }
- }
-
- check.exprOrType(x, e.X, nil, iota, false)
- if x.mode == invalid {
- goto Error
- }
- mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
- if mode == invalid {
- check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
- goto Error
- }
- if x.mode == typexpr {
- // method expression
- sig, ok := typ.(*Signature)
- if !ok {
- check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
- goto Error
- }
- // the receiver type becomes the type of the first function
- // argument of the method expression's function type
- // TODO(gri) at the moment, method sets don't correctly track
- // pointer vs non-pointer receivers => typechecker is too lenient
- x.mode = value
- x.typ = &Signature{
- Params: append([]*Var{{Type: x.typ}}, sig.Params...),
- Results: sig.Results,
- IsVariadic: sig.IsVariadic,
- }
- } else {
- // regular selector
- x.mode = mode
- x.typ = typ
- }
-
- case *ast.IndexExpr:
- check.expr(x, e.X, hint, iota)
-
- valid := false
- length := int64(-1) // valid if >= 0
- switch typ := underlying(x.typ).(type) {
- case *Basic:
- if isString(typ) {
- valid = true
- if x.mode == constant {
- length = int64(len(x.val.(string)))
- }
- // an indexed string always yields a byte value
- // (not a constant) even if the string and the
- // index are constant
- x.mode = value
- x.typ = Typ[Byte]
- }
-
- case *Array:
- valid = true
- length = typ.Len
- if x.mode != variable {
- x.mode = value
- }
- x.typ = typ.Elt
-
- case *Pointer:
- if typ, _ := underlying(typ.Base).(*Array); typ != nil {
- valid = true
- length = typ.Len
- x.mode = variable
- x.typ = typ.Elt
- }
-
- case *Slice:
- valid = true
- x.mode = variable
- x.typ = typ.Elt
-
- case *Map:
- var key operand
- check.expr(&key, e.Index, nil, iota)
- if key.mode == invalid || !key.isAssignable(typ.Key) {
- check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key)
- goto Error
- }
- x.mode = valueok
- x.typ = typ.Elt
- x.expr = e
- return
- }
-
- if !valid {
- check.invalidOp(x.pos(), "cannot index %s", x)
- goto Error
- }
-
- if e.Index == nil {
- check.invalidAST(e.Pos(), "missing index expression for %s", x)
- return
- }
-
- check.index(e.Index, length, iota)
- // ok to continue
-
- case *ast.SliceExpr:
- check.expr(x, e.X, hint, iota)
-
- valid := false
- length := int64(-1) // valid if >= 0
- switch typ := underlying(x.typ).(type) {
- case *Basic:
- if isString(typ) {
- valid = true
- if x.mode == constant {
- length = int64(len(x.val.(string))) + 1 // +1 for slice
- }
- // a sliced string always yields a string value
- // of the same type as the original string (not
- // a constant) even if the string and the indices
- // are constant
- x.mode = value
- // x.typ doesn't change
- }
-
- case *Array:
- valid = true
- length = typ.Len + 1 // +1 for slice
- if x.mode != variable {
- check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x)
- goto Error
- }
- x.typ = &Slice{Elt: typ.Elt}
-
- case *Pointer:
- if typ, _ := underlying(typ.Base).(*Array); typ != nil {
- valid = true
- length = typ.Len + 1 // +1 for slice
- x.mode = variable
- x.typ = &Slice{Elt: typ.Elt}
- }
-
- case *Slice:
- valid = true
- x.mode = variable
- // x.typ doesn't change
- }
-
- if !valid {
- check.invalidOp(x.pos(), "cannot slice %s", x)
- goto Error
- }
-
- lo := int64(0)
- if e.Low != nil {
- lo = check.index(e.Low, length, iota)
- }
-
- hi := int64(-1)
- if e.High != nil {
- hi = check.index(e.High, length, iota)
- } else if length >= 0 {
- hi = length
- }
-
- if lo >= 0 && hi >= 0 && lo > hi {
- check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi)
- // ok to continue
- }
-
- case *ast.TypeAssertExpr:
- check.expr(x, e.X, hint, iota)
- if x.mode == invalid {
- goto Error
- }
- var T *Interface
- if T, _ = underlying(x.typ).(*Interface); T == nil {
- check.invalidOp(x.pos(), "%s is not an interface", x)
- goto Error
- }
- // x.(type) expressions are handled explicitly in type switches
- if e.Type == nil {
- check.errorf(e.Pos(), "use of .(type) outside type switch")
- goto Error
- }
- typ := check.typ(e.Type, false)
- if typ == Typ[Invalid] {
- goto Error
- }
- if method, wrongType := missingMethod(typ, T); method != nil {
- var msg string
- if wrongType {
- msg = "%s cannot have dynamic type %s (wrong type for method %s)"
- } else {
- msg = "%s cannot have dynamic type %s (missing method %s)"
- }
- check.errorf(e.Type.Pos(), msg, x, typ, method.Name)
- // ok to continue
- }
- x.mode = valueok
- x.expr = e
- x.typ = typ
-
- case *ast.CallExpr:
- check.exprOrType(x, e.Fun, nil, iota, false)
- if x.mode == invalid {
- goto Error
- } else if x.mode == typexpr {
- check.conversion(x, e, x.typ, iota)
- } else if sig, ok := underlying(x.typ).(*Signature); ok {
- // check parameters
-
- // If we have a trailing ... at the end of the parameter
- // list, the last argument must match the parameter type
- // []T of a variadic function parameter x ...T.
- passSlice := false
- if e.Ellipsis.IsValid() {
- if sig.IsVariadic {
- passSlice = true
- } else {
- check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun)
- // ok to continue
- }
- }
-
- // If we have a single argument that is a function call
- // we need to handle it separately. Determine if this
- // is the case without checking the argument.
- var call *ast.CallExpr
- if len(e.Args) == 1 {
- call, _ = unparen(e.Args[0]).(*ast.CallExpr)
- }
-
- n := 0 // parameter count
- if call != nil {
- // We have a single argument that is a function call.
- check.expr(x, call, nil, -1)
- if x.mode == invalid {
- goto Error // TODO(gri): we can do better
- }
- if t, _ := x.typ.(*Result); t != nil {
- // multiple result values
- n = len(t.Values)
- for i, obj := range t.Values {
- x.mode = value
- x.expr = nil // TODO(gri) can we do better here? (for good error messages)
- x.typ = obj.Type
- check.argument(sig, i, nil, x, passSlice && i+1 == n)
- }
- } else {
- // single result value
- n = 1
- check.argument(sig, 0, nil, x, passSlice)
- }
-
- } else {
- // We don't have a single argument or it is not a function call.
- n = len(e.Args)
- for i, arg := range e.Args {
- check.argument(sig, i, arg, x, passSlice && i+1 == n)
- }
- }
-
- // determine if we have enough arguments
- if sig.IsVariadic {
- // a variadic function accepts an "empty"
- // last argument: count one extra
- n++
- }
- if n < len(sig.Params) {
- check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
- // ok to continue
- }
-
- // determine result
- switch len(sig.Results) {
- case 0:
- x.mode = novalue
- case 1:
- x.mode = value
- x.typ = sig.Results[0].Type
- default:
- x.mode = value
- x.typ = &Result{Values: sig.Results}
- }
-
- } else if bin, ok := x.typ.(*builtin); ok {
- check.builtin(x, e, bin, iota)
-
- } else {
- check.invalidOp(x.pos(), "cannot call non-function %s", x)
- goto Error
- }
-
- case *ast.StarExpr:
- check.exprOrType(x, e.X, hint, iota, true)
- switch x.mode {
- case invalid:
- goto Error
- case typexpr:
- x.typ = &Pointer{Base: x.typ}
- default:
- if typ, ok := x.typ.(*Pointer); ok {
- x.mode = variable
- x.typ = typ.Base
- } else {
- check.invalidOp(x.pos(), "cannot indirect %s", x)
- goto Error
- }
- }
-
- case *ast.UnaryExpr:
- check.expr(x, e.X, hint, iota)
- check.unary(x, e.Op)
-
- case *ast.BinaryExpr:
- var y operand
- check.expr(x, e.X, hint, iota)
- check.expr(&y, e.Y, hint, iota)
- check.binary(x, &y, e.Op, hint)
-
- case *ast.KeyValueExpr:
- // key:value expressions are handled in composite literals
- check.invalidAST(e.Pos(), "no key:value expected")
- goto Error
-
- case *ast.ArrayType:
- if e.Len != nil {
- check.expr(x, e.Len, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- if x.mode != constant {
- if x.mode != invalid {
- check.errorf(x.pos(), "array length %s must be constant", x)
- }
- goto Error
- }
- n, ok := x.val.(int64)
- if !ok || n < 0 {
- check.errorf(x.pos(), "invalid array length %s", x)
- goto Error
- }
- x.typ = &Array{Len: n, Elt: check.typ(e.Elt, cycleOk)}
- } else {
- x.typ = &Slice{Elt: check.typ(e.Elt, true)}
- }
- x.mode = typexpr
-
- case *ast.StructType:
- x.mode = typexpr
- x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)}
-
- case *ast.FuncType:
- params, isVariadic := check.collectParams(e.Params, true)
- results, _ := check.collectParams(e.Results, false)
- x.mode = typexpr
- x.typ = &Signature{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic}
-
- case *ast.InterfaceType:
- x.mode = typexpr
- x.typ = &Interface{Methods: check.collectMethods(e.Methods)}
-
- case *ast.MapType:
- x.mode = typexpr
- x.typ = &Map{Key: check.typ(e.Key, true), Elt: check.typ(e.Value, true)}
-
- case *ast.ChanType:
- x.mode = typexpr
- x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)}
-
- default:
- check.dump("e = %s", e)
- unreachable()
- }
-
- // everything went well
- x.expr = e
- return
+ if trace {
+ c := ""
+ if cycleOk {
+ c = " ⨁"
+ }
+ check.trace(e.Pos(), "%s (%s, %d%s)", e, typeString(hint), iota, c)
+ defer check.untrace("=> %s", x)
+ }
+
+ if check.ctxt.Expr != nil {
+ defer check.callExpr(x)
+ }
+
+ switch e := e.(type) {
+ case *ast.BadExpr:
+ goto Error // error was reported before
+
+ case *ast.Ident:
+ if e.Name == "_" {
+ check.invalidOp(e.Pos(), "cannot use _ as value or type")
+ goto Error
+ }
+ obj := check.lookup(e)
+ if obj == nil {
+ goto Error // error was reported before
+ }
+ check.object(obj, cycleOk)
+ switch obj := obj.(type) {
+ case *Package:
+ check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
+ goto Error
+ case *Const:
+ if obj.Val == nil {
+ goto Error // cycle detected
+ }
+ x.mode = constant
+ if obj == universeIota {
+ if iota < 0 {
+ check.invalidAST(e.Pos(), "cannot use iota outside constant declaration")
+ goto Error
+ }
+ x.val = int64(iota)
+ } else {
+ x.val = obj.Val
+ }
+ case *TypeName:
+ x.mode = typexpr
+ if !cycleOk && underlying(obj.Type) == nil {
+ check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name)
+ x.expr = e
+ x.typ = Typ[Invalid]
+ return // don't goto Error - need x.mode == typexpr
+ }
+ case *Var:
+ x.mode = variable
+ case *Func:
+ x.mode = value
+ default:
+ unreachable()
+ }
+ x.typ = obj.GetType()
+
+ case *ast.Ellipsis:
+ // ellipses are handled explicitly where they are legal
+ // (array composite literals and parameter lists)
+ check.errorf(e.Pos(), "invalid use of '...'")
+ goto Error
+
+ case *ast.BasicLit:
+ x.setConst(e.Kind, e.Value)
+ if x.mode == invalid {
+ check.invalidAST(e.Pos(), "invalid literal %v", e.Value)
+ goto Error
+ }
+
+ case *ast.FuncLit:
+ if sig, ok := check.typ(e.Type, false).(*Signature); ok {
+ x.mode = value
+ x.typ = sig
+ check.later(nil, sig, e.Body)
+ } else {
+ check.invalidAST(e.Pos(), "invalid function literal %s", e)
+ goto Error
+ }
+
+ case *ast.CompositeLit:
+ typ := hint
+ openArray := false
+ if e.Type != nil {
+ // [...]T array types may only appear with composite literals.
+ // Check for them here so we don't have to handle ... in general.
+ typ = nil
+ if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil {
+ if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil {
+ // We have an "open" [...]T array type.
+ // Create a new ArrayType with unknown length (-1)
+ // and finish setting it up after analyzing the literal.
+ typ = &Array{Len: -1, Elt: check.typ(atyp.Elt, cycleOk)}
+ openArray = true
+ }
+ }
+ if typ == nil {
+ typ = check.typ(e.Type, false)
+ }
+ }
+ if typ == nil {
+ check.errorf(e.Pos(), "missing type in composite literal")
+ goto Error
+ }
+
+ switch utyp := underlying(deref(typ)).(type) {
+ case *Struct:
+ if len(e.Elts) == 0 {
+ break
+ }
+ fields := utyp.Fields
+ if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok {
+ // all elements must have keys
+ visited := make([]bool, len(fields))
+ for _, e := range e.Elts {
+ kv, _ := e.(*ast.KeyValueExpr)
+ if kv == nil {
+ check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal")
+ continue
+ }
+ key, _ := kv.Key.(*ast.Ident)
+ if key == nil {
+ check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key)
+ continue
+ }
+ i := utyp.fieldIndex(key.Name)
+ if i < 0 {
+ check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name)
+ continue
+ }
+ // 0 <= i < len(fields)
+ if visited[i] {
+ check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)
+ continue
+ }
+ visited[i] = true
+ check.expr(x, kv.Value, nil, iota)
+ etyp := fields[i].Type
+ if !x.isAssignable(etyp) {
+ check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
+ continue
+ }
+ }
+ } else {
+ // no element must have a key
+ for i, e := range e.Elts {
+ if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
+ check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal")
+ continue
+ }
+ check.expr(x, e, nil, iota)
+ if i >= len(fields) {
+ check.errorf(x.pos(), "too many values in struct literal")
+ break // cannot continue
+ }
+ // i < len(fields)
+ etyp := fields[i].Type
+ if !x.isAssignable(etyp) {
+ check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
+ continue
+ }
+ }
+ if len(e.Elts) < len(fields) {
+ check.errorf(e.Rbrace, "too few values in struct literal")
+ // ok to continue
+ }
+ }
+
+ case *Array:
+ n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota)
+ // if we have an "open" [...]T array, set the length now that we know it
+ if openArray {
+ utyp.Len = n
+ }
+
+ case *Slice:
+ check.indexedElts(e.Elts, utyp.Elt, -1, iota)
+
+ case *Map:
+ visited := make(map[interface{}]bool, len(e.Elts))
+ for _, e := range e.Elts {
+ kv, _ := e.(*ast.KeyValueExpr)
+ if kv == nil {
+ check.errorf(e.Pos(), "missing key in map literal")
+ continue
+ }
+ check.compositeLitKey(kv.Key)
+ check.expr(x, kv.Key, nil, iota)
+ if !x.isAssignable(utyp.Key) {
+ check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.Key)
+ continue
+ }
+ if x.mode == constant {
+ if visited[x.val] {
+ check.errorf(x.pos(), "duplicate key %s in map literal", x.val)
+ continue
+ }
+ visited[x.val] = true
+ }
+ check.expr(x, kv.Value, utyp.Elt, iota)
+ if !x.isAssignable(utyp.Elt) {
+ check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.Elt)
+ continue
+ }
+ }
+
+ default:
+ check.errorf(e.Pos(), "%s is not a valid composite literal type", typ)
+ goto Error
+ }
+
+ x.mode = value
+ x.typ = typ
+
+ case *ast.ParenExpr:
+ check.rawExpr(x, e.X, hint, iota, cycleOk)
+
+ case *ast.SelectorExpr:
+ sel := e.Sel.Name
+ // If the identifier refers to a package, handle everything here
+ // so we don't need a "package" mode for operands: package names
+ // can only appear in qualified identifiers which are mapped to
+ // selector expressions.
+ if ident, ok := e.X.(*ast.Ident); ok {
+ if pkg, ok := check.lookup(ident).(*Package); ok {
+ exp := pkg.Scope.Lookup(sel)
+ if exp == nil {
+ check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
+ goto Error
+ }
+ check.register(e.Sel, exp)
+ // Simplified version of the code for *ast.Idents:
+ // - imported packages use types.Scope and types.Objects
+ // - imported objects are always fully initialized
+ switch exp := exp.(type) {
+ case *Const:
+ assert(exp.Val != nil)
+ x.mode = constant
+ x.typ = exp.Type
+ x.val = exp.Val
+ case *TypeName:
+ x.mode = typexpr
+ x.typ = exp.Type
+ case *Var:
+ x.mode = variable
+ x.typ = exp.Type
+ case *Func:
+ x.mode = value
+ x.typ = exp.Type
+ default:
+ unreachable()
+ }
+ x.expr = e
+ return
+ }
+ }
+
+ check.exprOrType(x, e.X, nil, iota, false)
+ if x.mode == invalid {
+ goto Error
+ }
+ mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
+ if mode == invalid {
+ check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
+ goto Error
+ }
+ if x.mode == typexpr {
+ // method expression
+ sig, ok := typ.(*Signature)
+ if !ok {
+ check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
+ goto Error
+ }
+ // the receiver type becomes the type of the first function
+ // argument of the method expression's function type
+ // TODO(gri) at the moment, method sets don't correctly track
+ // pointer vs non-pointer receivers => typechecker is too lenient
+ x.mode = value
+ x.typ = &Signature{
+ Params: append([]*Var{{Type: x.typ}}, sig.Params...),
+ Results: sig.Results,
+ IsVariadic: sig.IsVariadic,
+ }
+ } else {
+ // regular selector
+ x.mode = mode
+ x.typ = typ
+ }
+
+ case *ast.IndexExpr:
+ check.expr(x, e.X, hint, iota)
+
+ valid := false
+ length := int64(-1) // valid if >= 0
+ switch typ := underlying(x.typ).(type) {
+ case *Basic:
+ if isString(typ) {
+ valid = true
+ if x.mode == constant {
+ length = int64(len(x.val.(string)))
+ }
+ // an indexed string always yields a byte value
+ // (not a constant) even if the string and the
+ // index are constant
+ x.mode = value
+ x.typ = Typ[Byte]
+ }
+
+ case *Array:
+ valid = true
+ length = typ.Len
+ if x.mode != variable {
+ x.mode = value
+ }
+ x.typ = typ.Elt
+
+ case *Pointer:
+ if typ, _ := underlying(typ.Base).(*Array); typ != nil {
+ valid = true
+ length = typ.Len
+ x.mode = variable
+ x.typ = typ.Elt
+ }
+
+ case *Slice:
+ valid = true
+ x.mode = variable
+ x.typ = typ.Elt
+
+ case *Map:
+ var key operand
+ check.expr(&key, e.Index, nil, iota)
+ if key.mode == invalid || !key.isAssignable(typ.Key) {
+ check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key)
+ goto Error
+ }
+ x.mode = valueok
+ x.typ = typ.Elt
+ x.expr = e
+ return
+ }
+
+ if !valid {
+ check.invalidOp(x.pos(), "cannot index %s", x)
+ goto Error
+ }
+
+ if e.Index == nil {
+ check.invalidAST(e.Pos(), "missing index expression for %s", x)
+ return
+ }
+
+ check.index(e.Index, length, iota)
+ // ok to continue
+
+ case *ast.SliceExpr:
+ check.expr(x, e.X, hint, iota)
+
+ valid := false
+ length := int64(-1) // valid if >= 0
+ switch typ := underlying(x.typ).(type) {
+ case *Basic:
+ if isString(typ) {
+ valid = true
+ if x.mode == constant {
+ length = int64(len(x.val.(string))) + 1 // +1 for slice
+ }
+ // a sliced string always yields a string value
+ // of the same type as the original string (not
+ // a constant) even if the string and the indices
+ // are constant
+ x.mode = value
+ // x.typ doesn't change
+ }
+
+ case *Array:
+ valid = true
+ length = typ.Len + 1 // +1 for slice
+ if x.mode != variable {
+ check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x)
+ goto Error
+ }
+ x.typ = &Slice{Elt: typ.Elt}
+
+ case *Pointer:
+ if typ, _ := underlying(typ.Base).(*Array); typ != nil {
+ valid = true
+ length = typ.Len + 1 // +1 for slice
+ x.mode = variable
+ x.typ = &Slice{Elt: typ.Elt}
+ }
+
+ case *Slice:
+ valid = true
+ x.mode = variable
+ // x.typ doesn't change
+ }
+
+ if !valid {
+ check.invalidOp(x.pos(), "cannot slice %s", x)
+ goto Error
+ }
+
+ lo := int64(0)
+ if e.Low != nil {
+ lo = check.index(e.Low, length, iota)
+ }
+
+ hi := int64(-1)
+ if e.High != nil {
+ hi = check.index(e.High, length, iota)
+ } else if length >= 0 {
+ hi = length
+ }
+
+ if lo >= 0 && hi >= 0 && lo > hi {
+ check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi)
+ // ok to continue
+ }
+
+ case *ast.TypeAssertExpr:
+ check.expr(x, e.X, hint, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ var T *Interface
+ if T, _ = underlying(x.typ).(*Interface); T == nil {
+ check.invalidOp(x.pos(), "%s is not an interface", x)
+ goto Error
+ }
+ // x.(type) expressions are handled explicitly in type switches
+ if e.Type == nil {
+ check.errorf(e.Pos(), "use of .(type) outside type switch")
+ goto Error
+ }
+ typ := check.typ(e.Type, false)
+ if typ == Typ[Invalid] {
+ goto Error
+ }
+ if method, wrongType := missingMethod(typ, T); method != nil {
+ var msg string
+ if wrongType {
+ msg = "%s cannot have dynamic type %s (wrong type for method %s)"
+ } else {
+ msg = "%s cannot have dynamic type %s (missing method %s)"
+ }
+ check.errorf(e.Type.Pos(), msg, x, typ, method.Name)
+ // ok to continue
+ }
+ x.mode = valueok
+ x.expr = e
+ x.typ = typ
+
+ case *ast.CallExpr:
+ check.exprOrType(x, e.Fun, nil, iota, false)
+ if x.mode == invalid {
+ goto Error
+ } else if x.mode == typexpr {
+ check.conversion(x, e, x.typ, iota)
+
+ } else if sig, ok := underlying(x.typ).(*Signature); ok {
+ if sig.Recv != nil {
+ if _, ok := sig.Recv.Type.(Viewish); ok {
+ check.sx_step(x, e, sig, hint, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ }
+ }
+
+ // check parameters
+
+ // If we have a trailing ... at the end of the parameter
+ // list, the last argument must match the parameter type
+ // []T of a variadic function parameter x ...T.
+ passSlice := false
+ if e.Ellipsis.IsValid() {
+ if sig.IsVariadic {
+ passSlice = true
+ } else {
+ check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun)
+ // ok to continue
+ }
+ }
+
+ // If we have a single argument that is a function call
+ // we need to handle it separately. Determine if this
+ // is the case without checking the argument.
+ var call *ast.CallExpr
+ if len(e.Args) == 1 {
+ call, _ = unparen(e.Args[0]).(*ast.CallExpr)
+ }
+
+ n := 0 // parameter count
+ if call != nil {
+ // We have a single argument that is a function call.
+ check.expr(x, call, nil, -1)
+ if x.mode == invalid {
+ goto Error // TODO(gri): we can do better
+ }
+ if t, _ := x.typ.(*Result); t != nil {
+ // multiple result values
+ n = len(t.Values)
+ for i, obj := range t.Values {
+ x.mode = value
+ x.expr = nil // TODO(gri) can we do better here? (for good error messages)
+ x.typ = obj.Type
+ check.argument(sig, i, nil, x, passSlice && i+1 == n)
+ }
+ } else {
+ // single result value
+ n = 1
+ check.argument(sig, 0, nil, x, passSlice)
+ }
+
+ } else {
+ // We don't have a single argument or it is not a function call.
+ n = len(e.Args)
+ for i, arg := range e.Args {
+ check.argument(sig, i, arg, x, passSlice && i+1 == n)
+ }
+ }
+
+ // determine if we have enough arguments
+ if sig.IsVariadic {
+ // a variadic function accepts an "empty"
+ // last argument: count one extra
+ n++
+ }
+ if n < len(sig.Params) {
+ check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
+ // ok to continue
+ }
+
+ // determine result
+ switch len(sig.Results) {
+ case 0:
+ x.mode = novalue
+ case 1:
+ x.mode = value
+ x.typ = sig.Results[0].Type
+ default:
+ x.mode = value
+ x.typ = &Result{Values: sig.Results}
+ }
+
+ } else if bin, ok := x.typ.(*builtin); ok {
+ check.builtin(x, e, bin, iota)
+
+ } else {
+ check.invalidOp(x.pos(), "cannot call non-function %s", x)
+ goto Error
+ }
+
+ case *ast.StarExpr:
+ check.exprOrType(x, e.X, hint, iota, true)
+ switch x.mode {
+ case invalid:
+ goto Error
+ case typexpr:
+ x.typ = &Pointer{Base: x.typ}
+ default:
+ if typ, ok := x.typ.(*Pointer); ok {
+ x.mode = variable
+ x.typ = typ.Base
+ } else {
+ check.invalidOp(x.pos(), "cannot indirect %s", x)
+ goto Error
+ }
+ }
+
+ case *ast.UnaryExpr:
+ check.expr(x, e.X, hint, iota)
+ check.unary(x, e.Op)
+
+ case *ast.BinaryExpr:
+ var y operand
+ check.expr(x, e.X, hint, iota)
+ check.expr(&y, e.Y, hint, iota)
+ check.binary(x, &y, e.Op, hint)
+
+ case *ast.KeyValueExpr:
+ // key:value expressions are handled in composite literals
+ check.invalidAST(e.Pos(), "no key:value expected")
+ goto Error
+
+ case *ast.ArrayType:
+ if e.Len != nil {
+ check.expr(x, e.Len, nil, iota)
+ if x.mode == invalid {
+ goto Error
+ }
+ if x.mode != constant {
+ if x.mode != invalid {
+ check.errorf(x.pos(), "array length %s must be constant", x)
+ }
+ goto Error
+ }
+ n, ok := x.val.(int64)
+ if !ok || n < 0 {
+ check.errorf(x.pos(), "invalid array length %s", x)
+ goto Error
+ }
+ x.typ = &Array{Len: n, Elt: check.typ(e.Elt, cycleOk)}
+ } else {
+ x.typ = &Slice{Elt: check.typ(e.Elt, true)}
+ }
+ x.mode = typexpr
+
+ case *ast.StructType:
+ x.mode = typexpr
+ x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)}
+
+ case *ast.FuncType:
+ params, isVariadic := check.collectParams(e.Params, true)
+ results, _ := check.collectParams(e.Results, false)
+ x.mode = typexpr
+ x.typ = &Signature{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic}
+
+ case *ast.InterfaceType:
+ x.mode = typexpr
+ x.typ = &Interface{Methods: check.collectMethods(e.Methods)}
+
+ case *ast.MapType:
+ x.mode = typexpr
+ x.typ = &Map{Key: check.typ(e.Key, true), Elt: check.typ(e.Value, true)}
+
+ case *ast.ChanType:
+ x.mode = typexpr
+ x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)}
+
+ //=== start custom
+ case *ast.ViewType:
+ x.mode = typexpr
+ if e.Key != nil {
+ x.typ = &View{Key: check.typ(e.Key, false), Elt: check.typ(e.Value, false)}
+ } else {
+ x.typ = &View{Elt: check.typ(e.Value, false)}
+ }
+
+ case *ast.TableType:
+ x.mode = typexpr
+ x.typ = &Table{Key: check.typ(e.Key, false), Elt: check.typ(e.Value, false)}
+ //=== end custom
+
+ default:
+ check.dump("e = %s", e)
+ unreachable()
+ }
+
+ // everything went well
+ x.expr = e
+ return
Error:
- x.mode = invalid
- x.expr = e
+ x.mode = invalid
+ x.expr = e
}
-*/
// exprOrType is like rawExpr but reports an error if e doesn't represents a value or type.
func (check *checker) exprOrType(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) {
@@ -1334,3 +1356,310 @@ func (check *checker) typOrNil(e ast.Expr, cycleOk bool) Type {
func (check *checker) typ(e ast.Expr, cycleOk bool) Type {
return check.rawTyp(e, cycleOk, false)
}
+
+// ==================================================
+// == Simplex methods
+
+func (check *checker) sx_step(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) {
+ sel, ok := e.Fun.(*ast.SelectorExpr)
+ if !ok {
+ x.mode = invalid
+ return
+ }
+ builtin := sel.Sel.Name
+
+ switch builtin {
+
+ case "select", "reject":
+ check.sx_predicate_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Key: v.KeyType(), Elt: v.EltType()}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "detect":
+ check.sx_predicate_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ r := sig.Recv.Type.(Viewish).EltType()
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "collect":
+ res_typ := check.sx_map_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Key: v.KeyType(), Elt: res_typ}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "inject":
+ res_typ := check.sx_inject_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Key: v.KeyType(), Elt: res_typ}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "group":
+ res_typ := check.sx_map_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Key: res_typ, Elt: &View{Key: v.KeyType(), Elt: v.EltType()}}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "index":
+ res_typ := check.sx_map_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Key: res_typ, Elt: v.EltType()}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ case "sort":
+ check.sx_map_function(x, e, sig, hint, iota)
+ if x.mode != invalid {
+ v := sig.Recv.Type.(Viewish)
+ r := &View{Elt: v.EltType()}
+ sig.Results = []*Var{{Type: r}}
+ }
+
+ default:
+ unreachable()
+
+ }
+}
+
+func (check *checker) sx_predicate_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) {
+ sel, ok := e.Fun.(*ast.SelectorExpr)
+ if !ok {
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return
+ }
+
+ var view_typ Viewish
+ switch v := sig.Recv.Type.(type) {
+ case *View:
+ view_typ = v
+ case *Table:
+ view_typ = v
+ default:
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return
+ }
+
+ var f_op operand
+ var f ast.Expr
+
+ switch sel.Sel.Name {
+ case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
+ if len(e.Args) != 1 {
+ check.errorf(e.Pos(), "%s expects one argument", e)
+ x.mode = invalid
+ return
+ }
+
+ f = e.Args[0]
+ check.expr(&f_op, f, nil, iota)
+
+ default:
+ unreachable()
+ }
+
+ if f_sig, ok := f_op.typ.(*Signature); ok {
+ if len(f_sig.Results) != 1 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return
+ }
+ if !isBoolean(f_sig.Results[0].Type) {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return
+ }
+ if len(f_sig.Params) != 1 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return
+ }
+ if !isIdentical(f_sig.Params[0].Type, view_typ.EltType()) {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return
+ }
+
+ sig.Params = []*Var{{Type: f_sig}}
+ } else {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return
+ }
+}
+
+func (check *checker) sx_map_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) Type {
+ var res_typ Type
+
+ sel, ok := e.Fun.(*ast.SelectorExpr)
+ if !ok {
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return nil
+ }
+
+ var view_typ Viewish
+ switch v := sig.Recv.Type.(type) {
+ case *View:
+ view_typ = v
+ case *Table:
+ view_typ = v
+ default:
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return nil
+ }
+
+ var f_op operand
+ var f ast.Expr
+
+ switch sel.Sel.Name {
+ case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
+ if len(e.Args) != 1 {
+ check.errorf(e.Pos(), "%s expects one argument", e)
+ x.mode = invalid
+ return nil
+ }
+
+ f = e.Args[0]
+ check.expr(&f_op, f, nil, iota)
+
+ default:
+ unreachable()
+ }
+
+ if f_sig, ok := f_op.typ.(*Signature); ok {
+ if len(f_sig.Results) != 1 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ if _, isSig := f_sig.Results[0].Type.(*Signature); isSig {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ res_typ = f_sig.Results[0].Type
+
+ if len(f_sig.Params) != 1 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ if !isIdentical(f_sig.Params[0].Type, view_typ.EltType()) {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+
+ sig.Params = []*Var{{Type: f_sig}}
+
+ } else {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+
+ return res_typ
+}
+
+func (check *checker) sx_inject_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) Type {
+ var res_typ Type
+
+ sel, ok := e.Fun.(*ast.SelectorExpr)
+ if !ok {
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return nil
+ }
+
+ var view_typ Viewish
+ var group_view_typ *View
+ switch v := sig.Recv.Type.(type) {
+ case *View:
+ view_typ = v
+ case *Table:
+ view_typ = v
+ default:
+ check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
+ x.mode = invalid
+ return nil
+ }
+
+ group_view_typ, ok = view_typ.EltType().(*View)
+ if !ok {
+ check.errorf(e.Pos(), "not an inject view receiver %s", e)
+ x.mode = invalid
+ return nil
+ }
+
+ var f_op operand
+ var f ast.Expr
+
+ switch sel.Sel.Name {
+ case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
+ if len(e.Args) != 1 {
+ check.errorf(e.Pos(), "%s expects one argument", e)
+ x.mode = invalid
+ return nil
+ }
+
+ f = e.Args[0]
+ check.expr(&f_op, f, nil, iota)
+
+ default:
+ unreachable()
+ }
+
+ if f_sig, ok := f_op.typ.(*Signature); ok {
+ if len(f_sig.Results) != 1 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ if _, isSig := f_sig.Results[0].Type.(*Signature); isSig {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ res_typ = f_sig.Results[0].Type
+
+ if len(f_sig.Params) != 2 {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+
+ acc_typ := &Slice{Elt: res_typ}
+ if !isIdentical(f_sig.Params[0].Type, acc_typ) {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+ if !isIdentical(f_sig.Params[1].Type, group_view_typ.EltType()) {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+
+ sig.Params = []*Var{{Type: f_sig}}
+
+ } else {
+ check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
+ x.mode = invalid
+ return nil
+ }
+
+ return res_typ
+}
diff --git a/lang/types/expr_sx.go b/lang/types/expr_sx.go
deleted file mode 100644
index b2baee0..0000000
--- a/lang/types/expr_sx.go
+++ /dev/null
@@ -1,981 +0,0 @@
-package types
-
-import (
- "simplex.sh/lang/ast"
-)
-
-// rawExpr typechecks expression e and initializes x with the expression
-// value or type. If an error occurred, x.mode is set to invalid.
-// A hint != nil is used as operand type for untyped shifted operands;
-// iota >= 0 indicates that the expression is part of a constant declaration.
-// cycleOk indicates whether it is ok for a type expression to refer to itself.
-//
-// see expr.go:641
-func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) {
- if trace {
- c := ""
- if cycleOk {
- c = " ⨁"
- }
- check.trace(e.Pos(), "%s (%s, %d%s)", e, typeString(hint), iota, c)
- defer check.untrace("=> %s", x)
- }
-
- if check.ctxt.Expr != nil {
- defer check.callExpr(x)
- }
-
- switch e := e.(type) {
- case *ast.BadExpr:
- goto Error // error was reported before
-
- case *ast.Ident:
- if e.Name == "_" {
- check.invalidOp(e.Pos(), "cannot use _ as value or type")
- goto Error
- }
- obj := check.lookup(e)
- if obj == nil {
- goto Error // error was reported before
- }
- check.object(obj, cycleOk)
- switch obj := obj.(type) {
- case *Package:
- check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
- goto Error
- case *Const:
- if obj.Val == nil {
- goto Error // cycle detected
- }
- x.mode = constant
- if obj == universeIota {
- if iota < 0 {
- check.invalidAST(e.Pos(), "cannot use iota outside constant declaration")
- goto Error
- }
- x.val = int64(iota)
- } else {
- x.val = obj.Val
- }
- case *TypeName:
- x.mode = typexpr
- if !cycleOk && underlying(obj.Type) == nil {
- check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name)
- x.expr = e
- x.typ = Typ[Invalid]
- return // don't goto Error - need x.mode == typexpr
- }
- case *Var:
- x.mode = variable
- case *Func:
- x.mode = value
- default:
- unreachable()
- }
- x.typ = obj.GetType()
-
- case *ast.Ellipsis:
- // ellipses are handled explicitly where they are legal
- // (array composite literals and parameter lists)
- check.errorf(e.Pos(), "invalid use of '...'")
- goto Error
-
- case *ast.BasicLit:
- x.setConst(e.Kind, e.Value)
- if x.mode == invalid {
- check.invalidAST(e.Pos(), "invalid literal %v", e.Value)
- goto Error
- }
-
- case *ast.FuncLit:
- if sig, ok := check.typ(e.Type, false).(*Signature); ok {
- x.mode = value
- x.typ = sig
- check.later(nil, sig, e.Body)
- } else {
- check.invalidAST(e.Pos(), "invalid function literal %s", e)
- goto Error
- }
-
- case *ast.CompositeLit:
- typ := hint
- openArray := false
- if e.Type != nil {
- // [...]T array types may only appear with composite literals.
- // Check for them here so we don't have to handle ... in general.
- typ = nil
- if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil {
- if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil {
- // We have an "open" [...]T array type.
- // Create a new ArrayType with unknown length (-1)
- // and finish setting it up after analyzing the literal.
- typ = &Array{Len: -1, Elt: check.typ(atyp.Elt, cycleOk)}
- openArray = true
- }
- }
- if typ == nil {
- typ = check.typ(e.Type, false)
- }
- }
- if typ == nil {
- check.errorf(e.Pos(), "missing type in composite literal")
- goto Error
- }
-
- switch utyp := underlying(deref(typ)).(type) {
- case *Struct:
- if len(e.Elts) == 0 {
- break
- }
- fields := utyp.Fields
- if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok {
- // all elements must have keys
- visited := make([]bool, len(fields))
- for _, e := range e.Elts {
- kv, _ := e.(*ast.KeyValueExpr)
- if kv == nil {
- check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal")
- continue
- }
- key, _ := kv.Key.(*ast.Ident)
- if key == nil {
- check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key)
- continue
- }
- i := utyp.fieldIndex(key.Name)
- if i < 0 {
- check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name)
- continue
- }
- // 0 <= i < len(fields)
- if visited[i] {
- check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)
- continue
- }
- visited[i] = true
- check.expr(x, kv.Value, nil, iota)
- etyp := fields[i].Type
- if !x.isAssignable(etyp) {
- check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
- continue
- }
- }
- } else {
- // no element must have a key
- for i, e := range e.Elts {
- if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
- check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal")
- continue
- }
- check.expr(x, e, nil, iota)
- if i >= len(fields) {
- check.errorf(x.pos(), "too many values in struct literal")
- break // cannot continue
- }
- // i < len(fields)
- etyp := fields[i].Type
- if !x.isAssignable(etyp) {
- check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp)
- continue
- }
- }
- if len(e.Elts) < len(fields) {
- check.errorf(e.Rbrace, "too few values in struct literal")
- // ok to continue
- }
- }
-
- case *Array:
- n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota)
- // if we have an "open" [...]T array, set the length now that we know it
- if openArray {
- utyp.Len = n
- }
-
- case *Slice:
- check.indexedElts(e.Elts, utyp.Elt, -1, iota)
-
- case *Map:
- visited := make(map[interface{}]bool, len(e.Elts))
- for _, e := range e.Elts {
- kv, _ := e.(*ast.KeyValueExpr)
- if kv == nil {
- check.errorf(e.Pos(), "missing key in map literal")
- continue
- }
- check.compositeLitKey(kv.Key)
- check.expr(x, kv.Key, nil, iota)
- if !x.isAssignable(utyp.Key) {
- check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.Key)
- continue
- }
- if x.mode == constant {
- if visited[x.val] {
- check.errorf(x.pos(), "duplicate key %s in map literal", x.val)
- continue
- }
- visited[x.val] = true
- }
- check.expr(x, kv.Value, utyp.Elt, iota)
- if !x.isAssignable(utyp.Elt) {
- check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.Elt)
- continue
- }
- }
-
- default:
- check.errorf(e.Pos(), "%s is not a valid composite literal type", typ)
- goto Error
- }
-
- x.mode = value
- x.typ = typ
-
- case *ast.ParenExpr:
- check.rawExpr(x, e.X, hint, iota, cycleOk)
-
- case *ast.SelectorExpr:
- sel := e.Sel.Name
- // If the identifier refers to a package, handle everything here
- // so we don't need a "package" mode for operands: package names
- // can only appear in qualified identifiers which are mapped to
- // selector expressions.
- if ident, ok := e.X.(*ast.Ident); ok {
- if pkg, ok := check.lookup(ident).(*Package); ok {
- exp := pkg.Scope.Lookup(sel)
- if exp == nil {
- check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
- goto Error
- }
- check.register(e.Sel, exp)
- // Simplified version of the code for *ast.Idents:
- // - imported packages use types.Scope and types.Objects
- // - imported objects are always fully initialized
- switch exp := exp.(type) {
- case *Const:
- assert(exp.Val != nil)
- x.mode = constant
- x.typ = exp.Type
- x.val = exp.Val
- case *TypeName:
- x.mode = typexpr
- x.typ = exp.Type
- case *Var:
- x.mode = variable
- x.typ = exp.Type
- case *Func:
- x.mode = value
- x.typ = exp.Type
- default:
- unreachable()
- }
- x.expr = e
- return
- }
- }
-
- check.exprOrType(x, e.X, nil, iota, false)
- if x.mode == invalid {
- goto Error
- }
- mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
- if mode == invalid {
- check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
- goto Error
- }
- if x.mode == typexpr {
- // method expression
- sig, ok := typ.(*Signature)
- if !ok {
- check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
- goto Error
- }
- // the receiver type becomes the type of the first function
- // argument of the method expression's function type
- // TODO(gri) at the moment, method sets don't correctly track
- // pointer vs non-pointer receivers => typechecker is too lenient
- x.mode = value
- x.typ = &Signature{
- Params: append([]*Var{{Type: x.typ}}, sig.Params...),
- Results: sig.Results,
- IsVariadic: sig.IsVariadic,
- }
- } else {
- // regular selector
- x.mode = mode
- x.typ = typ
- }
-
- case *ast.IndexExpr:
- check.expr(x, e.X, hint, iota)
-
- valid := false
- length := int64(-1) // valid if >= 0
- switch typ := underlying(x.typ).(type) {
- case *Basic:
- if isString(typ) {
- valid = true
- if x.mode == constant {
- length = int64(len(x.val.(string)))
- }
- // an indexed string always yields a byte value
- // (not a constant) even if the string and the
- // index are constant
- x.mode = value
- x.typ = Typ[Byte]
- }
-
- case *Array:
- valid = true
- length = typ.Len
- if x.mode != variable {
- x.mode = value
- }
- x.typ = typ.Elt
-
- case *Pointer:
- if typ, _ := underlying(typ.Base).(*Array); typ != nil {
- valid = true
- length = typ.Len
- x.mode = variable
- x.typ = typ.Elt
- }
-
- case *Slice:
- valid = true
- x.mode = variable
- x.typ = typ.Elt
-
- case *Map:
- var key operand
- check.expr(&key, e.Index, nil, iota)
- if key.mode == invalid || !key.isAssignable(typ.Key) {
- check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key)
- goto Error
- }
- x.mode = valueok
- x.typ = typ.Elt
- x.expr = e
- return
- }
-
- if !valid {
- check.invalidOp(x.pos(), "cannot index %s", x)
- goto Error
- }
-
- if e.Index == nil {
- check.invalidAST(e.Pos(), "missing index expression for %s", x)
- return
- }
-
- check.index(e.Index, length, iota)
- // ok to continue
-
- case *ast.SliceExpr:
- check.expr(x, e.X, hint, iota)
-
- valid := false
- length := int64(-1) // valid if >= 0
- switch typ := underlying(x.typ).(type) {
- case *Basic:
- if isString(typ) {
- valid = true
- if x.mode == constant {
- length = int64(len(x.val.(string))) + 1 // +1 for slice
- }
- // a sliced string always yields a string value
- // of the same type as the original string (not
- // a constant) even if the string and the indices
- // are constant
- x.mode = value
- // x.typ doesn't change
- }
-
- case *Array:
- valid = true
- length = typ.Len + 1 // +1 for slice
- if x.mode != variable {
- check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x)
- goto Error
- }
- x.typ = &Slice{Elt: typ.Elt}
-
- case *Pointer:
- if typ, _ := underlying(typ.Base).(*Array); typ != nil {
- valid = true
- length = typ.Len + 1 // +1 for slice
- x.mode = variable
- x.typ = &Slice{Elt: typ.Elt}
- }
-
- case *Slice:
- valid = true
- x.mode = variable
- // x.typ doesn't change
- }
-
- if !valid {
- check.invalidOp(x.pos(), "cannot slice %s", x)
- goto Error
- }
-
- lo := int64(0)
- if e.Low != nil {
- lo = check.index(e.Low, length, iota)
- }
-
- hi := int64(-1)
- if e.High != nil {
- hi = check.index(e.High, length, iota)
- } else if length >= 0 {
- hi = length
- }
-
- if lo >= 0 && hi >= 0 && lo > hi {
- check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi)
- // ok to continue
- }
-
- case *ast.TypeAssertExpr:
- check.expr(x, e.X, hint, iota)
- if x.mode == invalid {
- goto Error
- }
- var T *Interface
- if T, _ = underlying(x.typ).(*Interface); T == nil {
- check.invalidOp(x.pos(), "%s is not an interface", x)
- goto Error
- }
- // x.(type) expressions are handled explicitly in type switches
- if e.Type == nil {
- check.errorf(e.Pos(), "use of .(type) outside type switch")
- goto Error
- }
- typ := check.typ(e.Type, false)
- if typ == Typ[Invalid] {
- goto Error
- }
- if method, wrongType := missingMethod(typ, T); method != nil {
- var msg string
- if wrongType {
- msg = "%s cannot have dynamic type %s (wrong type for method %s)"
- } else {
- msg = "%s cannot have dynamic type %s (missing method %s)"
- }
- check.errorf(e.Type.Pos(), msg, x, typ, method.Name)
- // ok to continue
- }
- x.mode = valueok
- x.expr = e
- x.typ = typ
-
- case *ast.CallExpr:
- check.exprOrType(x, e.Fun, nil, iota, false)
- if x.mode == invalid {
- goto Error
- } else if x.mode == typexpr {
- check.conversion(x, e, x.typ, iota)
-
- } else if sig, ok := underlying(x.typ).(*Signature); ok {
- if sig.Recv != nil {
- if _, ok := sig.Recv.Type.(Viewish); ok {
- check.sx_step(x, e, sig, hint, iota)
- if x.mode == invalid {
- goto Error
- }
- }
- }
-
- // check parameters
-
- // If we have a trailing ... at the end of the parameter
- // list, the last argument must match the parameter type
- // []T of a variadic function parameter x ...T.
- passSlice := false
- if e.Ellipsis.IsValid() {
- if sig.IsVariadic {
- passSlice = true
- } else {
- check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun)
- // ok to continue
- }
- }
-
- // If we have a single argument that is a function call
- // we need to handle it separately. Determine if this
- // is the case without checking the argument.
- var call *ast.CallExpr
- if len(e.Args) == 1 {
- call, _ = unparen(e.Args[0]).(*ast.CallExpr)
- }
-
- n := 0 // parameter count
- if call != nil {
- // We have a single argument that is a function call.
- check.expr(x, call, nil, -1)
- if x.mode == invalid {
- goto Error // TODO(gri): we can do better
- }
- if t, _ := x.typ.(*Result); t != nil {
- // multiple result values
- n = len(t.Values)
- for i, obj := range t.Values {
- x.mode = value
- x.expr = nil // TODO(gri) can we do better here? (for good error messages)
- x.typ = obj.Type
- check.argument(sig, i, nil, x, passSlice && i+1 == n)
- }
- } else {
- // single result value
- n = 1
- check.argument(sig, 0, nil, x, passSlice)
- }
-
- } else {
- // We don't have a single argument or it is not a function call.
- n = len(e.Args)
- for i, arg := range e.Args {
- check.argument(sig, i, arg, x, passSlice && i+1 == n)
- }
- }
-
- // determine if we have enough arguments
- if sig.IsVariadic {
- // a variadic function accepts an "empty"
- // last argument: count one extra
- n++
- }
- if n < len(sig.Params) {
- check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
- // ok to continue
- }
-
- // determine result
- switch len(sig.Results) {
- case 0:
- x.mode = novalue
- case 1:
- x.mode = value
- x.typ = sig.Results[0].Type
- default:
- x.mode = value
- x.typ = &Result{Values: sig.Results}
- }
-
- } else if bin, ok := x.typ.(*builtin); ok {
- check.builtin(x, e, bin, iota)
-
- } else {
- check.invalidOp(x.pos(), "cannot call non-function %s", x)
- goto Error
- }
-
- case *ast.StarExpr:
- check.exprOrType(x, e.X, hint, iota, true)
- switch x.mode {
- case invalid:
- goto Error
- case typexpr:
- x.typ = &Pointer{Base: x.typ}
- default:
- if typ, ok := x.typ.(*Pointer); ok {
- x.mode = variable
- x.typ = typ.Base
- } else {
- check.invalidOp(x.pos(), "cannot indirect %s", x)
- goto Error
- }
- }
-
- case *ast.UnaryExpr:
- check.expr(x, e.X, hint, iota)
- check.unary(x, e.Op)
-
- case *ast.BinaryExpr:
- var y operand
- check.expr(x, e.X, hint, iota)
- check.expr(&y, e.Y, hint, iota)
- check.binary(x, &y, e.Op, hint)
-
- case *ast.KeyValueExpr:
- // key:value expressions are handled in composite literals
- check.invalidAST(e.Pos(), "no key:value expected")
- goto Error
-
- case *ast.ArrayType:
- if e.Len != nil {
- check.expr(x, e.Len, nil, iota)
- if x.mode == invalid {
- goto Error
- }
- if x.mode != constant {
- if x.mode != invalid {
- check.errorf(x.pos(), "array length %s must be constant", x)
- }
- goto Error
- }
- n, ok := x.val.(int64)
- if !ok || n < 0 {
- check.errorf(x.pos(), "invalid array length %s", x)
- goto Error
- }
- x.typ = &Array{Len: n, Elt: check.typ(e.Elt, cycleOk)}
- } else {
- x.typ = &Slice{Elt: check.typ(e.Elt, true)}
- }
- x.mode = typexpr
-
- case *ast.StructType:
- x.mode = typexpr
- x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)}
-
- case *ast.FuncType:
- params, isVariadic := check.collectParams(e.Params, true)
- results, _ := check.collectParams(e.Results, false)
- x.mode = typexpr
- x.typ = &Signature{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic}
-
- case *ast.InterfaceType:
- x.mode = typexpr
- x.typ = &Interface{Methods: check.collectMethods(e.Methods)}
-
- case *ast.MapType:
- x.mode = typexpr
- x.typ = &Map{Key: check.typ(e.Key, true), Elt: check.typ(e.Value, true)}
-
- case *ast.ChanType:
- x.mode = typexpr
- x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)}
-
- //=== start custom
- case *ast.ViewType:
- x.mode = typexpr
- if e.Key != nil {
- x.typ = &View{Key: check.typ(e.Key, false), Elt: check.typ(e.Value, false)}
- } else {
- x.typ = &View{Elt: check.typ(e.Value, false)}
- }
-
- case *ast.TableType:
- x.mode = typexpr
- x.typ = &Table{Key: check.typ(e.Key, false), Elt: check.typ(e.Value, false)}
- //=== end custom
-
- default:
- check.dump("e = %s", e)
- unreachable()
- }
-
- // everything went well
- x.expr = e
- return
-
-Error:
- x.mode = invalid
- x.expr = e
-}
-
-func (check *checker) sx_step(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) {
- sel, ok := e.Fun.(*ast.SelectorExpr)
- if !ok {
- x.mode = invalid
- return
- }
- builtin := sel.Sel.Name
-
- switch builtin {
-
- case "select", "reject":
- check.sx_predicate_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Key: v.KeyType(), Elt: v.EltType()}
- sig.Results = []*Var{{Type: r}}
- }
-
- case "detect":
- check.sx_predicate_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- r := sig.Recv.Type.(Viewish).EltType()
- sig.Results = []*Var{{Type: r}}
- }
-
- case "collect":
- res_typ := check.sx_map_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Key: v.KeyType(), Elt: res_typ}
- sig.Results = []*Var{{Type: r}}
- }
-
- case "inject":
- res_typ := check.sx_inject_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Key: v.KeyType(), Elt: res_typ}
- sig.Results = []*Var{{Type: r}}
- }
-
- case "group":
- res_typ := check.sx_map_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Key: res_typ, Elt: &View{Key: v.KeyType(), Elt: v.EltType()}}
- sig.Results = []*Var{{Type: r}}
- }
-
- case "index":
- res_typ := check.sx_map_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Key: res_typ, Elt: v.EltType()}
- sig.Results = []*Var{{Type: r}}
- }
-
- case "sort":
- check.sx_map_function(x, e, sig, hint, iota)
- if x.mode != invalid {
- v := sig.Recv.Type.(Viewish)
- r := &View{Elt: v.EltType()}
- sig.Results = []*Var{{Type: r}}
- }
-
- default:
- unreachable()
-
- }
-}
-
-func (check *checker) sx_predicate_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) {
- sel, ok := e.Fun.(*ast.SelectorExpr)
- if !ok {
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return
- }
-
- var view_typ Viewish
- switch v := sig.Recv.Type.(type) {
- case *View:
- view_typ = v
- case *Table:
- view_typ = v
- default:
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return
- }
-
- var f_op operand
- var f ast.Expr
-
- switch sel.Sel.Name {
- case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
- if len(e.Args) != 1 {
- check.errorf(e.Pos(), "%s expects one argument", e)
- x.mode = invalid
- return
- }
-
- f = e.Args[0]
- check.expr(&f_op, f, nil, iota)
-
- default:
- unreachable()
- }
-
- if f_sig, ok := f_op.typ.(*Signature); ok {
- if len(f_sig.Results) != 1 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return
- }
- if !isBoolean(f_sig.Results[0].Type) {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return
- }
- if len(f_sig.Params) != 1 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return
- }
- if !isIdentical(f_sig.Params[0].Type, view_typ.EltType()) {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return
- }
-
- sig.Params = []*Var{{Type: f_sig}}
- } else {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return
- }
-}
-
-func (check *checker) sx_map_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) Type {
- var res_typ Type
-
- sel, ok := e.Fun.(*ast.SelectorExpr)
- if !ok {
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return nil
- }
-
- var view_typ Viewish
- switch v := sig.Recv.Type.(type) {
- case *View:
- view_typ = v
- case *Table:
- view_typ = v
- default:
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return nil
- }
-
- var f_op operand
- var f ast.Expr
-
- switch sel.Sel.Name {
- case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
- if len(e.Args) != 1 {
- check.errorf(e.Pos(), "%s expects one argument", e)
- x.mode = invalid
- return nil
- }
-
- f = e.Args[0]
- check.expr(&f_op, f, nil, iota)
-
- default:
- unreachable()
- }
-
- if f_sig, ok := f_op.typ.(*Signature); ok {
- if len(f_sig.Results) != 1 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- if _, isSig := f_sig.Results[0].Type.(*Signature); isSig {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- res_typ = f_sig.Results[0].Type
-
- if len(f_sig.Params) != 1 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- if !isIdentical(f_sig.Params[0].Type, view_typ.EltType()) {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
-
- sig.Params = []*Var{{Type: f_sig}}
-
- } else {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
-
- return res_typ
-}
-
-func (check *checker) sx_inject_function(x *operand, e *ast.CallExpr, sig *Signature, hint Type, iota int) Type {
- var res_typ Type
-
- sel, ok := e.Fun.(*ast.SelectorExpr)
- if !ok {
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return nil
- }
-
- var view_typ Viewish
- var group_view_typ *View
- switch v := sig.Recv.Type.(type) {
- case *View:
- view_typ = v
- case *Table:
- view_typ = v
- default:
- check.errorf(e.Pos(), "not a view or table receiver %s", e.Fun)
- x.mode = invalid
- return nil
- }
-
- group_view_typ, ok = view_typ.EltType().(*View)
- if !ok {
- check.errorf(e.Pos(), "not an inject view receiver %s", e)
- x.mode = invalid
- return nil
- }
-
- var f_op operand
- var f ast.Expr
-
- switch sel.Sel.Name {
- case "select", "detect", "reject", "collect", "inject", "group", "index", "sort":
- if len(e.Args) != 1 {
- check.errorf(e.Pos(), "%s expects one argument", e)
- x.mode = invalid
- return nil
- }
-
- f = e.Args[0]
- check.expr(&f_op, f, nil, iota)
-
- default:
- unreachable()
- }
-
- if f_sig, ok := f_op.typ.(*Signature); ok {
- if len(f_sig.Results) != 1 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- if _, isSig := f_sig.Results[0].Type.(*Signature); isSig {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- res_typ = f_sig.Results[0].Type
-
- if len(f_sig.Params) != 2 {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
-
- acc_typ := &Slice{Elt: res_typ}
- if !isIdentical(f_sig.Params[0].Type, acc_typ) {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
- if !isIdentical(f_sig.Params[1].Type, group_view_typ.EltType()) {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
-
- sig.Params = []*Var{{Type: f_sig}}
-
- } else {
- check.errorf(f.Pos(), "not a "+sel.Sel.Name+" function %s", f)
- x.mode = invalid
- return nil
- }
-
- return res_typ
-}
diff --git a/lang/types/operand.go b/lang/types/operand.go
index 75e7558..8b18e6d 100644
--- a/lang/types/operand.go
+++ b/lang/types/operand.go
@@ -36,6 +36,17 @@ var operandModeString = [...]string{
valueok: "value,ok",
}
+var sx_step_names = [...]string{
+ "select",
+ "reject",
+ "detect",
+ "collect",
+ "inject",
+ "group",
+ "index",
+ "sort",
+}
+
// An operand represents an intermediate value during type checking.
// Operands have an (addressing) mode, the expression evaluating to
// the operand, the operand's type, and for constants a constant value.
@@ -124,87 +135,145 @@ func (x *operand) isNil() bool {
return x.mode == constant && x.val == nilConst
}
-/*Simplex
// TODO(gri) The functions operand.isAssignable, checker.convertUntyped,
// checker.isRepresentable, and checker.assignOperand are
// overlapping in functionality. Need to simplify and clean up.
// isAssignable reports whether x is assignable to a variable of type T.
func (x *operand) isAssignable(T Type) bool {
- if x.mode == invalid || T == Typ[Invalid] {
- return true // avoid spurious errors
- }
-
- V := x.typ
-
- // x's type is identical to T
- if isIdentical(V, T) {
- return true
- }
-
- Vu := underlying(V)
- Tu := underlying(T)
-
- // x's type V and T have identical underlying types
- // and at least one of V or T is not a named type
- if isIdentical(Vu, Tu) {
- return !isNamed(V) || !isNamed(T)
- }
-
- // T is an interface type and x implements T
- if Ti, ok := Tu.(*Interface); ok {
- if m, _ := missingMethod(x.typ, Ti); m == nil {
- return true
- }
- }
-
- // x is a bidirectional channel value, T is a channel
- // type, x's type V and T have identical element types,
- // and at least one of V or T is not a named type
- if Vc, ok := Vu.(*Chan); ok && Vc.Dir == ast.SEND|ast.RECV {
- if Tc, ok := Tu.(*Chan); ok && isIdentical(Vc.Elt, Tc.Elt) {
- return !isNamed(V) || !isNamed(T)
- }
- }
-
- // x is the predeclared identifier nil and T is a pointer,
- // function, slice, map, channel, or interface type
- if x.isNil() {
- switch t := Tu.(type) {
- case *Basic:
- if t.Kind == UnsafePointer {
- return true
- }
- case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface:
- return true
- }
- return false
- }
-
- // x is an untyped constant representable by a value of type T
- // TODO(gri) This is borrowing from checker.convertUntyped and
- // checker.isRepresentable. Need to clean up.
- if isUntyped(Vu) {
- switch t := Tu.(type) {
- case *Basic:
- if x.mode == constant {
- return isRepresentableConst(x.val, t.Kind)
- }
- // The result of a comparison is an untyped boolean,
- // but may not be a constant.
- if Vb, _ := Vu.(*Basic); Vb != nil {
- return Vb.Kind == UntypedBool && isBoolean(Tu)
- }
- case *Interface:
- return x.isNil() || len(t.Methods) == 0
- case *Pointer, *Signature, *Slice, *Map, *Chan:
- return x.isNil()
- }
- }
-
- return false
+ if x.mode == invalid || T == Typ[Invalid] {
+ return true // avoid spurious errors
+ }
+
+ V := x.typ
+
+ // x's type is identical to T
+ if isIdentical(V, T) {
+ return true
+ }
+
+ Vu := underlying(V)
+ Tu := underlying(T)
+
+ // x's type V and T have identical underlying types
+ // and at least one of V or T is not a named type
+ if isIdentical(Vu, Tu) {
+ return !isNamed(V) || !isNamed(T)
+ }
+
+ // T is an interface type and x implements T
+ if Ti, ok := Tu.(*Interface); ok {
+ if m, _ := missingMethod(x.typ, Ti); m == nil {
+ return true
+ }
+ }
+
+ // x is a bidirectional channel value, T is a channel
+ // type, x's type V and T have identical element types,
+ // and at least one of V or T is not a named type
+ if Vc, ok := Vu.(*Chan); ok && Vc.Dir == ast.SEND|ast.RECV {
+ if Tc, ok := Tu.(*Chan); ok && isIdentical(Vc.Elt, Tc.Elt) {
+ return !isNamed(V) || !isNamed(T)
+ }
+ }
+
+ //=== start custom
+
+ // x is a keyed view value, T is a view type,
+ // x's type V and T have identical element types,
+ // and at least one of V or T is not a named type
+ if Vv, ok := Vu.(*View); ok {
+ if Tv, ok := Tu.(*View); ok && Vv.Key != nil && isIdentical(Vv.Elt, Tv.Elt) {
+ return !isNamed(V) || !isNamed(T)
+ }
+ if _, ok := Tu.(*Interface); ok && isNamed(T) {
+ n := T.(*NamedType).Obj.GetName()
+ if n == "Resolver" || n == "IndexedView" {
+ return true
+ }
+ }
+ }
+
+ // x is a table value, T is a view keyed type,
+ // x's type V and T have identical element types,
+ // and at least one of V or T is not a named type
+ if Vv, ok := Vu.(*Table); ok {
+ if Tv, ok := Tu.(*View); ok && isIdentical(Vv.Elt, Tv.Elt) && isIdentical(Vv.Key, Tv.Key) {
+ return !isNamed(V) || !isNamed(T)
+ }
+ if _, ok := Tu.(*Interface); ok && isNamed(T) {
+ n := T.(*NamedType).Obj.GetName()
+ if n == "Resolver" || n == "IndexedView" || n == "Table" {
+ return true
+ }
+ }
+ }
+
+ // x is a table value, T is a view type,
+ // x's type V and T have identical element types,
+ // and at least one of V or T is not a named type
+ if Vv, ok := Vu.(*Table); ok {
+ if Tv, ok := Tu.(*View); ok && isIdentical(Vv.Elt, Tv.Elt) && Tv.Key == nil {
+ return !isNamed(V) || !isNamed(T)
+ }
+ if _, ok := Tu.(*Interface); ok && isNamed(T) {
+ n := T.(*NamedType).Obj.GetName()
+ if n == "Resolver" || n == "IndexedView" || n == "Table" {
+ return true
+ }
+ }
+ }
+
+ //=== end custom
+
+ // x is the predeclared identifier nil and T is a pointer,
+ // function, slice, map, channel, or interface type
+ if x.isNil() {
+ switch t := Tu.(type) {
+ case *Basic:
+ if t.Kind == UnsafePointer {
+ return true
+ }
+ case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface:
+ return true
+
+ //=== start custom
+ case *View, *Table:
+ return true
+ //=== end custom
+ }
+ return false
+ }
+
+ // x is an untyped constant representable by a value of type T
+ // TODO(gri) This is borrowing from checker.convertUntyped and
+ // checker.isRepresentable. Need to clean up.
+ if isUntyped(Vu) {
+ switch t := Tu.(type) {
+ case *Basic:
+ if x.mode == constant {
+ return isRepresentableConst(x.val, t.Kind)
+ }
+ // The result of a comparison is an untyped boolean,
+ // but may not be a constant.
+ if Vb, _ := Vu.(*Basic); Vb != nil {
+ return Vb.Kind == UntypedBool && isBoolean(Tu)
+ }
+ case *Interface:
+ return x.isNil() || len(t.Methods) == 0
+ case *Pointer, *Signature, *Slice, *Map, *Chan:
+ return x.isNil()
+
+ //=== start custom
+ case *View, *Table:
+ return x.isNil()
+ //=== end custom
+
+ }
+ }
+
+ return false
}
-*/
// isInteger reports whether x is a (typed or untyped) integer value.
func (x *operand) isInteger() bool {
@@ -350,48 +419,56 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType {
return nil
}
-/*Simplex
func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
- typ = deref(typ)
-
- if t, ok := typ.(*NamedType); ok {
- for _, m := range t.Methods {
- if name.IsSame(m.QualifiedName) {
- assert(m.Type != nil)
- return value, m.Type
- }
- }
- typ = t.Underlying
- }
-
- switch t := typ.(type) {
- case *Struct:
- var next []embeddedType
- for _, f := range t.Fields {
- if name.IsSame(f.QualifiedName) {
- return variable, f.Type
- }
- if f.IsAnonymous {
- // Possible optimization: If the embedded type
- // is a pointer to the current type we could
- // ignore it.
- next = append(next, embeddedType{typ: deref(f.Type).(*NamedType)})
- }
- }
- if len(next) > 0 {
- res := lookupFieldBreadthFirst(next, name)
- return res.mode, res.typ
- }
-
- case *Interface:
- for _, m := range t.Methods {
- if name.IsSame(m.QualifiedName) {
- return value, m.Type
- }
- }
- }
-
- // not found
- return invalid, nil
+ typ = deref(typ)
+
+ if t, ok := typ.(*NamedType); ok {
+ for _, m := range t.Methods {
+ if name.IsSame(m.QualifiedName) {
+ assert(m.Type != nil)
+ return value, m.Type
+ }
+ }
+ typ = t.Underlying
+ }
+
+ switch t := typ.(type) {
+ case *Struct:
+ var next []embeddedType
+ for _, f := range t.Fields {
+ if name.IsSame(f.QualifiedName) {
+ return variable, f.Type
+ }
+ if f.IsAnonymous {
+ // Possible optimization: If the embedded type
+ // is a pointer to the current type we could
+ // ignore it.
+ next = append(next, embeddedType{typ: deref(f.Type).(*NamedType)})
+ }
+ }
+ if len(next) > 0 {
+ res := lookupFieldBreadthFirst(next, name)
+ return res.mode, res.typ
+ }
+
+ case *Interface:
+ for _, m := range t.Methods {
+ if name.IsSame(m.QualifiedName) {
+ return value, m.Type
+ }
+ }
+
+ //=== start custom
+ case *View, *Table:
+ for _, n := range sx_step_names {
+ if n == name.Name {
+ return value, &Signature{Recv: &Var{Type: typ}}
+ }
+ }
+ //=== end custom
+
+ }
+
+ // not found
+ return invalid, nil
}
-*/
diff --git a/lang/types/operand_sx.go b/lang/types/operand_sx.go
deleted file mode 100644
index fbcd574..0000000
--- a/lang/types/operand_sx.go
+++ /dev/null
@@ -1,213 +0,0 @@
-package types
-
-import (
- "simplex.sh/lang/ast"
-)
-
-// TODO(gri) The functions operand.isAssignable, checker.convertUntyped,
-// checker.isRepresentable, and checker.assignOperand are
-// overlapping in functionality. Need to simplify and clean up.
-
-// isAssignable reports whether x is assignable to a variable of type T.
-//
-// see operand.go:133
-func (x *operand) isAssignable(T Type) bool {
- if x.mode == invalid || T == Typ[Invalid] {
- return true // avoid spurious errors
- }
-
- V := x.typ
-
- // x's type is identical to T
- if isIdentical(V, T) {
- return true
- }
-
- Vu := underlying(V)
- Tu := underlying(T)
-
- // x's type V and T have identical underlying types
- // and at least one of V or T is not a named type
- if isIdentical(Vu, Tu) {
- return !isNamed(V) || !isNamed(T)
- }
-
- // T is an interface type and x implements T
- if Ti, ok := Tu.(*Interface); ok {
- if m, _ := missingMethod(x.typ, Ti); m == nil {
- return true
- }
- }
-
- // x is a bidirectional channel value, T is a channel
- // type, x's type V and T have identical element types,
- // and at least one of V or T is not a named type
- if Vc, ok := Vu.(*Chan); ok && Vc.Dir == ast.SEND|ast.RECV {
- if Tc, ok := Tu.(*Chan); ok && isIdentical(Vc.Elt, Tc.Elt) {
- return !isNamed(V) || !isNamed(T)
- }
- }
-
- //=== start custom
-
- // x is a keyed view value, T is a view type,
- // x's type V and T have identical element types,
- // and at least one of V or T is not a named type
- if Vv, ok := Vu.(*View); ok {
- if Tv, ok := Tu.(*View); ok && Vv.Key != nil && isIdentical(Vv.Elt, Tv.Elt) {
- return !isNamed(V) || !isNamed(T)
- }
- if _, ok := Tu.(*Interface); ok && isNamed(T) {
- n := T.(*NamedType).Obj.GetName()
- if n == "Deferred" || n == "IndexedView" {
- return true
- }
- }
- }
-
- // x is a table value, T is a view keyed type,
- // x's type V and T have identical element types,
- // and at least one of V or T is not a named type
- if Vv, ok := Vu.(*Table); ok {
- if Tv, ok := Tu.(*View); ok && isIdentical(Vv.Elt, Tv.Elt) && isIdentical(Vv.Key, Tv.Key) {
- return !isNamed(V) || !isNamed(T)
- }
- if _, ok := Tu.(*Interface); ok && isNamed(T) {
- n := T.(*NamedType).Obj.GetName()
- if n == "Deferred" || n == "IndexedView" || n == "Table" {
- return true
- }
- }
- }
-
- // x is a table value, T is a view type,
- // x's type V and T have identical element types,
- // and at least one of V or T is not a named type
- if Vv, ok := Vu.(*Table); ok {
- if Tv, ok := Tu.(*View); ok && isIdentical(Vv.Elt, Tv.Elt) && Tv.Key == nil {
- return !isNamed(V) || !isNamed(T)
- }
- if _, ok := Tu.(*Interface); ok && isNamed(T) {
- n := T.(*NamedType).Obj.GetName()
- if n == "Deferred" || n == "IndexedView" || n == "Table" {
- return true
- }
- }
- }
-
- //=== end custom
-
- // x is the predeclared identifier nil and T is a pointer,
- // function, slice, map, channel, or interface type
- if x.isNil() {
- switch t := Tu.(type) {
- case *Basic:
- if t.Kind == UnsafePointer {
- return true
- }
- case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface:
- return true
-
- //=== start custom
- case *View, *Table:
- return true
- //=== end custom
- }
- return false
- }
-
- // x is an untyped constant representable by a value of type T
- // TODO(gri) This is borrowing from checker.convertUntyped and
- // checker.isRepresentable. Need to clean up.
- if isUntyped(Vu) {
- switch t := Tu.(type) {
- case *Basic:
- if x.mode == constant {
- return isRepresentableConst(x.val, t.Kind)
- }
- // The result of a comparison is an untyped boolean,
- // but may not be a constant.
- if Vb, _ := Vu.(*Basic); Vb != nil {
- return Vb.Kind == UntypedBool && isBoolean(Tu)
- }
- case *Interface:
- return x.isNil() || len(t.Methods) == 0
- case *Pointer, *Signature, *Slice, *Map, *Chan:
- return x.isNil()
-
- //=== start custom
- case *View, *Table:
- return x.isNil()
- //=== end custom
-
- }
- }
-
- return false
-}
-
-// see operand.go:354
-func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
- typ = deref(typ)
-
- if t, ok := typ.(*NamedType); ok {
- for _, m := range t.Methods {
- if name.IsSame(m.QualifiedName) {
- assert(m.Type != nil)
- return value, m.Type
- }
- }
- typ = t.Underlying
- }
-
- switch t := typ.(type) {
- case *Struct:
- var next []embeddedType
- for _, f := range t.Fields {
- if name.IsSame(f.QualifiedName) {
- return variable, f.Type
- }
- if f.IsAnonymous {
- // Possible optimization: If the embedded type
- // is a pointer to the current type we could
- // ignore it.
- next = append(next, embeddedType{typ: deref(f.Type).(*NamedType)})
- }
- }
- if len(next) > 0 {
- res := lookupFieldBreadthFirst(next, name)
- return res.mode, res.typ
- }
-
- case *Interface:
- for _, m := range t.Methods {
- if name.IsSame(m.QualifiedName) {
- return value, m.Type
- }
- }
-
- //=== start custom
- case *View, *Table:
- for _, n := range sx_step_names {
- if n == name.Name {
- return value, &Signature{Recv: &Var{Type: typ}}
- }
- }
- //=== end custom
-
- }
-
- // not found
- return invalid, nil
-}
-
-var sx_step_names = [...]string{
- "select",
- "reject",
- "detect",
- "collect",
- "inject",
- "group",
- "index",
- "sort",
-}
diff --git a/lang/types/predicates.go b/lang/types/predicates.go
index c7d3c90..ae8d199 100644
--- a/lang/types/predicates.go
+++ b/lang/types/predicates.go
@@ -84,114 +84,131 @@ func isComparable(typ Type) bool {
return false
}
-/*Simplex
func hasNil(typ Type) bool {
- switch underlying(typ).(type) {
- case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
- return true
- }
- return false
+ switch underlying(typ).(type) {
+ case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
+ return true
+
+ //=== start custom
+ case *View, *Table:
+ return true
+ //=== end custom
+
+ }
+ return false
}
-*/
-/*Simplex
// identical returns true if x and y are identical.
func isIdentical(x, y Type) bool {
- if x == y {
- return true
- }
-
- switch x := x.(type) {
- case *Basic:
- // Basic types are singletons except for the rune and byte
- // aliases, thus we cannot solely rely on the x == y check
- // above.
- if y, ok := y.(*Basic); ok {
- return x.Kind == y.Kind
- }
-
- case *Array:
- // Two array types are identical if they have identical element types
- // and the same array length.
- if y, ok := y.(*Array); ok {
- return x.Len == y.Len && isIdentical(x.Elt, y.Elt)
- }
-
- case *Slice:
- // Two slice types are identical if they have identical element types.
- if y, ok := y.(*Slice); ok {
- return isIdentical(x.Elt, y.Elt)
- }
-
- case *Struct:
- // Two struct types are identical if they have the same sequence of fields,
- // and if corresponding fields have the same names, and identical types,
- // and identical tags. Two anonymous fields are considered to have the same
- // name. Lower-case field names from different packages are always different.
- if y, ok := y.(*Struct); ok {
- if len(x.Fields) == len(y.Fields) {
- for i, f := range x.Fields {
- g := y.Fields[i]
- if !f.QualifiedName.IsSame(g.QualifiedName) ||
- !isIdentical(f.Type, g.Type) ||
- f.Tag != g.Tag ||
- f.IsAnonymous != g.IsAnonymous {
- return false
- }
- }
- return true
- }
- }
-
- case *Pointer:
- // Two pointer types are identical if they have identical base types.
- if y, ok := y.(*Pointer); ok {
- return isIdentical(x.Base, y.Base)
- }
-
- case *Signature:
- // Two function types are identical if they have the same number of parameters
- // and result values, corresponding parameter and result types are identical,
- // and either both functions are variadic or neither is. Parameter and result
- // names are not required to match.
- if y, ok := y.(*Signature); ok {
- return identicalTypes(x.Params, y.Params) &&
- identicalTypes(x.Results, y.Results) &&
- x.IsVariadic == y.IsVariadic
- }
-
- case *Interface:
- // Two interface types are identical if they have the same set of methods with
- // the same names and identical function types. Lower-case method names from
- // different packages are always different. The order of the methods is irrelevant.
- if y, ok := y.(*Interface); ok {
- return identicalMethods(x.Methods, y.Methods) // methods are sorted
- }
-
- case *Map:
- // Two map types are identical if they have identical key and value types.
- if y, ok := y.(*Map); ok {
- return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
- }
-
- case *Chan:
- // Two channel types are identical if they have identical value types
- // and the same direction.
- if y, ok := y.(*Chan); ok {
- return x.Dir == y.Dir && isIdentical(x.Elt, y.Elt)
- }
-
- case *NamedType:
- // Two named types are identical if their type names originate
- // in the same type declaration.
- if y, ok := y.(*NamedType); ok {
- return x.Obj == y.Obj
- }
- }
-
- return false
+ if x == y {
+ return true
+ }
+
+ switch x := x.(type) {
+ case *Basic:
+ // Basic types are singletons except for the rune and byte
+ // aliases, thus we cannot solely rely on the x == y check
+ // above.
+ if y, ok := y.(*Basic); ok {
+ return x.Kind == y.Kind
+ }
+
+ case *Array:
+ // Two array types are identical if they have identical element types
+ // and the same array length.
+ if y, ok := y.(*Array); ok {
+ return x.Len == y.Len && isIdentical(x.Elt, y.Elt)
+ }
+
+ case *Slice:
+ // Two slice types are identical if they have identical element types.
+ if y, ok := y.(*Slice); ok {
+ return isIdentical(x.Elt, y.Elt)
+ }
+
+ case *Struct:
+ // Two struct types are identical if they have the same sequence of fields,
+ // and if corresponding fields have the same names, and identical types,
+ // and identical tags. Two anonymous fields are considered to have the same
+ // name. Lower-case field names from different packages are always different.
+ if y, ok := y.(*Struct); ok {
+ if len(x.Fields) == len(y.Fields) {
+ for i, f := range x.Fields {
+ g := y.Fields[i]
+ if !f.QualifiedName.IsSame(g.QualifiedName) ||
+ !isIdentical(f.Type, g.Type) ||
+ f.Tag != g.Tag ||
+ f.IsAnonymous != g.IsAnonymous {
+ return false
+ }
+ }
+ return true
+ }
+ }
+
+ case *Pointer:
+ // Two pointer types are identical if they have identical base types.
+ if y, ok := y.(*Pointer); ok {
+ return isIdentical(x.Base, y.Base)
+ }
+
+ case *Signature:
+ // Two function types are identical if they have the same number of parameters
+ // and result values, corresponding parameter and result types are identical,
+ // and either both functions are variadic or neither is. Parameter and result
+ // names are not required to match.
+ if y, ok := y.(*Signature); ok {
+ return identicalTypes(x.Params, y.Params) &&
+ identicalTypes(x.Results, y.Results) &&
+ x.IsVariadic == y.IsVariadic
+ }
+
+ case *Interface:
+ // Two interface types are identical if they have the same set of methods with
+ // the same names and identical function types. Lower-case method names from
+ // different packages are always different. The order of the methods is irrelevant.
+ if y, ok := y.(*Interface); ok {
+ return identicalMethods(x.Methods, y.Methods) // methods are sorted
+ }
+
+ case *Map:
+ // Two map types are identical if they have identical key and value types.
+ if y, ok := y.(*Map); ok {
+ return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
+ }
+
+ case *Chan:
+ // Two channel types are identical if they have identical value types
+ // and the same direction.
+ if y, ok := y.(*Chan); ok {
+ return x.Dir == y.Dir && isIdentical(x.Elt, y.Elt)
+ }
+
+ case *NamedType:
+ // Two named types are identical if their type names originate
+ // in the same type declaration.
+ if y, ok := y.(*NamedType); ok {
+ return x.Obj == y.Obj
+ }
+
+ //=== start custom
+ case *View:
+ // Two map types are identical if they have identical key and value types.
+ if y, ok := y.(*View); ok {
+ return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
+ }
+
+ case *Table:
+ // Two map types are identical if they have identical key and value types.
+ if y, ok := y.(*Table); ok {
+ return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
+ }
+ //=== end custom
+
+ }
+
+ return false
}
-*/
// identicalTypes returns true if both lists a and b have the
// same length and corresponding objects have identical types.
diff --git a/lang/types/predicates_sx.go b/lang/types/predicates_sx.go
deleted file mode 100644
index c1746b1..0000000
--- a/lang/types/predicates_sx.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package types
-
-// see predicates.go:88
-func hasNil(typ Type) bool {
- switch underlying(typ).(type) {
- case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
- return true
-
- //=== start custom
- case *View, *Table:
- return true
- //=== end custom
-
- }
- return false
-}
-
-// identical returns true if x and y are identical.
-//
-// see predicates.go:99
-func isIdentical(x, y Type) bool {
- if x == y {
- return true
- }
-
- switch x := x.(type) {
- case *Basic:
- // Basic types are singletons except for the rune and byte
- // aliases, thus we cannot solely rely on the x == y check
- // above.
- if y, ok := y.(*Basic); ok {
- return x.Kind == y.Kind
- }
-
- case *Array:
- // Two array types are identical if they have identical element types
- // and the same array length.
- if y, ok := y.(*Array); ok {
- return x.Len == y.Len && isIdentical(x.Elt, y.Elt)
- }
-
- case *Slice:
- // Two slice types are identical if they have identical element types.
- if y, ok := y.(*Slice); ok {
- return isIdentical(x.Elt, y.Elt)
- }
-
- case *Struct:
- // Two struct types are identical if they have the same sequence of fields,
- // and if corresponding fields have the same names, and identical types,
- // and identical tags. Two anonymous fields are considered to have the same
- // name. Lower-case field names from different packages are always different.
- if y, ok := y.(*Struct); ok {
- if len(x.Fields) == len(y.Fields) {
- for i, f := range x.Fields {
- g := y.Fields[i]
- if !f.QualifiedName.IsSame(g.QualifiedName) ||
- !isIdentical(f.Type, g.Type) ||
- f.Tag != g.Tag ||
- f.IsAnonymous != g.IsAnonymous {
- return false
- }
- }
- return true
- }
- }
-
- case *Pointer:
- // Two pointer types are identical if they have identical base types.
- if y, ok := y.(*Pointer); ok {
- return isIdentical(x.Base, y.Base)
- }
-
- case *Signature:
- // Two function types are identical if they have the same number of parameters
- // and result values, corresponding parameter and result types are identical,
- // and either both functions are variadic or neither is. Parameter and result
- // names are not required to match.
- if y, ok := y.(*Signature); ok {
- return identicalTypes(x.Params, y.Params) &&
- identicalTypes(x.Results, y.Results) &&
- x.IsVariadic == y.IsVariadic
- }
-
- case *Interface:
- // Two interface types are identical if they have the same set of methods with
- // the same names and identical function types. Lower-case method names from
- // different packages are always different. The order of the methods is irrelevant.
- if y, ok := y.(*Interface); ok {
- return identicalMethods(x.Methods, y.Methods) // methods are sorted
- }
-
- case *Map:
- // Two map types are identical if they have identical key and value types.
- if y, ok := y.(*Map); ok {
- return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
- }
-
- case *Chan:
- // Two channel types are identical if they have identical value types
- // and the same direction.
- if y, ok := y.(*Chan); ok {
- return x.Dir == y.Dir && isIdentical(x.Elt, y.Elt)
- }
-
- case *NamedType:
- // Two named types are identical if their type names originate
- // in the same type declaration.
- if y, ok := y.(*NamedType); ok {
- return x.Obj == y.Obj
- }
-
- //=== start custom
- case *View:
- // Two map types are identical if they have identical key and value types.
- if y, ok := y.(*View); ok {
- return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
- }
-
- case *Table:
- // Two map types are identical if they have identical key and value types.
- if y, ok := y.(*Table); ok {
- return isIdentical(x.Key, y.Key) && isIdentical(x.Elt, y.Elt)
- }
- //=== end custom
-
- }
-
- return false
-}
diff --git a/lang/types/types.go b/lang/types/types.go
index 0791f60..bc2aa68 100644
--- a/lang/types/types.go
+++ b/lang/types/types.go
@@ -229,3 +229,30 @@ func (*Interface) aType() {}
func (*Map) aType() {}
func (*Chan) aType() {}
func (*NamedType) aType() {}
+
+// ==================================================================
+// == Simplex Views and Tables
+
+// A View represents a view type view[Key]Elt.
+type View struct {
+ Key, Elt Type
+}
+
+// A View represents a table type table[Key]Elt.
+type Table struct {
+ Key, Elt Type
+}
+
+type Viewish interface {
+ KeyType() Type
+ EltType() Type
+}
+
+func (v *View) KeyType() Type { return v.Key }
+func (v *Table) KeyType() Type { return v.Key }
+
+func (v *View) EltType() Type { return v.Elt }
+func (v *Table) EltType() Type { return v.Elt }
+
+func (*View) aType() {}
+func (*Table) aType() {}
diff --git a/lang/types/types_sx.go b/lang/types/types_sx.go
deleted file mode 100644
index af234e4..0000000
--- a/lang/types/types_sx.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package types
-
-// A View represents a view type view[Key]Elt.
-type View struct {
- Key, Elt Type
-}
-
-// A View represents a table type table[Key]Elt.
-type Table struct {
- Key, Elt Type
-}
-
-type Viewish interface {
- KeyType() Type
- EltType() Type
-}
-
-func (v *View) KeyType() Type { return v.Key }
-func (v *Table) KeyType() Type { return v.Key }
-
-func (v *View) EltType() Type { return v.Elt }
-func (v *Table) EltType() Type { return v.Elt }
-
-func (*View) aType() {}
-func (*Table) aType() {}
diff --git a/lang/types/universe.go b/lang/types/universe.go
index a95feb4..9aa886b 100644
--- a/lang/types/universe.go
+++ b/lang/types/universe.go
@@ -59,6 +59,7 @@ var predeclaredConstants = [...]*Const{
{"false", Typ[UntypedBool], false, nil},
{"iota", Typ[UntypedInt], zeroConst, nil},
{"nil", Typ[UntypedNil], nilConst, nil},
+ {"SxVersion", Typ[UntypedString], "", nil},
}
var predeclaredFunctions = [...]*builtin{
@@ -128,7 +129,7 @@ func def(obj Object) {
}
// exported identifiers go into package unsafe
scope := Universe
- if ast.IsExported(name) {
+ if ast.IsExported(name) && name != "SxVersion" {
scope = Unsafe.Scope
}
if scope.Insert(obj) != nil {
diff --git a/net/http/api/api.go b/net/http/api/api.go
index a1ab94c..2d155bd 100644
--- a/net/http/api/api.go
+++ b/net/http/api/api.go
@@ -10,9 +10,6 @@ import (
"simplex.sh/cas"
"simplex.sh/cas/btree"
"simplex.sh/runtime"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
- "strings"
)
type (
@@ -20,7 +17,7 @@ type (
name string
env *runtime.Environment
tables map[string]runtime.Table
- views map[string]promise.Deferred
+ views map[string]runtime.Resolver
routes map[string]string
ViewTables map[string]*table_handle
@@ -37,7 +34,7 @@ func New(env *runtime.Environment, name string) *API {
name,
env,
map[string]runtime.Table{},
- map[string]promise.Deferred{},
+ map[string]runtime.Resolver{},
map[string]string{},
map[string]*table_handle{},
}
@@ -116,42 +113,26 @@ func (api *API) DeferredId() string {
return "API/" + api.name
}
-func (api *API) Resolve(state promise.State, events chan<- event.Event) {
- var (
- funnel event.Funnel
- )
+func (api *API) Resolve(state *runtime.Transaction) runtime.IChange {
for _, table := range api.tables {
- funnel.Add(state.Resolve(table).C)
+ state.Resolve(table)
}
- for _, view := range api.views {
- funnel.Add(state.Resolve(view).C)
- }
-
- for e := range funnel.Run() {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
+ for name, view := range api.views {
+ var (
+ change = state.Resolve(view)
+ )
- event, ok := e.(*runtime.ConsistentTable)
- if !ok {
- continue
- }
-
- if strings.HasPrefix(event.Table, "API/FORMAT_JSON/") {
- name := event.Table[len("API/FORMAT_JSON/"):]
-
- if event.B == nil {
- delete(api.ViewTables, name)
- } else {
- api.ViewTables[name] = &table_handle{addr: event.B}
- }
+ switch change.Type() {
+ case runtime.ChangeRemove:
+ delete(api.ViewTables, name)
+ case runtime.ChangeInsert, runtime.ChangeUpdate:
+ api.ViewTables[name] = &table_handle{addr: change.B}
}
}
+ return runtime.IChange{}
}
func (api *API) ServeHTTP(w http.ResponseWriter, req *http.Request) {
diff --git a/runtime/change.go b/runtime/change.go
new file mode 100644
index 0000000..b34a6f9
--- /dev/null
+++ b/runtime/change.go
@@ -0,0 +1,103 @@
+package runtime
+
+import (
+ "bytes"
+ "simplex.sh/cas"
+ "sort"
+ "sync"
+)
+
+type (
+ ChangeType int8
+
+ IChange struct {
+ A cas.Addr
+ B cas.Addr
+ MemberChanges []MemberChange
+
+ Err error
+ Stack []byte
+
+ mutex sync.Mutex
+ }
+
+ MemberChange struct {
+ CollatedKey []byte
+ Key cas.Addr
+ IChange
+ }
+)
+
+const (
+ ChangeNone ChangeType = iota
+ ChangeInsert
+ ChangeUpdate
+ ChangeRemove
+)
+
+func (c IChange) Type() ChangeType {
+ if c.A == nil && c.B == nil {
+ return ChangeNone
+ }
+
+ if c.A == nil {
+ return ChangeInsert
+ }
+
+ if c.B == nil {
+ return ChangeRemove
+ }
+
+ if bytes.Compare(c.A, c.B) == 0 {
+ return ChangeNone
+ }
+
+ return ChangeUpdate
+}
+
+func (c *IChange) MemberChanged(collated_key []byte, key_addr cas.Addr, ichange IChange) {
+ change := MemberChange{
+ CollatedKey: collated_key,
+ Key: key_addr,
+ IChange: ichange,
+ }
+
+ if change.Type() == ChangeNone {
+ return
+ }
+
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+
+ i := sort.Search(len(c.MemberChanges), func(i int) bool {
+ return bytes.Compare(c.MemberChanges[i].CollatedKey, change.CollatedKey) != -1
+ })
+
+ if cap(c.MemberChanges) < len(c.MemberChanges)+1 {
+ src := c.MemberChanges
+ dst := make([]MemberChange, len(c.MemberChanges), cap(c.MemberChanges)+100)
+ copy(dst, src)
+ c.MemberChanges = dst
+ }
+
+ // is append
+ if i >= len(c.MemberChanges) {
+ c.MemberChanges = append(c.MemberChanges, change)
+ return
+ }
+
+ if bytes.Compare(c.MemberChanges[i].CollatedKey, change.CollatedKey) == 0 {
+ panic("already record change for collated_key")
+ }
+
+ // make room
+ dst := c.MemberChanges[:len(c.MemberChanges)+1]
+ if i > 0 {
+ copy(dst, c.MemberChanges[:i])
+ }
+ copy(dst[i+1:], c.MemberChanges[i:])
+ c.MemberChanges = dst
+
+ // set change
+ c.MemberChanges[i] = change
+}
diff --git a/runtime/event/dispatcher.go b/runtime/event/dispatcher.go
deleted file mode 100644
index 825201a..0000000
--- a/runtime/event/dispatcher.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package event
-
-import (
- "runtime"
- "sync"
-)
-
-type (
- Dispatcher struct {
- operations chan interface{}
- exchanges map[string]*exchange
- }
-
- Subscription struct {
- C <-chan Event
-
- exchange *exchange
- outbound chan Event
- cursor int
- }
-
- exchange struct {
- name string
- broken bool
- inbound chan Event
- log []Event
- rw_mtx *sync.RWMutex
- cond *sync.Cond
- }
-
- disp_op__register struct {
- name string
- reply chan chan<- Event
- }
-
- disp_op__subscribe struct {
- name string
- reply chan *exchange
- }
-
- disp_op__stop struct {
- }
-)
-
-func (disp *Dispatcher) Start() {
- if disp.operations == nil {
- disp.operations = make(chan interface{}, 1)
- disp.exchanges = make(map[string]*exchange)
- go disp.go_run()
- }
-}
-
-func (disp *Dispatcher) Stop() {
- disp.operations <- &disp_op__stop{}
-}
-
-func (disp *Dispatcher) Subscribe(name string) *Subscription {
- reply := make(chan *exchange, 1)
- disp.operations <- &disp_op__subscribe{name, reply}
- exch := <-reply
-
- out := make(chan Event, 1)
- sub := &Subscription{
- C: out,
- outbound: out,
- exchange: exch,
- }
-
- go sub.go_run()
-
- return sub
-}
-
-// Returns a named channel.
-func (disp *Dispatcher) Register(name string) chan<- Event {
- reply := make(chan chan<- Event, 1)
- disp.operations <- &disp_op__register{name, reply}
- return <-reply
-}
-
-func (disp *Dispatcher) register(name string) *exchange {
- e := disp.exchanges[name]
- if e != nil {
- return e
- }
-
- rw_mtx := &sync.RWMutex{}
- e = &exchange{
- name: name,
- inbound: make(chan Event, 1),
- rw_mtx: rw_mtx,
- cond: sync.NewCond(rw_mtx),
- }
-
- disp.exchanges[name] = e
-
- go e.go_run()
-
- return e
-}
-
-func (disp *Dispatcher) go_run() {
- for op := range disp.operations {
- switch o := op.(type) {
-
- case *disp_op__register:
- o.reply <- disp.register(o.name).inbound
- close(o.reply)
-
- case *disp_op__subscribe:
- e := disp.register(o.name)
- o.reply <- e
-
- case *disp_op__stop:
- close(disp.operations)
- for _, e := range disp.exchanges {
- e.break_exchange()
- }
- return
-
- }
- }
-}
-
-func (sub *Subscription) go_run() {
- for {
- closed, broken := sub.pop_events()
-
- if closed || broken {
- break
- }
- }
-
- ensure_closed(sub.outbound)
-}
-
-func (sub *Subscription) get_log() (log []Event, closed, broken bool) {
- sub.exchange.cond.L.Lock()
- defer sub.exchange.cond.L.Unlock()
- // wait for event
- for sub.cursor == len(sub.exchange.log) && sub.exchange.inbound != nil && !sub.exchange.broken {
- sub.exchange.cond.Wait()
- }
-
- return sub.exchange.log, sub.exchange.inbound == nil, sub.exchange.broken
-}
-
-func (sub *Subscription) pop_events() (closed, broken bool) {
- var (
- log []Event
- )
-
- log, closed, broken = sub.get_log()
-
- for sub.cursor < len(log) {
- runtime.Gosched()
- event := log[sub.cursor]
- if closed || broken {
- sub.outbound <- event
- sub.cursor += 1
- } else {
- select {
- case sub.outbound <- event:
- sub.cursor += 1
- default:
- closed = false
- broken = false
- return
- }
- }
- }
-
- return
-}
-
-func (exch *exchange) push_event(event Event) {
- exch.cond.L.Lock()
- defer exch.cond.L.Unlock()
-
- exch.log = append(exch.log, event)
-
- exch.cond.Broadcast()
-}
-
-func (exch *exchange) break_exchange() {
- exch.cond.L.Lock()
- defer exch.cond.L.Unlock()
-
- ensure_closed(exch.inbound)
- exch.inbound = nil // closed
- exch.broken = true
-
- exch.cond.Broadcast()
-}
-
-func (exch *exchange) close_exchange() {
- exch.cond.L.Lock()
- defer exch.cond.L.Unlock()
-
- ensure_closed(exch.inbound)
- exch.inbound = nil // closed
-
- exch.cond.Broadcast()
-}
-
-func (exch *exchange) go_run() {
- for event := range exch.inbound {
- exch.push_event(event)
- }
-
- exch.close_exchange()
-}
-
-func ensure_closed(c chan Event) {
- defer func() { recover() }()
- close(c)
-}
diff --git a/runtime/event/funnel.go b/runtime/event/funnel.go
deleted file mode 100644
index dacb358..0000000
--- a/runtime/event/funnel.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package event
-
-import (
- "sync"
-)
-
-type (
- Funnel struct {
- inbound []<-chan Event
- outbound <-chan Event
- }
-)
-
-func (f *Funnel) Add(ch <-chan Event) {
- f.inbound = append(f.inbound, ch)
-}
-
-func (f *Funnel) Run() <-chan Event {
- if f.outbound != nil {
- return f.outbound
- }
-
- if len(f.inbound) == 0 {
- collector := make(chan Event, 1)
- f.outbound = collector
- close(collector)
- return f.outbound
- }
-
- if len(f.inbound) == 1 {
- f.outbound = f.inbound[0]
- return f.outbound
- }
-
- collector := make(chan Event, 1)
- f.outbound = collector
-
- go f.go_sink(collector)
-
- return f.outbound
-}
-
-func (f *Funnel) go_sink(collector chan Event) {
- var wg sync.WaitGroup
- wg.Add(len(f.inbound))
-
- defer close(collector)
-
- for _, ch := range f.inbound {
- go f.go_collect(&wg, collector, ch)
- }
-
- wg.Wait()
-}
-
-func (f *Funnel) go_collect(wg *sync.WaitGroup, collector chan Event, ch <-chan Event) {
- defer wg.Done()
-
- for e := range ch {
- collector <- e
- }
-}
diff --git a/runtime/event/types.go b/runtime/event/types.go
deleted file mode 100644
index b437457..0000000
--- a/runtime/event/types.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package event
-
-type (
- Event interface {
- Event() string
- }
-
- Error interface {
- Event
- error
- }
-
- error_event struct{ Err error }
-)
-
-func NewError(err error) Error {
- return error_event{err}
-}
-
-func (err error_event) Event() string {
- return err.Error()
-}
-
-func (err error_event) Error() string {
- return err.Err.Error()
-}
diff --git a/runtime/events.go b/runtime/events.go
deleted file mode 100644
index b2da8a8..0000000
--- a/runtime/events.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package runtime
-
-import (
- "fmt"
- "simplex.sh/cas"
-)
-
-type (
- ev_DONE_pool struct {
- p *worker_pool_t
- }
-
- WorkerError struct {
- w *worker_t
- data interface{}
- err error
- caller []byte
- }
-
- // a unit of progres from a -> b
- // representing a changing key/value
- // a is ZeroSHA when adding the key
- // b is ZeroSHA when remove the key
- ChangedMember struct {
- table string
- collated_key []byte
- key cas.Addr
- a cas.Addr
- b cas.Addr
- }
-
- // a unit of progres from a -> b
- // representing a changing table
- // a is ZeroSHA when adding the table
- // b is ZeroSHA when remove the table
- ConsistentTable struct {
- Table string
- A cas.Addr
- B cas.Addr
- }
-)
-
-func (*ev_DONE_pool) isEvent() {}
-func (e *WorkerError) Event() string { return e.Error() }
-func (e *ChangedMember) Event() string {
- return fmt.Sprintf(
- "ChangedMember(table: %s, member: %s)",
- e.table, e.collated_key,
- )
-}
-func (e *ConsistentTable) Event() string {
- return fmt.Sprintf(
- "Consistent(table: %s)",
- e.Table,
- )
-}
-
-func (e *WorkerError) Error() string { return fmt.Sprintf("%s: %s\n%s", e.w, e.err, e.caller) }
diff --git a/runtime/methods.go b/runtime/methods.go
index 0e255ee..f9ca888 100644
--- a/runtime/methods.go
+++ b/runtime/methods.go
@@ -2,11 +2,9 @@ package runtime
import (
"simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
)
-func DeclareTable(name string) promise.Deferred {
+func DeclareTable(name string) Resolver {
return &table_op{name}
}
@@ -14,7 +12,7 @@ func DeclareTable(name string) promise.Deferred {
type V view[]M
V.select(func(M)bool) -> V
*/
-func Select(v IndexedView, f select_func, name string) promise.Deferred {
+func Select(v IndexedView, f select_func, name string) Resolver {
return &select_op{src: v, fun: f, name: name}
}
@@ -22,7 +20,7 @@ func Select(v IndexedView, f select_func, name string) promise.Deferred {
type V view[]M
V.reject(func(M)bool) -> V
*/
-func Reject(v IndexedView, f reject_func, name string) promise.Deferred {
+func Reject(v IndexedView, f reject_func, name string) Resolver {
return &reject_op{src: v, fun: f, name: name}
}
@@ -42,7 +40,7 @@ func Detect(v IndexedView, f func(interface{}) bool, name string) interface{} {
V.collect(func(M)N) -> W
(Note: the key type remains unchanged)
*/
-func Collect(v IndexedView, f collect_func, name string) promise.Deferred {
+func Collect(v IndexedView, f collect_func, name string) Resolver {
return &collect_op{src: v, fun: f, name: name}
}
@@ -62,7 +60,7 @@ func Inject(v IndexedView, f func(interface{}, []interface{}) interface{}, name
V.group(func(M)N) -> W
(Note: the key type of the inner view remains unchanged)
*/
-func Group(v IndexedView, f group_func, name string) promise.Deferred {
+func Group(v IndexedView, f group_func, name string) Resolver {
return &group_op{src: v, fun: f, name: name}
}
@@ -74,7 +72,7 @@ func Group(v IndexedView, f group_func, name string) promise.Deferred {
v.index(f) is equivalent to v.group(f).collect(func(v view[]M)M{ return v.detect(func(_){return true}) })
*/
-func Index(v IndexedView, f index_func, name string) promise.Deferred {
+func Index(v IndexedView, f index_func, name string) Resolver {
return &index_op{src: v, fun: f, name: name}
}
@@ -83,11 +81,11 @@ func Index(v IndexedView, f index_func, name string) promise.Deferred {
V.sort(func(M)N) -> V
(Note: the key type is lost)
*/
-func Sort(v IndexedView, f sort_func, name string) promise.Deferred {
+func Sort(v IndexedView, f sort_func, name string) Resolver {
return &sort_op{src: v, fun: f, name: name}
}
-func Union(v ...promise.Deferred) promise.Deferred {
+func Union(v ...Resolver) Resolver {
panic("not yet implemented")
}
@@ -147,4 +145,4 @@ func (op *group_op) DeferredId() string { return op.name }
func (op *index_op) DeferredId() string { return op.name }
func (op *sort_op) DeferredId() string { return op.name }
-func (op *index_op) Resolve(state promise.State, events chan<- event.Event) {}
+func (op *index_op) Resolve(txn *Transaction) IChange { return IChange{} }
diff --git a/runtime/methods_collect.go b/runtime/methods_collect.go
index 1736440..06d009c 100644
--- a/runtime/methods_collect.go
+++ b/runtime/methods_collect.go
@@ -1,56 +1,43 @@
package runtime
-import (
- "simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
-)
-
-func (op *collect_op) Resolve(state promise.State, events chan<- event.Event) {
+func (op *collect_op) Resolve(state *Transaction) IChange {
var (
- src_events = state.Resolve(op.src)
- table = state.GetTable(op.name)
+ i_change = state.Resolve(op.src)
+ o_change = IChange{}
)
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
+ if i_change.Type() == ChangeNone {
+ return o_change
+ }
- i_change, ok := e.(*ChangedMember)
- if !ok {
- continue
- }
+ var (
+ table = state.GetTable(op.name)
+ )
+
+ for _, m := range i_change.MemberChanges {
+ switch m.Type() {
- // removed
- if i_change.b == nil {
- prev_key_addr, prev_elt_addr, err := table.Del(i_change.collated_key)
+ case ChangeRemove:
+ _, prev_elt_addr, err := table.Del(m.CollatedKey)
if err != nil {
panic("runtime: " + err.Error())
}
- if prev_key_addr != nil && prev_elt_addr != nil {
- events <- &ChangedMember{op.name, i_change.collated_key, prev_key_addr, prev_elt_addr, nil}
- }
-
- continue
- }
+ o_change.MemberChanged(m.CollatedKey, m.Key, IChange{A: prev_elt_addr, B: nil})
- { // added or updated
- curr_elt_addr := op.fun(&Context{state.Store()}, i_change.b)
+ case ChangeUpdate, ChangeInsert:
+ curr_elt_addr := op.fun(&Context{state.Store()}, m.B)
- prev_elt_addr, err := table.Set(i_change.collated_key, i_change.key, curr_elt_addr)
+ prev_elt_addr, err := table.Set(m.CollatedKey, m.Key, curr_elt_addr)
if err != nil {
panic("runtime: " + err.Error())
}
- if cas.CompareAddr(prev_elt_addr, curr_elt_addr) != 0 {
- events <- &ChangedMember{op.name, i_change.collated_key, i_change.key, prev_elt_addr, curr_elt_addr}
- }
+
+ o_change.MemberChanged(m.CollatedKey, m.Key, IChange{A: prev_elt_addr, B: curr_elt_addr})
+
}
}
- tab_addr_a, tab_addr_b := state.CommitTable(op.name, table)
- events <- &ConsistentTable{op.name, tab_addr_a, tab_addr_b}
+ o_change.A, o_change.B = state.CommitTable(op.name, table)
+ return o_change
}
diff --git a/runtime/methods_group.go b/runtime/methods_group.go
index 752bca3..1ad394f 100644
--- a/runtime/methods_group.go
+++ b/runtime/methods_group.go
@@ -1,59 +1,5 @@
package runtime
-import (
- "bytes"
- "simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
-)
-
-func (op *group_op) Resolve(state promise.State, events chan<- event.Event) {
- var (
- src_events = state.Resolve(op.src)
- table = state.GetTable(op.name)
- )
-
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
-
- i_change, ok := e.(*ChangedMember)
- if !ok {
- continue
- }
-
- var (
- coll_key_a []byte
- coll_key_b []byte
- key_b interface{}
- )
-
- // calculate collated group key for a and b
- if i_change.a != nil {
- group_key := op.fun(&Context{state.Store()}, i_change.a)
- coll_key_a = cas.Collate(group_key)
- }
- if i_change.b != nil {
- key_b = op.fun(&Context{state.Store()}, i_change.b)
- coll_key_b = cas.Collate(key_b)
- }
-
- // propagate event
- // - to sub table at coll_key_b
- // - to groups table
- if bytes.Compare(coll_key_a, coll_key_b) == 0 {
- continue
- }
-
- // remove old entry from sub table
- // add new entry to sub table (while potentially adding new subtables)
- }
-
- // remove empty sub tables
-
- tab_addr_a, tab_addr_b := state.CommitTable(op.name, table)
- events <- &ConsistentTable{op.name, tab_addr_a, tab_addr_b}
+func (op *group_op) Resolve(state *Transaction) IChange {
+ return IChange{}
}
diff --git a/runtime/methods_select.go b/runtime/methods_select.go
index a61c1bb..c3ad9de 100644
--- a/runtime/methods_select.go
+++ b/runtime/methods_select.go
@@ -1,100 +1,88 @@
package runtime
-import (
- "simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
-)
-
-func (op *select_op) Resolve(state promise.State, events chan<- event.Event) {
- var (
- src_events = state.Resolve(op.src)
- fun = op.fun
+func (op *select_op) Resolve(state *Transaction) IChange {
+ return apply_select_reject_filter(
+ op.src,
+ op.name,
+ op.fun,
+ true,
+ state,
)
+}
- apply_select_reject_filter(op.name, fun, true, src_events, events, state)
+func (op *reject_op) Resolve(state *Transaction) IChange {
+ return apply_select_reject_filter(
+ op.src,
+ op.name,
+ select_func(op.fun),
+ false,
+ state,
+ )
}
-func (op *reject_op) Resolve(state promise.State, events chan<- event.Event) {
+func apply_select_reject_filter(r Resolver, op_name string, op_fun select_func,
+ expected bool, state *Transaction) IChange {
+
var (
- src_events = state.Resolve(op.src)
- fun = select_func(op.fun)
+ i_change = state.Resolve(r)
+ o_change IChange
)
- apply_select_reject_filter(op.name, fun, false, src_events, events, state)
-}
-
-func apply_select_reject_filter(op_name string, op_fun select_func,
- expected bool, src_events *event.Subscription, dst_events chan<- event.Event,
- state promise.State) {
+ if i_change.Type() == ChangeNone {
+ return o_change
+ }
var (
table = state.GetTable(op_name)
)
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- dst_events <- err
- continue
- }
-
- i_change, ok := e.(*ChangedMember)
- if !ok {
- continue
- }
+ for _, i_m := range i_change.MemberChanges {
var (
- o_change = &ChangedMember{op_name, i_change.collated_key, i_change.key, i_change.a, i_change.b}
+ o_m = i_m.IChange
)
- if o_change.a != nil {
- if op_fun(&Context{state.Store()}, o_change.a) != expected {
- o_change.a = nil
+ // was part of selection
+ if i_m.A != nil {
+ if op_fun(&Context{state.Store()}, i_m.A) != expected {
+ o_m.A = nil
}
}
- if o_change.b != nil {
- if op_fun(&Context{state.Store()}, o_change.b) != expected {
- o_change.b = nil
+ // will be part of selection
+ if i_m.B != nil {
+ if op_fun(&Context{state.Store()}, i_m.B) != expected {
+ o_m.B = nil
}
}
- // ignore unchanged data
- if o_change.a == nil && o_change.b == nil {
+ if o_m.Type() == ChangeNone {
continue
}
- if o_change.a != nil {
- // remove kv from table
- _, prev, err := table.Del(o_change.collated_key)
+ switch o_m.Type() {
+ case ChangeRemove:
+ _, prev_elt_addr, err := table.Del(i_m.CollatedKey)
if err != nil {
panic("runtime: " + err.Error())
}
- if prev != nil {
- o_change.a = nil
- }
- }
- if o_change.b != nil {
+ o_m.A = prev_elt_addr
+ o_change.MemberChanged(i_m.CollatedKey, i_m.Key, o_m)
+
+ case ChangeUpdate, ChangeInsert:
// insert kv into table
- prev, err := table.Set(o_change.collated_key, o_change.key, o_change.b)
+ prev_elt_addr, err := table.Set(i_m.CollatedKey, i_m.Key, o_m.B)
if err != nil {
panic("runtime: " + err.Error())
}
- if cas.CompareAddr(prev, o_change.b) == 0 {
- o_change.b = nil
- }
- }
- // ignore unchanged data
- if o_change.a == nil && o_change.b == nil {
- continue
- }
+ o_m.A = prev_elt_addr
+ o_change.MemberChanged(i_m.CollatedKey, i_m.Key, o_m)
- dst_events <- o_change
+ }
}
- tab_addr_a, tab_addr_b := state.CommitTable(op_name, table)
- dst_events <- &ConsistentTable{op_name, tab_addr_a, tab_addr_b}
+ o_change.A, o_change.B = state.CommitTable(op_name, table)
+ return o_change
}
diff --git a/runtime/methods_sort.go b/runtime/methods_sort.go
index b6c8a99..91592a2 100644
--- a/runtime/methods_sort.go
+++ b/runtime/methods_sort.go
@@ -3,28 +3,23 @@ package runtime
import (
"bytes"
"simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
)
-func (op *sort_op) Resolve(state promise.State, events chan<- event.Event) {
+func (op *sort_op) Resolve(state *Transaction) IChange {
var (
- src_events = state.Resolve(op.src)
- table = state.GetTable(op.name)
+ i_change = state.Resolve(op.src)
+ o_change IChange
)
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
+ if i_change.Type() == ChangeNone {
+ return o_change
+ }
- i_change, ok := e.(*ChangedMember)
- if !ok {
- continue
- }
+ var (
+ table = state.GetTable(op.name)
+ )
+ for _, m := range i_change.MemberChanges {
var (
coll_key_a []byte
coll_key_b []byte
@@ -32,13 +27,13 @@ func (op *sort_op) Resolve(state promise.State, events chan<- event.Event) {
)
// calculate collated sort key for a and b
- if i_change.a != nil {
- sort_key := op.fun(&Context{state.Store()}, i_change.a)
- coll_key_a = cas.Collate([]interface{}{sort_key, i_change.collated_key})
+ if m.A != nil {
+ sort_key := op.fun(&Context{state.Store()}, m.A)
+ coll_key_a = cas.Collate([]interface{}{sort_key, m.CollatedKey})
}
- if i_change.b != nil {
- sort_key := op.fun(&Context{state.Store()}, i_change.b)
- key_b = []interface{}{sort_key, i_change.collated_key}
+ if m.B != nil {
+ sort_key := op.fun(&Context{state.Store()}, m.B)
+ key_b = []interface{}{sort_key, m.CollatedKey}
coll_key_b = cas.Collate(key_b)
}
@@ -49,35 +44,41 @@ func (op *sort_op) Resolve(state promise.State, events chan<- event.Event) {
panic("runtime: " + err.Error())
}
- events <- &ChangedMember{op.name, coll_key_b, key_addr, i_change.a, i_change.b}
+ prev_elt_addr, err := table.Set(coll_key_a, key_addr, m.B)
+ if err != nil {
+ panic("runtime: " + err.Error())
+ }
+
+ o_change.MemberChanged(coll_key_a, key_addr, IChange{A: prev_elt_addr, B: m.B})
continue
}
// remove old entry
- if i_change.a != nil {
- key_addr, elt_addr, err := table.Del(coll_key_a)
+ if m.A != nil {
+ _, prev_elt_addr, err := table.Del(coll_key_a)
if err != nil {
panic("runtime: " + err.Error())
}
- events <- &ChangedMember{op.name, coll_key_a, key_addr, elt_addr, nil}
+
+ o_change.MemberChanged(m.CollatedKey, m.Key, IChange{A: prev_elt_addr, B: nil})
}
// add new entry
- if i_change.b != nil {
+ if m.B != nil {
key_addr, err := cas.Encode(state.Store(), key_b, -1)
if err != nil {
panic("runtime: " + err.Error())
}
- prev_elt_addr, err := table.Set(coll_key_a, key_addr, i_change.b)
+ prev_elt_addr, err := table.Set(coll_key_b, key_addr, m.B)
if err != nil {
panic("runtime: " + err.Error())
}
- events <- &ChangedMember{op.name, coll_key_b, key_addr, prev_elt_addr, i_change.b}
+ o_change.MemberChanged(coll_key_b, key_addr, IChange{A: prev_elt_addr, B: m.B})
}
}
- tab_addr_a, tab_addr_b := state.CommitTable(op.name, table)
- events <- &ConsistentTable{op.name, tab_addr_a, tab_addr_b}
+ o_change.A, o_change.B = state.CommitTable(op.name, table)
+ return o_change
}
diff --git a/runtime/methods_table.go b/runtime/methods_table.go
index 77dd31b..467c9ad 100644
--- a/runtime/methods_table.go
+++ b/runtime/methods_table.go
@@ -2,19 +2,15 @@ package runtime
import (
"simplex.sh/cas"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
)
-func (op *table_op) Resolve(state promise.State, events chan<- event.Event) {
- table := state.GetTable(op.name)
+func (op *table_op) Resolve(state *Transaction) IChange {
+ var (
+ table = state.GetTable(op.name)
+ o_change IChange
+ )
- transaction, ok := state.(*Transaction)
- if !ok {
- panic("Expected state to be a transaction.")
- }
-
- for _, change := range transaction.changes {
+ for _, change := range state.changes {
if change.Table != op.name {
continue
}
@@ -46,32 +42,28 @@ func (op *table_op) Resolve(state promise.State, events chan<- event.Event) {
panic("runtime: " + err.Error())
}
- if cas.CompareAddr(prev_elt_addr, elt_addr) != 0 {
- events <- &ChangedMember{op.name, key_coll, key_addr, prev_elt_addr, elt_addr}
- }
+ o_change.MemberChanged(key_coll, key_addr, IChange{A: prev_elt_addr, B: elt_addr})
case UNSET:
var (
- key_coll []byte
- key_addr cas.Addr
- elt_addr cas.Addr
- err error
+ key_coll []byte
+ key_addr cas.Addr
+ prev_elt_addr cas.Addr
+ err error
)
key_coll = cas.Collate(change.Key)
- key_addr, elt_addr, err = table.Del(key_coll)
+ key_addr, prev_elt_addr, err = table.Del(key_coll)
if err != nil {
panic("runtime: " + err.Error())
}
- if key_addr != nil || elt_addr != nil {
- events <- &ChangedMember{op.name, key_coll, key_addr, elt_addr, nil}
- }
+ o_change.MemberChanged(key_coll, key_addr, IChange{A: prev_elt_addr, B: nil})
}
}
- tab_addr_a, tab_addr_b := state.CommitTable(op.name, table)
- events <- &ConsistentTable{op.name, tab_addr_a, tab_addr_b}
+ o_change.A, o_change.B = state.CommitTable(op.name, table)
+ return o_change
}
diff --git a/runtime/promise/type.go b/runtime/promise/type.go
deleted file mode 100644
index 41dde64..0000000
--- a/runtime/promise/type.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package promise
-
-import (
- "simplex.sh/cas"
- "simplex.sh/cas/btree"
- "simplex.sh/runtime/event"
-)
-
-type (
- Deferred interface {
- DeferredId() string
- Resolve(state State, events chan<- event.Event)
- }
-
- State interface {
- Store() cas.Store
-
- Resolve(Deferred) *event.Subscription
-
- GetTable(string) *btree.Tree
- CommitTable(string, *btree.Tree) (prev, curr cas.Addr)
- }
-)
diff --git a/runtime/terminal_dump.go b/runtime/terminal_dump.go
index bfa011a..d28cfc4 100644
--- a/runtime/terminal_dump.go
+++ b/runtime/terminal_dump.go
@@ -5,8 +5,6 @@ import (
"reflect"
"simplex.sh/cas"
"simplex.sh/cas/btree"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
)
func Dump(view IndexedView) {
@@ -21,67 +19,62 @@ func (t *dump_terminal) DeferredId() string {
return "dump(" + t.view.DeferredId() + ")"
}
-func (t *dump_terminal) Resolve(state promise.State, events chan<- event.Event) {
- src_events := state.Resolve(t.view)
+func (t *dump_terminal) Resolve(state *Transaction) IChange {
+ var (
+ i_change = state.Resolve(t.view)
+ )
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
+ if i_change.Type() == ChangeRemove {
+ return IChange{}
+ }
+
+ var (
+ table = GetTable(state.Store(), i_change.B)
+ iter = table.Iter()
+ keyed bool
+ key_typ reflect.Type
+ )
+
+ if kv, ok := t.view.(KeyedView); ok {
+ keyed = true
+ key_typ = kv.KeyType()
+ }
- event, ok := e.(*ConsistentTable)
- if !ok {
- continue
+ for {
+ key_addr, elt_addr, err := iter.Next()
+ if err == btree.EOI {
+ err = nil
+ break
+ }
+ if err != nil {
+ panic("runtime: " + err.Error())
}
var (
- table = GetTable(state.Store(), event.B)
- iter = table.Iter()
- keyed bool
- key_typ reflect.Type
+ key reflect.Value
+ elt reflect.Value
)
- if kv, ok := t.view.(KeyedView); ok {
- keyed = true
- key_typ = kv.KeyType()
- }
-
- for {
- key_addr, elt_addr, err := iter.Next()
- if err == btree.EOI {
- err = nil
- break
- }
+ if keyed {
+ key = reflect.New(key_typ)
+ err = cas.DecodeValue(state.Store(), key_addr, key)
if err != nil {
panic("runtime: " + err.Error())
}
+ }
- var (
- key reflect.Value
- elt reflect.Value
- )
-
- if keyed {
- key = reflect.New(key_typ)
- err = cas.DecodeValue(state.Store(), key_addr, key)
- if err != nil {
- panic("runtime: " + err.Error())
- }
- }
-
- elt = reflect.New(t.view.EltType())
- err = cas.DecodeValue(state.Store(), elt_addr, elt)
- if err != nil {
- panic("runtime: " + err.Error())
- }
+ elt = reflect.New(t.view.EltType())
+ err = cas.DecodeValue(state.Store(), elt_addr, elt)
+ if err != nil {
+ panic("runtime: " + err.Error())
+ }
- if keyed {
- fmt.Printf("V: %+v %+v\n", key.Interface(), elt.Interface())
- } else {
- fmt.Printf("V: %+v\n", elt.Interface())
- }
+ if keyed {
+ fmt.Printf("V: %+v %+v\n", key.Interface(), elt.Interface())
+ } else {
+ fmt.Printf("V: %+v\n", elt.Interface())
}
}
+
+ return IChange{}
}
diff --git a/runtime/terminal_void.go b/runtime/terminal_void.go
index 1fe97fc..3c34713 100644
--- a/runtime/terminal_void.go
+++ b/runtime/terminal_void.go
@@ -1,36 +1,22 @@
package runtime
-import (
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
-)
-
/*
Void() registers a side-effect free terminal. It is mainly useful for debugging
as it ensurs that the Deferred def is resolved.
*/
-func Void(def promise.Deferred) {
- Env.RegisterTerminal(&void_terminal{def})
+func Void(r Resolver) {
+ Env.RegisterTerminal(&void_terminal{r})
}
type void_terminal struct {
- def promise.Deferred
+ r Resolver
}
func (t *void_terminal) DeferredId() string {
- return "void(" + t.def.DeferredId() + ")"
+ return "void(" + t.r.DeferredId() + ")"
}
-func (t *void_terminal) Resolve(state promise.State, events chan<- event.Event) {
- src_events := state.Resolve(t.def)
-
- for e := range src_events.C {
- // propagate error events
- if err, ok := e.(event.Error); ok {
- events <- err
- continue
- }
-
- // ignore
- }
+func (t *void_terminal) Resolve(state *Transaction) IChange {
+ state.Resolve(t.r)
+ return IChange{}
}
diff --git a/runtime/tests/select_test.sx b/runtime/tests/select_test.sx
new file mode 100644
index 0000000..1d5eeae
--- /dev/null
+++ b/runtime/tests/select_test.sx
@@ -0,0 +1,33 @@
+package tests
+
+import (
+ "testing"
+)
+
+func TestSelect(t *testing.T) {
+
+ env := runtime.Env{}
+ tab := make(table[int]string, "test/table")
+ vie := tab.select(func(m string) bool {
+ return len(m) > 0 && m[0:1] == "b"
+ })
+
+ env.Store = redis.New("", 0, "")
+ txn := env.NewTransaction()
+ txn.Set("test/table", 1, "foo")
+ txn.Set("test/table", 2, "bar")
+ txn.Commit()
+
+ if s, ok := vie[1]; ok {
+ t.Error("vie[1] is not supposed to be present")
+ }
+
+ if s, ok := vie[2]; !ok {
+ t.Error("vie[2] is supposed to be present")
+
+ if s != "bar" {
+ t.Error("vie[2] is supposed to be `bar`")
+ }
+ }
+
+}
diff --git a/runtime/transaction.go b/runtime/transaction.go
index 3e81491..951b643 100644
--- a/runtime/transaction.go
+++ b/runtime/transaction.go
@@ -2,21 +2,21 @@ package runtime
import (
"fmt"
+ "runtime/debug"
"simplex.sh/cas"
"simplex.sh/cas/btree"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
+ "sync"
"time"
)
type (
Transaction struct {
- env *Environment
- changes []*Change
- tables *btree.Tree
- errors []interface{}
- pool *worker_pool_t
- dispatcher *event.Dispatcher
+ env *Environment
+ changes []*Change
+ tables *btree.Tree
+ errors []interface{}
+ broadcasters map[string]broadcaster
+ mutex sync.Mutex
// parent transaction
Parent cas.Addr
@@ -93,32 +93,19 @@ func (txn *Transaction) Commit() {
// wait for prev txn to resolve
- pool := &worker_pool_t{}
- disp := &event.Dispatcher{}
- txn.pool = pool
- txn.dispatcher = disp
+ promises := make([]broadcaster, len(txn.env.terminals))
- // start the workers
- disp.Start()
- pool.Start()
-
- var (
- event_collector event.Funnel
- )
-
- for _, t := range txn.env.terminals {
- pool.schedule(txn, t)
- event_collector.Add(disp.Subscribe(t.DeferredId()).C)
+ for i, t := range txn.env.terminals {
+ promises[i] = txn.GoResolve(t)
}
- for e := range event_collector.Run() {
- // handle events
- fmt.Printf("Ev (%T): %+v\n", e, e)
+ for _, b := range promises {
+ b <- <-b
}
- // wait for the workers to finish
- disp.Stop()
- pool.Stop()
+ for _, err := range txn.errors {
+ fmt.Printf("[E] %s\n", err)
+ }
// commit the _tables table
tables_addr, err := txn.tables.Commit()
@@ -186,13 +173,45 @@ func (txn *Transaction) CommitTable(name string, tree *btree.Tree) (prev, curr c
return prev_elt_addr, elt_addr
}
-func (txn *Transaction) Resolve(def promise.Deferred) *event.Subscription {
- if txn.pool == nil {
- panic("transaction has no running worker pool")
+func (txn *Transaction) Resolve(r Resolver) IChange {
+ b := txn.GoResolve(r)
+ c := <-b
+ b <- c
+ return c
+}
+
+func (txn *Transaction) GoResolve(r Resolver) broadcaster {
+ txn.mutex.Lock()
+ defer txn.mutex.Unlock()
+
+ if txn.broadcasters == nil {
+ txn.broadcasters = make(map[string]broadcaster, 50)
+ }
+
+ b := txn.broadcasters[r.DeferredId()]
+ if b != nil {
+ return b
}
- txn.pool.schedule(txn, def)
- return txn.dispatcher.Subscribe(def.DeferredId())
+ b = make(chan IChange, 1)
+ txn.broadcasters[r.DeferredId()] = b
+ go func() {
+ if e := recover(); e != nil {
+ if err, ok := e.(error); ok {
+ txn.errors = append(txn.errors, err)
+ b <- IChange{Err: err, Stack: debug.Stack()}
+ } else {
+ txn.errors = append(txn.errors, fmt.Errorf("panic: %+v", e))
+ b <- IChange{Err: fmt.Errorf("panic: %+v", e), Stack: debug.Stack()}
+ }
+ }
+
+ b <- r.Resolve(txn)
+ }()
+
+ return b
}
func (txn *Transaction) Store() cas.Store { return txn.env.Store }
+
+type broadcaster chan IChange
diff --git a/runtime/types.go b/runtime/types.go
index a75b8a0..7b28cb4 100644
--- a/runtime/types.go
+++ b/runtime/types.go
@@ -2,16 +2,20 @@ package runtime
import (
"reflect"
- "simplex.sh/runtime/promise"
)
type (
+ Resolver interface {
+ DeferredId() string
+ Resolve(*Transaction) IChange
+ }
+
Terminal interface {
- promise.Deferred
+ Resolver
}
Table interface {
- promise.Deferred
+ Resolver
TableId() string
KeyType() reflect.Type
@@ -19,14 +23,14 @@ type (
}
KeyedView interface {
- promise.Deferred
+ Resolver
KeyType() reflect.Type
EltType() reflect.Type
}
IndexedView interface {
- promise.Deferred
+ Resolver
EltType() reflect.Type
}
diff --git a/runtime/worker.go b/runtime/worker.go
deleted file mode 100644
index 5f49196..0000000
--- a/runtime/worker.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package runtime
-
-import (
- "fmt"
- "runtime/debug"
- "simplex.sh/runtime/event"
- "simplex.sh/runtime/promise"
- "sync"
-)
-
-type worker_t struct {
- txn *Transaction
- def promise.Deferred
-}
-
-func (w *worker_t) String() string {
- return "Worker(" + w.def.DeferredId() + ")"
-}
-
-func (w *worker_t) run(wg *sync.WaitGroup) {
- events := w.txn.dispatcher.Register(w.def.DeferredId())
-
- go w.go_resolve(events, wg)
-}
-
-func (w *worker_t) go_resolve(events chan<- event.Event, wg *sync.WaitGroup) {
- defer func() {
- if e := recover(); e != nil {
- if err, ok := e.(error); ok {
- events <- &WorkerError{w, e, err, debug.Stack()}
- } else {
- events <- &WorkerError{w, e, fmt.Errorf("error: %+v", e), debug.Stack()}
- }
- }
- close(events)
- wg.Done()
- }()
-
- w.def.Resolve(w.txn, events)
-}
diff --git a/runtime/worker_pool.go b/runtime/worker_pool.go
deleted file mode 100644
index 6f72a34..0000000
--- a/runtime/worker_pool.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package runtime
-
-import (
- "simplex.sh/runtime/promise"
- "sync"
-)
-
-type worker_pool_t struct {
- operations chan *schedule_worker_op
- wg *sync.WaitGroup
-}
-
-type schedule_worker_op struct {
- def promise.Deferred
- txn *Transaction
- reply chan bool
-}
-
-func (p *worker_pool_t) Start() {
- p.operations = make(chan *schedule_worker_op, 1)
- p.wg = &sync.WaitGroup{}
-
- go p.go_run()
-}
-
-func (p *worker_pool_t) Stop() {
- p.wg.Wait()
- close(p.operations)
-}
-
-func (p *worker_pool_t) go_run() {
- var (
- workers = map[string]bool{}
- wg = p.wg
- )
-
- for op := range p.operations {
-
- started := workers[op.def.DeferredId()]
- if !started {
- w := &worker_t{def: op.def, txn: op.txn}
- workers[op.def.DeferredId()] = true
-
- wg.Add(1)
- w.run(wg)
- }
- op.reply <- true
- close(op.reply)
-
- }
-}
-
-func (p *worker_pool_t) schedule(txn *Transaction, def promise.Deferred) {
- reply := make(chan bool, 1)
- p.operations <- &schedule_worker_op{
- txn: txn,
- def: def,
- reply: reply,
- }
- <-reply
-}