diff options
-rw-r--r-- | README.md | 16 | ||||
-rw-r--r-- | compiler/compiler_test.go | 103 | ||||
-rw-r--r-- | spec/spec.go | 15 |
3 files changed, 123 insertions, 11 deletions
@@ -113,14 +113,14 @@ top level object: entry object: -| Field | Type | Nullable | Description | -|----------|------------------|----------|-----------------------------------------------------------------------------------------------| -| kind | string | false | A name of a token kind | -| pattern | string | false | A pattern in a regular expression | -| modes | array of strings | true | Mode names that an entry is enabled in (default: "default") | -| push | string | true | A mode name that the lexer pushes to own mode stack when a token matching the pattern appears | -| pop | bool | true | When `pop` is `true`, the lexer pops a mode from own mode stack. | -| fragment | bool | true | When `fragment` is `true`, its entry is a fragment. | +| Field | Type | Nullable | Description | +|----------|------------------|----------|-----------------------------------------------------------------------------------------------------------------------| +| kind | string | false | A name of a token kind. The name must be unique, but duplicate names between fragments and non-fragments are allowed. | +| pattern | string | false | A pattern in a regular expression | +| modes | array of strings | true | Mode names that an entry is enabled in (default: "default") | +| push | string | true | A mode name that the lexer pushes to own mode stack when a token matching the pattern appears | +| pop | bool | true | When `pop` is `true`, the lexer pops a mode from own mode stack. | +| fragment | bool | true | When `fragment` is `true`, its entry is a fragment. | See [Regular Expression Syntax](#regular-expression-syntax) for more details on the regular expression syntax. diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go new file mode 100644 index 0000000..c76bb24 --- /dev/null +++ b/compiler/compiler_test.go @@ -0,0 +1,103 @@ +package compiler + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/nihei9/maleeni/spec" +) + +func TestCompile(t *testing.T) { + tests := []struct { + Caption string + Spec string + Err bool + }{ + { + Caption: "allow duplicates names between fragments and non-fragments", + Spec: ` +{ + "entries": [ + { + "kind": "a2z", + "pattern": "\\f{a2z}" + }, + { + "fragment": true, + "kind": "a2z", + "pattern": "[a-z]" + } + ] +} +`, + }, + { + Caption: "don't allow duplicates names in non-fragments", + Spec: ` +{ + "entries": [ + { + "kind": "a2z", + "pattern": "a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z" + }, + { + "kind": "a2z", + "pattern": "[a-z]" + } + ] +} +`, + Err: true, + }, + { + Caption: "don't allow duplicates names in fragments", + Spec: ` +{ + "entries": [ + { + "kind": "a2z", + "pattern": "\\f{a2z}" + }, + { + "fragments": true, + "kind": "a2z", + "pattern": "a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z" + }, + { + "fragments": true, + "kind": "a2z", + "pattern": "[a-z]" + } + ] +} +`, + Err: true, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("#%v %s", i, tt.Caption), func(t *testing.T) { + lspec := &spec.LexSpec{} + err := json.Unmarshal([]byte(tt.Spec), lspec) + if err != nil { + t.Fatalf("%v", err) + } + clspec, err := Compile(lspec) + if tt.Err { + if err == nil { + t.Fatalf("expected an error") + } + if clspec != nil { + t.Fatalf("Compile function mustn't return a compiled specification") + } + } else { + if err != nil { + t.Fatalf("unexpected error") + } + if clspec == nil { + t.Fatalf("Compile function must return a compiled specification") + } + } + }) + } +} diff --git a/spec/spec.go b/spec/spec.go index 61955ac..829008a 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -143,11 +143,20 @@ func (s *LexSpec) Validate() error { } { ks := map[string]struct{}{} + fks := map[string]struct{}{} for _, e := range s.Entries { - if _, exist := ks[e.Kind.String()]; exist { - return fmt.Errorf("kinds `%v` are duplicates", e.Kind) + // Allow duplicate names between fragments and non-fragments. + if e.Fragment { + if _, exist := fks[e.Kind.String()]; exist { + return fmt.Errorf("kinds `%v` are duplicates", e.Kind) + } + fks[e.Kind.String()] = struct{}{} + } else { + if _, exist := ks[e.Kind.String()]; exist { + return fmt.Errorf("kinds `%v` are duplicates", e.Kind) + } + ks[e.Kind.String()] = struct{}{} } - ks[e.Kind.String()] = struct{}{} } } return nil |