aboutsummaryrefslogtreecommitdiff
path: root/spec/spec.go
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2021-04-17 22:51:06 +0900
committerRyo Nihei <nihei.dev@gmail.com>2021-04-17 22:51:06 +0900
commit88f83624dc6d7c3b66a34c7c3f414719530e421f (patch)
tree31c0d8c966a4eaf98dd1670855298a8b4e0969c2 /spec/spec.go
parentChange the lexical specs of regexp and define concrete syntax error values (diff)
downloadtre-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.go89
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"`
}