Skip to content

Commit

Permalink
draft: stateful parser is too stateful
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Musat authored and gabotechs committed Dec 24, 2022
1 parent e3a3caf commit a7e6d76
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 54 deletions.
33 changes: 33 additions & 0 deletions internal/js/grammar/.export_test/export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Exporting declarations
export let name1, name2/*, … */; // also var
export const name1 = 1, name2 = 2/*, … */; // also var, let
export function functionName() { /* … */ }
export class ClassName { /* … */ }
export function* generatorFunctionName() { /* … */ }
export const { name1, name2: bar } = o;
export const [ name1, name2 ] = array;

const nameN = 0
const variable1 = 0
const variable2 = 0

// Export list
export { name1, /* …, */ nameN };
export { variable1 as name1, variable2 as name2, /* …, */ nameN };
export { name1 as default /*, … */ };

// Default exports
export default expression;
export default function functionName() { /* … */ }
export default class ClassName { /* … */ }
export default function* generatorFunctionName() { /* … */ }
export default function () { /* … */ }
export default class { /* … */ }
export default function* () { /* … */ }

// Aggregating modules
export * from "module-name";
export * as name1 from "module-name";
export { name1, /* …, */ nameN } from "module-name";
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";
export { default, /* …, */ } from "module-name";
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ import {
import("whatever.js");
import('whatever.js')

export * from ".export"

import { Field } from "redux-form";
import MultiContentListView from "./views/ListView";
import MultiContentAddView from "./views/AddView";
Expand Down
File renamed without changes.
59 changes: 59 additions & 0 deletions internal/js/grammar/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//nolint:govet
package grammar

import (
"github.com/alecthomas/participle/v2/lexer"
)

type ExportDeconstruction struct {
Names []string `"{" ((Ident "as" @Ident) | @Ident) ("," ((Ident "as" @Ident) | @Ident))* "}"`
}

type DeclarationExport struct {
Default bool `"export" ("let"|"const"|"var"|"function"|"class")`
Name string `@Ident`
}

type ListExport struct {
ExportDeconstruction *ExportDeconstruction `"export" @@`
}

type DefaultExport struct {
Default bool `"export" "default"`
}

type ProxyExport struct {
ExportDeconstruction *ExportDeconstruction `"export" @@`
From string `"from" @String`
}

var exportLexer = lexer.Rules{
"CommonExport": {
{"ALL", `\*`, nil},
{"Comment", commentRe, nil},
{"Whitespace", `\s+`, nil},
{"Punct", punctuationRe, nil},
},
"DeclarationExport": {
{Name: "String", Pattern: stringRe},
{Name: "Ident", Pattern: identRe, Action: lexer.Pop()},
lexer.Include("CommonExport"),
},
"ListExport": {
{Name: "ClosingBracket", Pattern: `}`, Action: lexer.Pop()},
{Name: "String", Pattern: stringRe},
{Name: "Ident", Pattern: identRe},
lexer.Include("CommonExport"),
},
"DefaultExport": {
{Name: "Default", Pattern: `default`, Action: lexer.Pop()},
{Name: "String", Pattern: stringRe},
{Name: "Ident", Pattern: identRe},
lexer.Include("CommonExport"),
},
"ProxyExport": {
{Name: "String", Pattern: stringRe, Action: lexer.Pop()},
{Name: "Ident", Pattern: identRe},
lexer.Include("CommonExport"),
},
}
143 changes: 143 additions & 0 deletions internal/js/grammar/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package grammar

import (
"os"
"path"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestExport(t *testing.T) {
tests := []struct {
Name string
ExpectedDeclaration []string
ExpectedProxy []string
}{
{
Name: `export let name1, name2;`,
ExpectedDeclaration: []string{"name1"},
},
{
Name: `export const name1 = 1`,
ExpectedDeclaration: []string{},
},
{
Name: `export function functionName() { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export class ClassName { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export function* generatorFunctionName() { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export const { name1, name2: bar } = o;`,
ExpectedDeclaration: []string{},
},
{
Name: `export const [ name1, name2 ] = array;`,
ExpectedDeclaration: []string{},
},
{
Name: `export { name1, /* …, */ nameN };`,
ExpectedDeclaration: []string{},
},
{
Name: `export { variable1 as name1, variable2 as name2, /* …, */ nameN };`,
ExpectedDeclaration: []string{},
},
{
Name: `export { variable1 as "string name" };`,
ExpectedDeclaration: []string{},
},
{
Name: `export { name1 as default /*, … */ };`,
ExpectedDeclaration: []string{},
},
{
Name: `export default expression;`,
ExpectedDeclaration: []string{},
},
{
Name: `export default function functionName() { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export default class ClassName { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export default function* generatorFunctionName() { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export default function () { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export default class { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export default function* () { /* … */ }`,
ExpectedDeclaration: []string{},
},
{
Name: `export * from "module-name";`,
ExpectedDeclaration: []string{},
},
{
Name: `export * as name1 from "module-name";`,
ExpectedDeclaration: []string{},
},
{
Name: `export { name1, /* …, */ nameN } from "module-name";`,
ExpectedDeclaration: []string{},
},
{
Name: `export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";`,
ExpectedDeclaration: []string{},
},
{
Name: `export { default, /* …, */ } from "module-name"; `,
ExpectedDeclaration: []string{},
},
{
Name: "export.js",
ExpectedDeclaration: []string{},
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
a := require.New(t)
var content []byte
if strings.HasSuffix(tt.Name, ".js") {
var err error
content, err = os.ReadFile(path.Join(".export_test", tt.Name))
a.NoError(err)
} else {
content = []byte(tt.Name)
}
parsed, err := parser.ParseBytes("", content)
a.NoError(err)

var declarationResults []string
var proxyResults []string
for _, stmt := range parsed.Statements {
if stmt.DeclarationExport != nil {
declarationResults = append(declarationResults, stmt.StaticImport.Path)
} else if stmt.DynamicImport != nil {
proxyResults = append(proxyResults, stmt.DynamicImport.Path)
}
}
a.Equal(tt.ExpectedDeclaration, declarationResults)
a.Equal(tt.ExpectedProxy, proxyResults)
})
}
}
86 changes: 36 additions & 50 deletions internal/js/grammar/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,58 @@ package grammar
import (
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
)

type Deconstruction struct {
Names []string `"{" @Ident ("as" Ident)? ("," (@Ident ("as" Ident)?)?)* "}"`
}

type AllImport struct {
Alias string `ALL ("as" @Ident)?`
}

type SelectionImport struct {
AllImport *AllImport `(@@?`
Deconstruction *Deconstruction ` @@?)!`
}

type Imported struct {
Default bool `(@Ident? ","?`
SelectionImport *SelectionImport ` @@?)!`
}

type StaticImport struct {
Imported *Imported `"import" (@@ "from")?`
Path string `@String`
}
"dep-tree/internal/utils"
)

