diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-04-17 22:51:06 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-04-17 22:51:06 +0900 |
commit | 88f83624dc6d7c3b66a34c7c3f414719530e421f (patch) | |
tree | 31c0d8c966a4eaf98dd1670855298a8b4e0969c2 /spec/spec.go | |
parent | Change the lexical specs of regexp and define concrete syntax error values (diff) | |
download | tre-88f83624dc6d7c3b66a34c7c3f414719530e421f.tar.gz tre-88f83624dc6d7c3b66a34c7c3f414719530e421f.tar.xz |
Add validation of lexical specs and improve error messages
Diffstat (limited to 'spec/spec.go')
-rw-r--r-- | spec/spec.go | 89 |
1 files changed, 82 insertions, 7 deletions
diff --git a/spec/spec.go b/spec/spec.go index d827b68..0f9b484 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -1,21 +1,96 @@ package spec +import ( + "fmt" + "regexp" + "strings" +) + +const lexKindPattern = "[A-Za-z_][0-9A-Za-z_]*" + +var lexKindRE = regexp.MustCompile(lexKindPattern) + +type LexKind string + +const LexKindNil = LexKind("") + +func (k LexKind) String() string { + return string(k) +} + +func (k LexKind) validate() error { + if k == "" { + return fmt.Errorf("kind doesn't allow to be the empty string") + } + if !lexKindRE.Match([]byte(k)) { + return fmt.Errorf("kind must be %v", lexKindPattern) + } + return nil +} + +type LexPattern string + +func (p LexPattern) validate() error { + if p == "" { + return fmt.Errorf("pattern doesn't allow to be the empty string") + } + return nil +} + type LexEntry struct { - Kind string `json:"kind"` - Pattern string `json:"pattern"` + Kind LexKind `json:"kind"` + Pattern LexPattern `json:"pattern"` } -func NewLexEntry(kind string, pattern string) *LexEntry { - return &LexEntry{ - Kind: kind, - Pattern: pattern, +func (e *LexEntry) validate() error { + err := e.Kind.validate() + if err != nil { + return err + } + err = e.Pattern.validate() + if err != nil { + return err } + return nil } type LexSpec struct { Entries []*LexEntry `json:"entries"` } +func (s *LexSpec) Validate() error { + if len(s.Entries) <= 0 { + return fmt.Errorf("the lexical specification must have at least one entry") + } + { + var errs []error + for i, e := range s.Entries { + err := e.validate() + if err != nil { + errs = append(errs, fmt.Errorf("entry #%v: %w", i+1, err)) + } + } + if len(errs) > 0 { + var b strings.Builder + fmt.Fprintf(&b, "%v", errs[0]) + for _, err := range errs[1:] { + fmt.Fprintf(&b, "\n%v", err) + } + return fmt.Errorf(b.String()) + } + } + { + ks := map[string]struct{}{} + for _, e := range s.Entries { + if _, exist := ks[e.Kind.String()]; exist { + return fmt.Errorf("kinds `%v` are duplicates", e.Kind) + } + ks[e.Kind.String()] = struct{}{} + } + } + return nil +} + type TransitionTable struct { InitialState int `json:"initial_state"` AcceptingStates map[int]int `json:"accepting_states"` @@ -23,6 +98,6 @@ type TransitionTable struct { } type CompiledLexSpec struct { - Kinds []string `json:"kinds"` + Kinds []LexKind `json:"kinds"` DFA *TransitionTable `json:"dfa"` } |