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 + + +} + +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) { + +} + +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 -}