type DynamicImport struct {
Path string `"import" "(" @String ")"`
}
type Statement struct {
// imports
DynamicImport *DynamicImport `@@`
StaticImport *StaticImport `| @@`
// exports
DeclarationExport *DeclarationExport `| @@`
ProxyExport *ProxyExport `| @@`
DefaultExport *DefaultExport `| @@`
ListExport *ListExport `| @@`
}

type File struct {
Statements []*Statement `(@@ | ANY | FALSE_IMPORT_1 | FALSE_IMPORT_2)*`
Statements []*Statement `(@@ | ANY | FALSE_IMPORT_1 | FALSE_IMPORT_2 | FALSE_EXPORT_1 | FALSE_EXPORT_2)*`
}

const identRe = `[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*`
const stringRe = `'[^']*'|"[^"]*"`
const commentRe = `//.*|/\*.*?\*/`

const punctuationRe = `[,{}()]`

var (
lex = lexer.MustStateful(lexer.Rules{
"Root": {
{"FALSE_IMPORT_1", `import[_$a-zA-Z0-9\\xA0-\\uFFFF]+`, nil},
{"FALSE_IMPORT_2", `[_$a-zA-Z0-9\\xA0-\\uFFFF]+import`, nil},
{"IMPORT", `import`, lexer.Push("Import")},
{"Whitespace", `\s+`, nil},
{"ANY", `.`, nil},
},
"Import": {
// Keywords.
{"COMMA", ",", nil},
{"ALL", `\*`, nil},
{"BRACKET_L", `{`, nil},
{"BRACKET_R", `}`, nil},
{"PARENTHESIS_L", `\(`, nil},
{"PARENTHESIS_R", `\)`, nil},
// Other.
{"Ident", `[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*`, nil},
{"String", `'[^']*'|"[^"]*"`, lexer.Pop()},
{"Comment", `//.*|/\*.*?\*/`, nil},
{"Whitespace", `\s+`, nil},
lex = lexer.MustStateful(utils.Merge(
lexer.Rules{
"Root": {
{"FALSE_IMPORT_1", `import` + identRe, nil},
{"FALSE_IMPORT_2", identRe + `import`, nil},
{"IMPORT", `import`, lexer.Push("Import")},
{"FALSE_EXPORT_1", `export` + identRe, nil},
{"FALSE_EXPORT_2", identRe + `export`, nil},

{"DECLARATION_EXPORT", `export\s+(const|let|var|function|class)`, lexer.Push("DeclarationExport")},
{"PROXY_EXPORT", `export\s*\{[\s,_$a-zA-Z0-9\\xA0-\\uFFFF]*}\s*from`, lexer.Push("ProxyExport")},
{"LIST_EXPORT", `export\s*{`, lexer.Push("ListExport")},
{"DEFAULT_EXPORT", `export\s+default`, lexer.Push("DefaultExport")},

{"Whitespace", `\s+`, nil},
{"ANY", `.`, nil},
},
},
})
importLexer,
exportLexer,
))
parser = participle.MustBuild[File](
participle.Lexer(lex),
participle.Elide("Whitespace", "Comment"),
participle.Unquote("String"),
participle.UseLookahead(2),
participle.UseLookahead(1024),
)
)

Expand Down
42 changes: 42 additions & 0 deletions internal/js/grammar/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//nolint:govet
package grammar

import "github.com/alecthomas/participle/v2/lexer"

type ImportDeconstruction struct {
Names []string `"{" @Ident ("as" Ident)? ("," (@Ident ("as" Ident)?)?)* "}"`
}

type AllImport struct {
Alias string `ALL ("as" @Ident)?`
}

type SelectionImport struct {
AllImport *AllImport `(@@?`
Deconstruction *ImportDeconstruction ` @@?)!`
}

type Imported struct {
Default bool `(@Ident? ","?`
SelectionImport *SelectionImport ` @@?)!`
}

type StaticImport struct {
Imported *Imported `"import" (@@ "from")?`
Path string `@String`
}

type DynamicImport struct {
Path string `"import" "(" @String ")"`
}

var importLexer = lexer.Rules{
"Import": {
{"ALL", `\*`, nil},
{"Punct", `[,{}()]`, nil},
{"Ident", `[_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*`, nil},
{"String", `'[^']*'|"[^"]*"`, lexer.Pop()},
{"Comment", `//.*|/\*.*?\*/`, nil},
{"Whitespace", `\s+`, nil},
},
}
Loading

0 comments on commit a7e6d76

Please sign in to comment.