Skip to content

Commit

Permalink
ruleguard: embed dsl/fluent dependency
Browse files Browse the repository at this point in the history
When importing "github.com/quasilyte/go-ruleguard/dsl/fluent",
use the embedded version so the binary does not depend on
that package during the run-time.

A simple `dslgen/dslgen.go` program writes all DSL sources
as byte slices and puts them in dslgen package. When we need
to create *types.Package out of them, we use generated byte
slices as a parser and typechecker input.

It's amusing that rules parser now has 3 different "importers".

Fixes #23

Signed-off-by: Iskander Sharipov <[email protected]>
  • Loading branch information
quasilyte committed Mar 18, 2020
1 parent dc8aae9 commit b00d7a7
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
3 changes: 3 additions & 0 deletions dslgen/dsl_sources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package dslgen

var Fluent = []byte("package fluent\n\n// Matcher is a main API group-level entry point.\n// It's used to define and configure the group rules.\n// It also represents a map of all rule-local variables.\ntype Matcher map[string]Var\n\n// Import loads given package path into a rule group imports table.\n//\n// That table is used during the rules compilation.\n//\n// The table has the following effect on the rules:\n//\t* For type expressions, it's used to resolve the\n//\t full package paths of qualified types, like `foo.Bar`.\n//\t If Import(`a/b/foo`) is called, `foo.Bar` will match\n//\t `a/b/foo.Bar` type during the pattern execution.\nfunc (m Matcher) Import(pkgPath string) {}\n\n// Match specifies a set of patterns that match a rule being defined.\n// Pattern matching succeeds if at least 1 pattern matches.\n//\n// If none of the given patterns matched, rule execution stops.\nfunc (m Matcher) Match(pattern string, alternatives ...string) Matcher {\n\treturn m\n}\n\n// Where applies additional constraint to a match.\n// If a given cond is not satisfied, a match is rejected and\n// rule execution stops.\nfunc (m Matcher) Where(cond bool) Matcher {\n\treturn m\n}\n\n// Report prints a message if associated rule match is successful.\n//\n// A message is a string that can contain interpolated expressions.\n// For every matched variable it's possible to interpolate\n// their printed representation into the message text with $<name>.\n// An entire match can be addressed with $$.\nfunc (m Matcher) Report(message string) Matcher {\n\treturn m\n}\n\n// Suggest assigns a quickfix suggestion for the matched code.\nfunc (m Matcher) Suggest(suggestion string) Matcher {\n\treturn m\n}\n\n// At binds the reported node to a named submatch.\n// If no explicit location is given, the outermost node ($$) is used.\nfunc (m Matcher) At(v Var) Matcher {\n\treturn m\n}\n\n// Var is a pattern variable that describes a named submatch.\ntype Var struct {\n\t// Pure reports whether expr matched by var is side-effect-free.\n\tPure bool\n\n\t// Const reports whether expr matched by var is a constant value.\n\tConst bool\n\n\t// Addressable reports whether the corresponding expression is addressable.\n\t// See https://golang.org/ref/spec#Address_operators.\n\tAddressable bool\n\n\t// Type is a type of a matched expr.\n\tType ExprType\n}\n\n// ExprType describes a type of a matcher expr.\ntype ExprType struct {\n\t// Size represents expression type size in bytes.\n\tSize int\n}\n\n// AssignableTo reports whether a type is assign-compatible with a given type.\n// See https://golang.org/pkg/go/types/#AssignableTo.\nfunc (ExprType) AssignableTo(typ string) bool { return boolResult }\n\n// ConvertibleTo reports whether a type is conversible to a given type.\n// See https://golang.org/pkg/go/types/#ConvertibleTo.\nfunc (ExprType) ConvertibleTo(typ string) bool { return boolResult }\n\n// Implements reports whether a type implements a given interface.\n// See https://golang.org/pkg/go/types/#Implements.\nfunc (ExprType) Implements(typ string) bool { return boolResult }\n\n// Is reports whether a type is identical to a given type.\nfunc (ExprType) Is(typ string) bool { return boolResult }\n\n\n\nvar boolResult bool\n\n")
53 changes: 53 additions & 0 deletions dslgen/dslgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// +build generate

package main

import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)

func main() {
// See #23.

data, err := dirToBytes("../dsl/fluent")
if err != nil {
panic(err)
}

f, err := os.Create("./dsl_sources.go")
if err != nil {
panic(err)
}
defer f.Close()

fmt.Fprintf(f, `package dslgen
var Fluent = []byte(%q)
`, string(data))
}

func dirToBytes(dir string) ([]byte, error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}

var buf bytes.Buffer
for i, f := range files {
data, err := ioutil.ReadFile(filepath.Join(dir, f.Name()))
if err != nil {
return nil, err
}
if i != 0 {
newline := bytes.IndexByte(data, '\n')
data = data[newline:]
}
buf.Write(data)
buf.WriteByte('\n')
}
return buf.Bytes(), nil
}
40 changes: 40 additions & 0 deletions ruleguard/dsl_importer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ruleguard

import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"

"github.com/quasilyte/go-ruleguard/dslgen"
)

type dslImporter struct {
fallback types.Importer
}

func newDSLImporter() *dslImporter {
return &dslImporter{fallback: importer.Default()}
}

func (i *dslImporter) Import(path string) (*types.Package, error) {
switch path {
case "github.com/quasilyte/go-ruleguard/dsl/fluent":
return i.importDSL(path, dslgen.Fluent)

default:
return i.fallback.Import(path)
}
}

func (i *dslImporter) importDSL(path string, src []byte) (*types.Package, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "dsl.go", src, 0)
if err != nil {
return nil, err
}
var typecheker types.Config
var info types.Info
return typecheker.Check(path, fset, []*ast.File{f}, &info)
}
4 changes: 3 additions & 1 deletion ruleguard/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type rulesParser struct {
types *types.Info

itab *typematch.ImportsTab
dslImporter types.Importer
stdImporter types.Importer // TODO(quasilyte): share importer with gogrep?
srcImporter types.Importer
}
Expand Down Expand Up @@ -176,6 +177,7 @@ func newRulesParser() *rulesParser {
itab: typematch.NewImportsTab(stdlib),
stdImporter: importer.Default(),
srcImporter: importer.ForCompiler(fset, "source", nil),
dslImporter: newDSLImporter(),
}
}

Expand All @@ -196,7 +198,7 @@ func (p *rulesParser) ParseFile(filename string, fset *token.FileSet, r io.Reade
return nil, fmt.Errorf("expected a gorules package name, found %s", f.Name.Name)
}

typechecker := types.Config{Importer: importer.Default()}
typechecker := types.Config{Importer: p.dslImporter}
p.types = &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}
_, err = typechecker.Check("gorules", fset, []*ast.File{f}, p.types)
if err != nil {
Expand Down

0 comments on commit b00d7a7

Please sign in to comment.