Repository: openconfig/goyang
Pull request #163 (2021-02-10 19:55:52)

Clean-up Parser and Lexer
- Remove unused exported function `FakeStatement`.
- Simplify by replacing `closeBrace` and `openBrace` with their character values.
- Rename `tIdentifier` to `tUnquoted`. Identifiers can be quoted in YANG, so this name is confusing. The original author intended these to be unquoted strings.
- Improve comments in the parser.


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 5d27a5f..164eda9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -11,7 +11,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        go: ['1.12', '1.13', '1.14', '1.15', '1.16', '1.x']
+        go: ['1.12', '1.13', '1.14']
     steps:
 
     - name: Set up Go ${{ matrix.go }}
diff --git a/README.md b/README.md
index df07ad8..3564724 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 ![Go](https://github.com/openconfig/goyang/workflows/Go/badge.svg?branch=master)
 [![Coverage Status](https://coveralls.io/repos/github/openconfig/goyang/badge.svg?branch=master)](https://coveralls.io/github/openconfig/goyang?branch=master)
 
-Current support for `goyang` is for the [latest 3 Go releases](https://golang.org/project/#release).
-
 # goyang
 YANG parser and compiler for Go programs.
 
diff --git a/pkg/yang/ast.go b/pkg/yang/ast.go
index 1f70453..671aeb7 100644
--- a/pkg/yang/ast.go
+++ b/pkg/yang/ast.go
@@ -65,6 +65,10 @@ type meta struct {
 	Module []*Module `yang:"module"`
 }
 
+func init() {
+	initTypes(reflect.TypeOf(&meta{}))
+}
+
 // aliases is a map of "aliased" names, that is, two types of statements
 // that parse (nearly) the same.
 var aliases = map[string]string{
@@ -74,27 +78,16 @@ var aliases = map[string]string{
 // BuildAST builds an abstract syntax tree based on the yang statement s.
 // Normally it should return a *Module.
 func BuildAST(s *Statement) (Node, error) {
-	return buildASTWithTypeDict(s, &typeDict)
-}
-
-// buildASTWithTypeDict creates an AST for the input statement, and returns its
-// root node. It also takes as input a type dictionary into which any
-// encountered typedefs within the statement are cached.
-func buildASTWithTypeDict(s *Statement, d *typeDictionary) (Node, error) {
-	initTypes(reflect.TypeOf(&meta{}), d)
-	v, err := build(s, nilValue, d)
+	v, err := build(s, nilValue)
 	if err != nil {
 		return nil, err
 	}
 	return v.Interface().(Node), nil
 }
 
-// build builds and returns an AST from the statement s and with parent p. It
-// also takes as input a type dictionary d into which any encountered typedefs
-// within the statement are cached. The type of value returned depends on the
-// keyword in s. It returns an error if it cannot build the statement into its
-// corresponding Node type.
-func build(s *Statement, p reflect.Value, d *typeDictionary) (v reflect.Value, err error) {
+// build builds and returns an AST from the statement s, with parent p, or
+// returns an error.  The type of value returned depends on the keyword in s.
+func build(s *Statement, p reflect.Value) (v reflect.Value, err error) {
 	defer func() {
 		// If we are returning a real Node then call addTypedefs
 		// if the node possibly contains typedefs.
@@ -102,7 +95,7 @@ func build(s *Statement, p reflect.Value, d *typeDictionary) (v reflect.Value, e
 			return
 		}
 		if t, ok := v.Interface().(Typedefer); ok {
-			d.addTypedefs(t)
+			addTypedefs(t)
 		}
 	}()
 	kind := s.Keyword
@@ -204,9 +197,7 @@ func build(s *Statement, p reflect.Value, d *typeDictionary) (v reflect.Value, e
 }
 
 // initTypes builds up the functions necessary to parse a Statement into the
-// type at. It also builds up the functions to populate the input type
-// dictionary d with any encountered typedefs within the statement. at must be
-// a of type pointer to structure and that structure should
+// type at.  at must be a of type pointer to structure and that structure should
 // implement Node.  For each field of the structure with a yang tag (e.g.,
 // `yang:"command"`), a function is created and "command" is mapped to it.  The
 // complete map is then added to the typeMap map with at as the key.
@@ -267,7 +258,7 @@ func build(s *Statement, p reflect.Value, d *typeDictionary) (v reflect.Value, e
 //                   (This is to support merging Module and SubModule).
 //
 // If at contains substructures, initTypes recurses on the substructures.
-func initTypes(at reflect.Type, d *typeDictionary) {
+func initTypes(at reflect.Type) {
 	if typeMap[at] != nil {
 		return // we already defined this type
 	}
@@ -332,7 +323,7 @@ func initTypes(at reflect.Type, d *typeDictionary) {
 			switch nameMap[name] {
 			case nil:
 				nameMap[name] = dt
-				initTypes(dt, d) // Make sure that structure type is included
+				initTypes(dt) // Make sure that structure type is included
 			case dt:
 			default:
 				panic("redeclared type " + name)
@@ -409,7 +400,7 @@ func initTypes(at reflect.Type, d *typeDictionary) {
 				}
 
 				// Use build to build the value for this field.
-				sv, err := build(s, v, d)
+				sv, err := build(s, v)
 				if err != nil {
 					return err
 				}
@@ -432,7 +423,7 @@ func initTypes(at reflect.Type, d *typeDictionary) {
 					if v.Type() != at {
 						panic(fmt.Sprintf("given type %s, need type %s", v.Type(), at))
 					}
-					sv, err := build(s, v, d)
+					sv, err := build(s, v)
 					if err != nil {
 						return err
 					}
diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go
index faa8d9d..af97ab4 100644
--- a/pkg/yang/entry.go
+++ b/pkg/yang/entry.go
@@ -535,7 +535,6 @@ func ToEntry(n Node) (e *Entry) {
 			Errors: []error{err},
 		}
 	}
-	ms := RootNode(n).modules
 	if e := entryCache[n]; e != nil {
 		return e
 	}
@@ -573,7 +572,7 @@ func ToEntry(n Node) (e *Entry) {
 	switch s := n.(type) {
 	case *Leaf:
 		e := newLeaf(n)
-		if errs := s.Type.resolve(ms.typeDict); errs != nil {
+		if errs := s.Type.resolve(); errs != nil {
 			e.Errors = errs
 		}
 		if s.Description != nil {
@@ -863,7 +862,7 @@ func ToEntry(n Node) (e *Entry) {
 			}
 
 			if n.Type != nil {
-				if errs := n.Type.resolve(ms.typeDict); errs != nil {
+				if errs := n.Type.resolve(); errs != nil {
 					e.addError(fmt.Errorf("deviation has unresolvable type, %v", errs))
 					continue
 				}
@@ -895,7 +894,7 @@ func ToEntry(n Node) (e *Entry) {
 
 					for _, sd := range d.Deviate {
 						if sd.Type != nil {
-							sd.Type.resolve(ms.typeDict)
+							sd.Type.resolve()
 						}
 					}
 				}
@@ -1340,12 +1339,6 @@ func (e *Entry) Namespace() *Value {
 	// Return the namespace of a valid root parent entry
 	if e != nil && e.Node != nil {
 		if root := RootNode(e.Node); root != nil {
-			if root.Kind() == "submodule" {
-				root = root.modules.Modules[root.BelongsTo.Name]
-				if root == nil {
-					return new(Value)
-				}
-			}
 			return root.Namespace
 		}
 	}
@@ -1367,7 +1360,7 @@ func (e *Entry) InstantiatingModule() (string, error) {
 
 	ns, err := e.Modules().FindModuleByNamespace(n.Name)
 	if err != nil {
-		return "", fmt.Errorf("could not find module %q when retrieving namespace for %s", n.Name, e.Name)
+		return "", fmt.Errorf("could not find module %s when retrieving namespace for %s", n.Name, e.Name)
 	}
 	return ns.Name, nil
 }
diff --git a/pkg/yang/lex.go b/pkg/yang/lex.go
index 5057c4e..0627868 100644
--- a/pkg/yang/lex.go
+++ b/pkg/yang/lex.go
@@ -20,7 +20,7 @@ package yang
 //    tError       // an error was encountered
 //    tEOF         // end-of-file
 //    tString      // A de-quoted string (e.g., "\"bob\"" becomes "bob")
-//    tIdentifier  // An un-quoted string
+//    tUnquoted    // An un-quoted string
 //    '{'
 //    ';'
 //    '}'
@@ -40,9 +40,6 @@ const (
 	eof       = 0x7fffffff // end of file, also an invalid rune
 	maxErrors = 8
 	tooMany   = "too many errors...\n"
-
-	openBrace  = '{'
-	closeBrace = '}'
 )
 
 // stateFn represents a state in the lexer as a function, returning the next
@@ -76,10 +73,10 @@ type lexer struct {
 type code int
 
 const (
-	tEOF        = code(-1 - iota) // Reached end of file
-	tError                        // An error
-	tString                       // A dequoted string
-	tIdentifier                   // A non-quoted string
+	tEOF      = code(-1 - iota) // Reached end of file
+	tError                      // An error
+	tString                     // A dequoted string
+	tUnquoted                   // A non-quoted string
 )
 
 // String returns c as a string.
@@ -89,8 +86,8 @@ func (c code) String() string {
 		return "Error"
 	case tString:
 		return "String"
-	case tIdentifier:
-		return "Identifier"
+	case tUnquoted:
+		return "Unquoted"
 	}
 	if c < 0 || c > '~' {
 		return fmt.Sprintf("%d", c)
@@ -358,7 +355,7 @@ func lexGround(l *lexer) stateFn {
 	switch c := l.peek(); c {
 	case eof:
 		return nil
-	case ';', openBrace, closeBrace:
+	case ';', '{', '}':
 		l.next()
 		l.emit(code(c))
 		return lexGround
@@ -397,20 +394,10 @@ func lexGround(l *lexer) stateFn {
 			l.next()
 			l.next()
 			return lexGround
-		default:
-			return lexIdentifier
-		}
-	case '+':
-		l.next()
-		switch l.peek() {
-		case '"', '\'':
-			l.emit(tIdentifier)
-			return lexGround
-		default:
-			return lexIdentifier
 		}
+		fallthrough
 	default:
-		return lexIdentifier
+		return lexUnquoted
 	}
 }
 
@@ -496,21 +483,21 @@ func lexQString(l *lexer) stateFn {
 	}
 }
 
-// lexIdentifier reads one identifier/number/un-quoted-string/...
+// lexUnquoted reads one identifier/number/un-quoted-string/...
 //
 // From https://tools.ietf.org/html/rfc7950#section-6.1.3:
 // An unquoted string is any sequence of characters that does not
 // contain any space, tab, carriage return, or line feed characters, a
 // single or double quote character, a semicolon (";"), braces ("{" or
 // "}"), or comment sequences ("//", "/*", or "*/").
-func lexIdentifier(l *lexer) stateFn {
+func lexUnquoted(l *lexer) stateFn {
 	for {
 		switch c := l.peek(); c {
 		// TODO: Support detection of comment immediately following an
-		// identifier, likely through supporting two peeks instead of
-		// just one.
-		case ' ', '\r', '\n', '\t', ';', '"', '\'', openBrace, closeBrace, eof:
-			l.emit(tIdentifier)
+		// unquoted string, likely through supporting two peeks instead
+		// of just one.
+		case ' ', '\r', '\n', '\t', ';', '"', '\'', '{', '}', eof:
+			l.emit(tUnquoted)
 			return lexGround
 		default:
 			l.next()
diff --git a/pkg/yang/modules.go b/pkg/yang/modules.go
index 04fb206..66c4ea0 100644
--- a/pkg/yang/modules.go
+++ b/pkg/yang/modules.go
@@ -28,7 +28,6 @@ type Modules struct {
 	includes   map[*Module]bool   // Modules we have already done include on
 	byPrefix   map[string]*Module // Cache of prefix lookup
 	byNS       map[string]*Module // Cache of namespace lookup
-	typeDict   *typeDictionary    // Cache for type definitions.
 }
 
 // NewModules returns a newly created and initialized Modules.
@@ -39,7 +38,6 @@ func NewModules() *Modules {
 		includes:   map[*Module]bool{},
 		byPrefix:   map[string]*Module{},
 		byNS:       map[string]*Module{},
-		typeDict:   &typeDictionary{dict: map[Node]map[string]*Typedef{}},
 	}
 }
 
@@ -63,7 +61,7 @@ func (ms *Modules) Parse(data, name string) error {
 		return err
 	}
 	for _, s := range ss {
-		n, err := buildASTWithTypeDict(s, ms.typeDict)
+		n, err := BuildAST(s)
 		if err != nil {
 			return err
 		}
@@ -285,7 +283,7 @@ func (ms *Modules) process() []error {
 	// has not yet been built.
 	errs = append(errs, ms.resolveIdentities()...)
 	// Append any errors found trying to resolve typedefs
-	errs = append(errs, ms.typeDict.resolveTypedefs()...)
+	errs = append(errs, resolveTypedefs()...)
 
 	return errs
 }
diff --git a/pkg/yang/node.go b/pkg/yang/node.go
index 4c66775..daa886f 100644
--- a/pkg/yang/node.go
+++ b/pkg/yang/node.go
@@ -127,24 +127,12 @@ func FindModuleByPrefix(n Node, prefix string) *Module {
 // MatchingExtensions returns the subset of the given node's extensions
 // that match the given module and identifier.
 func MatchingExtensions(n Node, module, identifier string) ([]*Statement, error) {
-	return matchingExtensions(n, n.Exts(), module, identifier)
-}
-
-// MatchingEntryExtensions returns the subset of the given entry's extensions
-// that match the given module and identifier.
-func MatchingEntryExtensions(e *Entry, module, identifier string) ([]*Statement, error) {
-	return matchingExtensions(e.Node, e.Exts, module, identifier)
-}
-
-// matchingEntryExtensions returns the subset of the given node's extensions
-// that match the given module and identifier.
-func matchingExtensions(n Node, exts []*Statement, module, identifier string) ([]*Statement, error) {
 	var matchingExtensions []*Statement
-	for _, ext := range exts {
+	for _, ext := range n.Exts() {
 		names := strings.SplitN(ext.Keyword, ":", 2)
 		mod := FindModuleByPrefix(n, names[0])
 		if mod == nil {
-			return nil, fmt.Errorf("matchingExtensions: module prefix %q not found", names[0])
+			return nil, fmt.Errorf("MatchingExtensions: module prefix %q not found", names[0])
 		}
 		if len(names) == 2 && names[1] == identifier && mod.Name == module {
 			matchingExtensions = append(matchingExtensions, ext)
diff --git a/pkg/yang/parse.go b/pkg/yang/parse.go
index 33913a9..7634f58 100644
--- a/pkg/yang/parse.go
+++ b/pkg/yang/parse.go
@@ -17,9 +17,6 @@ package yang
 // This file implements Parse, which  parses the input as generic YANG and
 // returns a slice of base Statements (which in turn may contain more
 // Statements, i.e., a slice of Statement trees.)
-//
-// TODO(borman): remove this TODO once ast.go is part of of this package.
-// See ast.go for the conversion of Statements into an AST tree of Nodes.
 
 import (
 	"bytes"
@@ -47,8 +44,13 @@ type parser struct {
 	hitBrace *Statement
 }
 
-// A Statement is a generic YANG statement.  A Statement may have optional
-// sub-statement (i.e., a Statement is a tree).
+// Statement is a generic YANG statement that may have sub-statements.
+// It implements the Node interface.
+//
+// Within the parser, it represents a non-terminal token.
+// From https://tools.ietf.org/html/rfc7950#section-6.3:
+// statement = keyword [argument] (";" / "{" *statement "}")
+// The argument is a string.
 type Statement struct {
 	Keyword     string
 	HasArgument bool
@@ -60,16 +62,6 @@ type Statement struct {
 	col  int // 1's based column number
 }
 
-// FakeStatement returns a statement filled in with keyword, file, line and col.
-func FakeStatement(keyword, file string, line, col int) *Statement {
-	return &Statement{
-		Keyword: keyword,
-		file:    file,
-		line:    line,
-		col:     col,
-	}
-}
-
 func (s *Statement) NName() string         { return s.Argument }
 func (s *Statement) Kind() string          { return s.Keyword }
 func (s *Statement) Statement() *Statement { return s }
@@ -80,9 +72,6 @@ func (s *Statement) Exts() []*Statement    { return nil }
 // argument.
 func (s *Statement) Arg() (string, bool) { return s.Argument, s.HasArgument }
 
-// Keyword returns the keyword of s.
-//func (s *Statement) Keyword() string { return s.Keyword }
-
 // SubStatements returns a slice of Statements found in s.
 func (s *Statement) SubStatements() []*Statement { return s.statements }
 
@@ -162,8 +151,8 @@ func (s *Statement) Write(w io.Writer, indent string) error {
 	return nil
 }
 
-// ignoreMe is returned to continue processing after an error (the parse will
-// fail, but we want to look for more errors).
+// ignoreMe is an error recovery token used by the parser in order
+// to continue processing for other errors in the file.
 var ignoreMe = &Statement{}
 
 // Parse parses the input as generic YANG and returns the statements parsed.
@@ -185,19 +174,18 @@ Loop:
 		case nil:
 			break Loop
 		case p.hitBrace:
-			fmt.Fprintf(p.errout, "%s:%d:%d: unexpected %c\n", ns.file, ns.line, ns.col, closeBrace)
+			fmt.Fprintf(p.errout, "%s:%d:%d: unexpected %c\n", ns.file, ns.line, ns.col, '}')
 		default:
 			statements = append(statements, ns)
 		}
 	}
 
-	p.checkStatementDepth()
+	p.checkStatementDepthIsZero()
 
 	if p.errout.Len() == 0 {
 		return statements, nil
 	}
 	return nil, errors.New(strings.TrimSpace(p.errout.String()))
-
 }
 
 // push pushes tokens t back on the input stream so they will be the next
@@ -217,12 +205,13 @@ func (p *parser) pop() *token {
 	return nil
 }
 
-// next returns the next token from the lexer.  It also handles string
-// concatenation.
+// next returns the next token from the lexer. If the next token is a
+// concatenated string, it returns the concatenated string as the token.
 func (p *parser) next() *token {
 	if t := p.pop(); t != nil {
 		return t
 	}
+	// next returns the next unprocessed lexer token.
 	next := func() *token {
 		for {
 			if t := p.lex.NextToken(); t.Code() != tError {
@@ -234,13 +223,15 @@ func (p *parser) next() *token {
 	if t.Code() != tString {
 		return t
 	}
-	// Handle `"string" + "string"`
+	// Process string concatenation (both single and double quote).
+	// See https://tools.ietf.org/html/rfc7950#section-6.1.3.1
+	// The lexer trimmed the quotes already.
 	for {
 		nt := next()
 		switch nt.Code() {
 		case tEOF:
 			return t
-		case tIdentifier:
+		case tUnquoted:
 			if nt.Text != "+" {
 				p.push(nt)
 				return t
@@ -249,42 +240,44 @@ func (p *parser) next() *token {
 			p.push(nt)
 			return t
 		}
-		// We found a +, now look for a following string
-		st := next()
-		switch st.Code() {
+		// Invariant: nt is a + sign.
+		nnt := next()
+		switch nnt.Code() {
 		case tEOF:
 			p.push(nt)
 			return t
 		case tString:
-			// concatenate the text and drop the nt and st tokens
-			// try again
-			t.Text += st.Text
+			// Accumulate the concatenation.
+			t.Text += nnt.Text
 		default:
-			p.push(st, nt)
+			p.push(nnt, nt)
 			return t
 		}
-
 	}
 }
 
 // nextStatement returns the next statement in the input, which may in turn
 // recurse to read sub statements.
+// nil is returned when EOF has been reached, or is reached halfway through
+// parsing the next statement (with associated syntax errors printed to
+// errout).
 func (p *parser) nextStatement() *Statement {
 	t := p.next()
 	switch t.Code() {
 	case tEOF:
 		return nil
-	case closeBrace:
+	case '}':
 		p.statementDepth -= 1
 		p.hitBrace.file = t.File
 		p.hitBrace.line = t.Line
 		p.hitBrace.col = t.Col
 		return p.hitBrace
-	case tIdentifier:
+	case tUnquoted:
 	default:
-		fmt.Fprintf(p.errout, "%v: not an identifier\n", t)
+		fmt.Fprintf(p.errout, "%v: keyword token not an unquoted string\n", t)
 		return ignoreMe
 	}
+	// Invariant: t represents a keyword token.
 
 	s := &Statement{
 		Keyword: t.Text,
@@ -293,29 +286,31 @@ func (p *parser) nextStatement() *Statement {
 		col:     t.Col,
 	}
 
-	// The keyword "pattern" must be treated special.  When
+	// The keyword "pattern" must be treated specially. When
 	// parsing the argument for "pattern", escape sequences
 	// must be expanded differently.
 	p.lex.inPattern = t.Text == "pattern"
 	t = p.next()
 	p.lex.inPattern = false
 	switch t.Code() {
-	case tString, tIdentifier:
+	case tString, tUnquoted:
 		s.HasArgument = true
 		s.Argument = t.Text
 		t = p.next()
 	}
+
 	switch t.Code() {
 	case tEOF:
 		fmt.Fprintf(p.errout, "%s: unexpected EOF\n", s.file)
 		return nil
 	case ';':
 		return s
-	case openBrace:
+	case '{':
 		p.statementDepth += 1
 		for {
 			switch ns := p.nextStatement(); ns {
 			case nil:
+				// Signal EOF reached.
 				return nil
 			case p.hitBrace:
 				return s
@@ -324,21 +319,20 @@ func (p *parser) nextStatement() *Statement {
 			}
 		}
 	default:
-		fmt.Fprintf(p.errout, "%v: syntax error\n", t)
+		fmt.Fprintf(p.errout, "%v: syntax error, expected ';' or nested statements in {}\n", t)
 		return ignoreMe
 	}
 }
 
-// Checks that we have a statement depth of 0. It's an error to exit
-// the parser with a depth of > 0, it means we are missing closing
+// checkStatementDepthIsZero checks that we aren't missing closing
 // braces. Note: the parser will error out for the case where we
-// start with an unmatched close brace, eg. depth < 0
+// start with an unmatched close brace, i.e. depth < 0
 //
-// This test is only done if there are no other errors as
+// This test should only be done if there are no other errors as
 // we may exit early due to those errors -- and therefore there *might*
 // not really be a mismatched brace issue.
-func (p *parser) checkStatementDepth() {
-	if p.errout.Len() > 0 || p.statementDepth < 1 {
+func (p *parser) checkStatementDepthIsZero() {
+	if p.errout.Len() > 0 || p.statementDepth == 0 {
 		return
 	}
 
diff --git a/pkg/yang/types.go b/pkg/yang/types.go
index 829e816..12977a9 100644
--- a/pkg/yang/types.go
+++ b/pkg/yang/types.go
@@ -92,37 +92,37 @@ func (d *typeDictionary) typedefs() []*Typedef {
 // addTypedefs is called from BuildAST after each Typedefer is defined.  There
 // are no error conditions in this process as it is simply used to build up the
 // typedef dictionary.
-func (d *typeDictionary) addTypedefs(t Typedefer) {
+func addTypedefs(t Typedefer) {
 	for _, td := range t.Typedefs() {
-		d.add(t, td.Name, td)
+		typeDict.add(t, td.Name, td)
 	}
 }
 
 // resolveTypedefs is called after all of modules and submodules have been read,
 // as well as their imports and includes.  It resolves all typedefs found in all
 // modules and submodules read in.
-func (d *typeDictionary) resolveTypedefs() []error {
+func resolveTypedefs() []error {
 	var errs []error
 
 	// When resolve typedefs, we may need to look up other typedefs.
 	// We gather all typedefs into a slice so we don't deadlock on
 	// typeDict.
-	for _, td := range d.typedefs() {
-		errs = append(errs, td.resolve(d)...)
+	for _, td := range typeDict.typedefs() {
+		errs = append(errs, td.resolve()...)
 	}
 	return errs
 }
 
 // resolve creates a YangType for t, if not already done.  Resolving t
 // requires resolving the Type that t is based on.
-func (t *Typedef) resolve(d *typeDictionary) []error {
+func (t *Typedef) resolve() []error {
 	// If we have no parent we are a base type and
 	// are already resolved.
 	if t.Parent == nil || t.YangType != nil {
 		return nil
 	}
 
-	if errs := t.Type.resolve(d); len(errs) != 0 {
+	if errs := t.Type.resolve(); len(errs) != 0 {
 		return errs
 	}
 
@@ -158,7 +158,7 @@ func (t *Typedef) resolve(d *typeDictionary) []error {
 
 // resolve resolves Type t, as well as the underlying typedef for t.  If t
 // cannot be resolved then one or more errors are returned.
-func (t *Type) resolve(d *typeDictionary) (errs []error) {
+func (t *Type) resolve() (errs []error) {
 	if t.YangType != nil {
 		return nil
 	}
@@ -182,13 +182,13 @@ check:
 		// If we have no prefix, or the prefix is what we call our own
 		// root, then we look in our ancestors for a typedef of name.
 		for n := Node(t); n != nil; n = n.ParentNode() {
-			if td = d.find(n, name); td != nil {
+			if td = typeDict.find(n, name); td != nil {
 				break check
 			}
 		}
 		// We need to check our sub-modules as well
 		for _, in := range root.Include {
-			if td = d.find(in.Module, name); td != nil {
+			if td = typeDict.find(in.Module, name); td != nil {
 				break check
 			}
 		}
@@ -208,12 +208,12 @@ check:
 		// what module it is part of and if it is defined at the top
 		// level of that module.
 		var err error
-		td, err = d.findExternal(t, prefix, name)
+		td, err = typeDict.findExternal(t, prefix, name)
 		if err != nil {
 			return []error{err}
 		}
 	}
-	if errs := td.resolve(d); len(errs) > 0 {
+	if errs := td.resolve(); len(errs) > 0 {
 		return errs
 	}
 
@@ -398,7 +398,7 @@ check:
 	// so we have to check equality the hard way.
 looking:
 	for _, ut := range t.Type {
-		errs = append(errs, ut.resolve(d)...)
+		errs = append(errs, ut.resolve()...)
 		if ut.YangType != nil {
 			for _, yt := range y.Type {
 				if ut.YangType.Equal(yt) {
diff --git a/pkg/yang/types_builtin.go b/pkg/yang/types_builtin.go
index e562065..53cc01e 100644
--- a/pkg/yang/types_builtin.go
+++ b/pkg/yang/types_builtin.go
@@ -28,335 +28,505 @@ import (
 
 // This file handles interpretation of types
 
-// These are the default ranges defined by the YANG standard.
-var (
-	Int8Range  = mustParseRangesInt("-128..127")
-	Int16Range = mustParseRangesInt("-32768..32767")
-	Int32Range = mustParseRangesInt("-2147483648..2147483647")
-	Int64Range = mustParseRangesInt("-9223372036854775808..9223372036854775807")
-
-	Uint8Range  = mustParseRangesInt("0..255")
-	Uint16Range = mustParseRangesInt("0..65535")
-	Uint32Range = mustParseRangesInt("0..4294967295")
-	Uint64Range = mustParseRangesInt("0..18446744073709551615")
-
-	Decimal64Range = mustParseRangesDecimal("min..max", 1)
-)
+// TypeKind is the enumeration of the base types available in YANG.  It
+// is analogous to reflect.Kind.
+type TypeKind uint
 
 const (
-	// MaxInt64 corresponds to the maximum value of a signed int64.
-	MaxInt64 = 1<<63 - 1
-	// MinInt64 corresponds to the maximum value of a signed int64.
-	MinInt64 = -1 << 63
-	// AbsMinInt64 is the absolute value of MinInt64.
-	AbsMinInt64 = 1 << 63
-	// MaxEnum is the maximum value of an enumeration.
-	MaxEnum = 1<<31 - 1
-	// MinEnum is the minimum value of an enumeration.
-	MinEnum = -1 << 31
-	// MaxBitfieldSize is the maximum number of bits in a bitfield.
-	MaxBitfieldSize = 1 << 32
-	// MaxFractionDigits is the maximum number of fractional digits as per RFC6020 Section 9.3.4.
-	MaxFractionDigits uint8 = 18
-
-	space18 = "000000000000000000" // used for prepending 0's
-)
-
-type NumberKind int
-
-const (
-	// Positive indicates that a Number is non-negative.
-	Positive = NumberKind(iota)
-	// Negative indicates that a Number is negative.
-	Negative
-	// MinNumber indicates that the Number is the minimum value allowed for the range.
-	MinNumber
-	// MaxNumber indicates that the Number is the maximum value allowed for the range.
-	MaxNumber
+	// Ynone represents the invalid (unset) type.
+	Ynone = TypeKind(iota)
+	// Yint8 is an int in the range [-128, 127].
+	Yint8
+	// Yint16 is an int in the range [-32768, 32767].
+	Yint16
+	// Yint32 is an int in the range [-2147483648, 2147483647].
+	Yint32
+	// Yint64 is an int in the range [-9223372036854775808, 9223372036854775807]
+	Yint64
+	// Yuint8 is an int in the range [0, 255]
+	Yuint8
+	// Yuint16 is an int in the range [0, 65535]
+	Yuint16
+	// Yuint32 is an int in the range [0, 4294967295]
+	Yuint32
+	// Yuint64 is an int in the range [0, 18446744073709551615]
+	Yuint64
+
+	// Ybinary stores arbitrary data.
+	Ybinary
+	// Ybits is a named set of bits or flags.
+	Ybits
+	// Ybool is true or false.
+	Ybool
+	// Ydecimal64 is a signed decimal number.
+	Ydecimal64
+	// Yempty has no associated value.
+	Yempty
+	// Yenum stores enumerated strings.
+	Yenum
+	// Yidentityref stores an extensible enumeration.
+	Yidentityref
+	// YinstanceIdentifier stores a reference to a data tree node.
+	YinstanceIdentifier
+	// Yleafref stores a reference to a leaf instance.
+	Yleafref
+	// Ystring is a human readable string.
+	Ystring
+	// Yunion is a choice of types.
+	Yunion
 )
 
-// A Number is either an integer the range of [-(1<<64) - 1, (1<<64)-1], or a
-// YANG decimal conforming to https://tools.ietf.org/html/rfc6020#section-9.3.4.
-type Number struct {
-	// Kind is the kind of number (+/-ve, min/max).
-	Kind NumberKind
-	// Absolute value of the number.
-	Value uint64
-	// Number of fractional digits.
-	FractionDigits uint8
+// TypeKindFromName maps the string name used in a YANG file to the enumerated
+// TypeKind used in this library.
+var TypeKindFromName = map[string]TypeKind{
+	"none":                Ynone,
+	"int8":                Yint8,
+	"int16":               Yint16,
+	"int32":               Yint32,
+	"int64":               Yint64,
+	"uint8":               Yuint8,
+	"uint16":              Yuint16,
+	"uint32":              Yuint32,
+	"uint64":              Yuint64,
+	"binary":              Ybinary,
+	"bits":                Ybits,
+	"boolean":             Ybool,
+	"decimal64":           Ydecimal64,
+	"empty":               Yempty,
+	"enumeration":         Yenum,
+	"identityref":         Yidentityref,
+	"instance-identifier": YinstanceIdentifier,
+	"leafref":             Yleafref,
+	"string":              Ystring,
+	"union":               Yunion,
 }
 
-var maxNumber = Number{Kind: MaxNumber}
-var minNumber = Number{Kind: MinNumber}
-
-// IsDecimal reports whether n is a decimal number.
-func (n Number) IsDecimal() bool {
-	return n.FractionDigits != 0
+// TypeKindToName maps the enumerated type used in this library to the string
+// used in a YANG file.
+var TypeKindToName = map[TypeKind]string{
+	Ynone:               "none",
+	Yint8:               "int8",
+	Yint16:              "int16",
+	Yint32:              "int32",
+	Yint64:              "int64",
+	Yuint8:              "uint8",
+	Yuint16:             "uint16",
+	Yuint32:             "uint32",
+	Yuint64:             "uint64",
+	Ybinary:             "binary",
+	Ybits:               "bits",
+	Ybool:               "boolean",
+	Ydecimal64:          "decimal64",
+	Yempty:              "empty",
+	Yenum:               "enumeration",
+	Yidentityref:        "identityref",
+	YinstanceIdentifier: "instance-identifier",
+	Yleafref:            "leafref",
+	Ystring:             "string",
+	Yunion:              "union",
 }
 
-// String returns n as a string in decimal.
-func (n Number) String() string {
-	var out string
-	switch n.Kind {
-	case MinNumber:
-		return "min"
-	case MaxNumber:
-		return "max"
-	}
-
-	out += strconv.FormatUint(n.Value, 10)
-
-	if n.IsDecimal() {
-		fd := int(n.FractionDigits)
-		if fd > 0 {
-			ofd := len(out) - fd
-			if ofd <= 0 {
-				// We want 0.1 not .1
-				out = space18[:-ofd+1] + out
-				ofd = 1
-			}
-			out = out[:ofd] + "." + out[ofd:]
-		}
-	}
-	if n.Kind == Negative {
-		out = "-" + out
+func (k TypeKind) String() string {
+	if s := TypeKindToName[k]; s != "" {
+		return s
 	}
+	return fmt.Sprintf("unknown-type-%d", k)
+}
 
-	return out
+// A EnumType represents a mapping of strings to integers.  It is used both
+// for enumerations as well as bitfields.
+type EnumType struct {
+	last     int64 // maximum value assigned thus far
+	min      int64 // minimum value allowed
+	max      int64 // maximum value allowed
+	unique   bool  // numeric values must be unique (enums)
+	toString map[int64]string
+	toInt    map[string]int64
 }
 
-// Int returns n as an int64.  It returns an error if n overflows an int64 or
-// the number is decimal.
-func (n Number) Int() (int64, error) {
-	nv := n.Value
-	if n.IsDecimal() {
-		nv = n.Value / uint64(math.Pow10(int(n.FractionDigits)))
-	}
-	switch n.Kind {
-	case MinNumber:
-		return MinInt64, nil
-	case MaxNumber:
-		return MaxInt64, nil
-	case Negative:
-		switch {
-		case nv == AbsMinInt64:
-			return MinInt64, nil
-		case nv < AbsMinInt64:
-			return -int64(nv), nil
-		}
-	case Positive:
-		if n.Value <= MaxInt64 {
-			return int64(nv), nil
-		}
-		return 0, errors.New("signed integer overflow")
-	default:
+// NewEnumType returns an initialized EnumType.
+func NewEnumType() *EnumType {
+	return &EnumType{
+		last:     -1, // +1 will start at 0
+		min:      MinEnum,
+		max:      MaxEnum,
+		unique:   true,
+		toString: map[int64]string{},
+		toInt:    map[string]int64{},
 	}
-	return 0, errors.New("unknown number type")
 }
 
-// addQuantum adds the smallest quantum to n without checking overflow.
-func (n Number) addQuantum(i uint64) Number {
-	switch n.Kind {
-	case MinNumber:
-		return n
-	case MaxNumber:
-		return n
-	case Negative:
-		if n.Value <= i {
-			n.Value = i - n.Value
-			n.Kind = Positive
-		} else {
-			n.Value -= i
-		}
-	case Positive:
-		n.Value += i
-	default:
-		panic("add to unknown number type")
+// NewBitfield returns an EnumType initialized as a bitfield.  Multiple string
+// values may map to the same numeric values.  Numeric values must be small
+// non-negative integers.
+func NewBitfield() *EnumType {
+	return &EnumType{
+		last:     -1, // +1 will start at 0
+		min:      0,
+		max:      MaxBitfieldSize - 1,
+		toString: map[int64]string{},
+		toInt:    map[string]int64{},
 	}
-	return n
 }
 
-// Less returns true if n is less than m. Panics if n and m are a mix of integer
-// and decimal.
-func (n Number) Less(m Number) bool {
-	switch {
-	case m.Kind == MinNumber:
-		return false
-	case n.Kind == MinNumber:
-		return true
-	case n.Kind == MaxNumber:
-		return false
-	case m.Kind == MaxNumber:
-		return true
-	case n.Kind == Negative && m.Kind != Negative:
-		return true
-	case n.Kind != Negative && m.Kind == Negative:
-		return false
+// Set sets name in e to the provided value.  Set returns an error if the value
+// is invalid, name is already signed, or when used as an enum rather than a
+// bitfield, the value has previousl been used.  When two different names are
+// assigned to the same value, the conversion from value to name will result in
+// the most recently assigned name.
+func (e *EnumType) Set(name string, value int64) error {
+	if _, ok := e.toInt[name]; ok {
+		return fmt.Errorf("field %s already assigned", name)
 	}
-
-	nt, mt := n.Trunc(), m.Trunc()
-	lt := nt < mt
-	if nt == mt {
-		nf, mf := n.frac(), m.frac()
-		if nf == mf {
-			return false
-		}
-		lt = nf < mf
+	if oname, ok := e.toString[value]; e.unique && ok {
+		return fmt.Errorf("fields %s and %s conflict on value %d", name, oname, value)
 	}
-
-	if n.Kind == Negative {
-		return !lt
+	if value < e.min {
+		return fmt.Errorf("value %d for %s too small (minimum is %d)", value, name, e.min)
 	}
-	return lt
-}
-
-// Equal returns true if n is equal to m.
-func (n Number) Equal(m Number) bool {
-	return !n.Less(m) && !m.Less(n)
+	if value > e.max {
+		return fmt.Errorf("value %d for %s too large (maximum is %d)", value, name, e.max)
+	}
+	e.toString[value] = name
+	e.toInt[name] = value
+	if value >= e.last {
+		e.last = value
+	}
+	return nil
 }
 
-// Trunc returns the whole part of abs(n) as a signed integer.
-func (n Number) Trunc() uint64 {
-	nv := n.Value
-	e := pow10(n.FractionDigits)
-	return nv / e
+// SetNext sets the name in e using the next possible value that is greater than
+// all previous values.
+func (e *EnumType) SetNext(name string) error {
+	if e.last == MaxEnum {
+		return fmt.Errorf("enum must specify value")
+	}
+	return e.Set(name, e.last+1)
 }
 
-// frac returns the fraction part with a precision of 18 fractional digits.
-// E.g. if n is 3.1 then n.frac() returns 100,000,000,000,000,000
-func (n Number) frac() uint64 {
-	frac := n.FractionDigits
-	i := n.Trunc() * pow10(frac)
-	return (n.Value - i) * pow10(uint8(18-frac))
-}
+// Name returns the name in e associated with value.  The empty string is
+// returned if no name has been assigned to value.
+func (e *EnumType) Name(value int64) string { return e.toString[value] }
 
-// YRange is a single range of consecutive numbers, inclusive.
-type YRange struct {
-	Min Number
-	Max Number
-}
+// Value returns the value associated with name in e associated.  0 is returned
+// if name is not in e, or if it is the first value in an unnumbered enum. Use
+// IsDefined to definitively confirm name is in e.
+func (e *EnumType) Value(name string) int64 { return e.toInt[name] }
 
-// Valid returns false if r is not a valid range (min > max).
-func (r YRange) Valid() bool {
-	return !r.Max.Less(r.Min)
+// IsDefined returns true if name is defined in e, else false.
+func (e *EnumType) IsDefined(name string) bool {
+	_, defined := e.toInt[name]
+	return defined
 }
 
-// String returns r as a string using YANG notation, either a simple
-// value if min == max or min..max.
-func (r YRange) String() string {
-	if r.Min.Equal(r.Max) {
-		return r.Min.String()
+// Names returns the sorted list of enum string names.
+func (e *EnumType) Names() []string {
+	names := make([]string, len(e.toInt))
+	i := 0
+	for name := range e.toInt {
+		names[i] = name
+		i++
 	}
-	return r.Min.String() + ".." + r.Max.String()
+	sort.Strings(names)
+	return names
 }
 
-func (r YRange) Equal(s YRange) bool {
-	return r.Min.Equal(s.Min) && r.Max.Equal(s.Max)
-}
+type int64Slice []int64
 
-// A YangRange is a set of non-overlapping ranges.
-type YangRange []YRange
+func (p int64Slice) Len() int           { return len(p) }
+func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
+func (p int64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
 
-// String returns the ranges r using YANG notation.  Individual ranges
-// are separated by pipes (|).
-func (r YangRange) String() string {
-	s := make([]string, len(r))
-	for i, r := range r {
-		s[i] = r.String()
+// Values returns the sorted list of enum values.
+func (e *EnumType) Values() []int64 {
+	values := make([]int64, len(e.toInt))
+	i := 0
+	for _, value := range e.toInt {
+		values[i] = value
+		i++
 	}
-	return strings.Join(s, "|")
+	sort.Sort(int64Slice(values))
+	return values
 }
 
-func (r YangRange) Len() int      { return len(r) }
-func (r YangRange) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
-func (r YangRange) Less(i, j int) bool {
-	switch {
-	case r[i].Min.Less(r[j].Min):
-		return true
-	case r[j].Min.Less(r[i].Min):
-		return false
-	default:
-		return r[i].Max.Less(r[j].Max)
+// NameMap returns a map of names to values.
+func (e *EnumType) NameMap() map[string]int64 {
+	m := make(map[string]int64, len(e.toInt))
+	for name, value := range e.toInt {
+		m[name] = value
 	}
+	return m
 }
 
-// Validate returns an error if r has either an invalid range or has
-// overlapping ranges.
-// r is expected to be sorted use YangRange.Sort()
-func (r YangRange) Validate() error {
-	if !sort.IsSorted(r) {
-		return errors.New("range not sorted")
-	}
-	switch {
-	case len(r) == 0:
-		return nil
-	case !r[0].Valid():
-		return errors.New("invalid number")
-	}
-	p := r[0]
-
-	for _, n := range r[1:] {
-		if n.Min.Less(p.Max) {
-			return errors.New("overlapping ranges")
-		}
+// ValueMap returns a map of values to names.
+func (e *EnumType) ValueMap() map[int64]string {
+	m := make(map[int64]string, len(e.toString))
+	for name, value := range e.toString {
+		m[name] = value
 	}
-	return nil
+	return m
 }
 
-// Sort r. Must be called before Validate and coalesce if unsorted
-func (r YangRange) Sort() {
-	sort.Sort(r)
+// A YangType is the internal representation of a type in YANG.  It may
+// refer to either a builtin type or type specified with typedef.  Not
+// all fields in YangType are used for all types.
+type YangType struct {
+	Name             string
+	Kind             TypeKind    // Ynone if not a base type
+	Base             *Type       `json:"-"`          // Base type for non-builtin types
+	IdentityBase     *Identity   `json:",omitempty"` // Base statement for a type using identityref
+	Root             *YangType   `json:"-"`          // root of this type that is the same
+	Bit              *EnumType   `json:",omitempty"` // bit position, "status" is lost
+	Enum             *EnumType   `json:",omitempty"` // enum name to value, "status" is lost
+	Units            string      `json:",omitempty"` // units to be used for this type
+	Default          string      `json:",omitempty"` // default value, if any
+	FractionDigits   int         `json:",omitempty"` // decimal64 fixed point precision
+	Length           YangRange   `json:",omitempty"` // this should be processed by section 12
+	OptionalInstance bool        `json:",omitempty"` // !require-instances which defaults to true
+	Path             string      `json:",omitempty"` // the path in a leafref
+	Pattern          []string    `json:",omitempty"` // limiting XSD-TYPES expressions on strings
+	POSIXPattern     []string    `json:",omitempty"` // limiting POSIX ERE on strings (specified by openconfig-extensions:posix-pattern)
+	Range            YangRange   `json:",omitempty"` // range for integers
+	Type             []*YangType `json:",omitempty"` // for unions
 }
 
-// Equal returns true if ranges r and q are identically equivalent.
-// TODO(borman): should we coalesce ranges in the comparison?
-func (r YangRange) Equal(q YangRange) bool {
-	if len(r) != len(q) {
+// BaseTypedefs is a map of all base types to the Typedef structure manufactured
+// for the type.
+var BaseTypedefs = map[string]*Typedef{}
+
+// typedef returns a Typedef created from y for insertion into the BaseTypedefs
+// map.
+func (y *YangType) typedef() *Typedef {
+	return &Typedef{
+		Name:   y.Name,
+		Source: &Statement{},
+		Type: &Type{
+			Name:     y.Name,
+			Source:   &Statement{},
+			YangType: y,
+		},
+		YangType: y,
+	}
+}
+
+// ssEqual returns true if the two slices are equivalent.
+func ssEqual(s1, s2 []string) bool {
+	if len(s1) != len(s2) {
 		return false
 	}
-	for i, r := range r {
-		if !r.Equal(q[i]) {
+	for x, s := range s1 {
+		if s != s2[x] {
 			return false
 		}
 	}
 	return true
 }
 
-// Contains returns true if all possible values in s are also possible values
-// in r.  An empty range is assumed to be min..max.
-// TODO(wenbli): The current behaviour of this is confusing and unexplained by
-// comments: why does the presence of min/max always cause the range to be
-// considered as contained? Should test on current schema, and if there are no
-// problems, change it to its natural definition.
-func (r YangRange) Contains(s YangRange) bool {
-	// No range means allow everything in range for the type.
-	if len(s) == 0 || len(r) == 0 {
-		return true
+// tsEqual returns true if the two Type slices are identical.
+func tsEqual(t1, t2 []*YangType) bool {
+	if len(t1) != len(t2) {
+		return false
 	}
-
-	// Check if every range in s is subsumed under r.
-	// Both range lists should be in order and non-adjacent (coalesced).
-	ri := 0
-	for _, ss := range s {
-		// min is always within range
-		if ss.Min.Kind != MinNumber {
-			for r[ri].Max.Less(ss.Min) {
-				ri += 1
-				if ri == len(r) {
-					return false
-				}
-			}
-		}
-		if (ss.Max.Kind == MaxNumber) || (ss.Min.Kind == MinNumber) {
-			continue
-		}
-		if ss.Min.Less(r[ri].Min) || r[ri].Max.Less(ss.Max) {
+	// For now we compare absolute pointers.
+	// This may be wrong.
+	for x, t := range t1 {
+		if !t.Equal(t2[x]) {
 			return false
 		}
 	}
 	return true
 }
 
+// Equal returns true if y and t describe the same type.
+func (y *YangType) Equal(t *YangType) bool {
+	switch {
+	case
+		// Don't check the Name, it contains no information
+		y.Kind != t.Kind,
+		y.Units != t.Units,
+		y.Default != t.Default,
+		y.FractionDigits != t.FractionDigits,
+		y.IdentityBase != t.IdentityBase,
+		len(y.Length) != len(t.Length),
+		!y.Length.Equal(t.Length),
+		y.OptionalInstance != t.OptionalInstance,
+		y.Path != t.Path,
+		!ssEqual(y.Pattern, t.Pattern),
+		!ssEqual(y.POSIXPattern, t.POSIXPattern),
+		len(y.Range) != len(t.Range),
+		!y.Range.Equal(t.Range),
+		!tsEqual(y.Type, t.Type):
+
+		return false
+	}
+	// TODO(borman): Base, Bit, Enum
+	return true
+}
+
+// Install builtin types as know types
+func init() {
+	for k, v := range baseTypes {
+		// Base types are always their own root
+		v.Root = v
+		BaseTypedefs[k] = v.typedef()
+	}
+}
+
+var baseTypes = map[string]*YangType{
+	"int8": {
+		Name:  "int8",
+		Kind:  Yint8,
+		Range: Int8Range,
+	},
+	"int16": {
+		Name:  "int16",
+		Kind:  Yint16,
+		Range: Int16Range,
+	},
+	"int32": {
+		Name:  "int32",
+		Kind:  Yint32,
+		Range: Int32Range,
+	},
+	"int64": {
+		Name:  "int64",
+		Kind:  Yint64,
+		Range: Int64Range,
+	},
+	"uint8": {
+		Name:  "uint8",
+		Kind:  Yuint8,
+		Range: Uint8Range,
+	},
+	"uint16": {
+		Name:  "uint16",
+		Kind:  Yuint16,
+		Range: Uint16Range,
+	},
+	"uint32": {
+		Name:  "uint32",
+		Kind:  Yuint32,
+		Range: Uint32Range,
+	},
+	"uint64": {
+		Name:  "uint64",
+		Kind:  Yuint64,
+		Range: Uint64Range,
+	},
+
+	"decimal64": {
+		Name:  "decimal64",
+		Kind:  Ydecimal64,
+		Range: Decimal64Range,
+	},
+	"string": {
+		Name: "string",
+		Kind: Ystring,
+	},
+	"boolean": {
+		Name: "boolean",
+		Kind: Ybool,
+	},
+	"enumeration": {
+		Name: "enumeration",
+		Kind: Yenum,
+	},
+	"bits": {
+		Name: "bits",
+		Kind: Ybits,
+	},
+	"binary": {
+		Name: "binary",
+		Kind: Ybinary,
+	},
+	"leafref": {
+		Name: "leafref",
+		Kind: Yleafref,
+	},
+	"identityref": {
+		Name: "identityref",
+		Kind: Yidentityref,
+	},
+	"empty": {
+		Name: "empty",
+		Kind: Yempty,
+	},
+	"union": {
+		Name: "union",
+		Kind: Yunion,
+	},
+	"instance-identifier": {
+		Name: "instance-identifier",
+		Kind: YinstanceIdentifier,
+	},
+}
+
+// These are the default ranges defined by the YANG standard.
+var (
+	Int8Range  = mustParseRangesInt("-128..127")
+	Int16Range = mustParseRangesInt("-32768..32767")
+	Int32Range = mustParseRangesInt("-2147483648..2147483647")
+	Int64Range = mustParseRangesInt("-9223372036854775808..9223372036854775807")
+
+	Uint8Range  = mustParseRangesInt("0..255")
+	Uint16Range = mustParseRangesInt("0..65535")
+	Uint32Range = mustParseRangesInt("0..4294967295")
+	Uint64Range = mustParseRangesInt("0..18446744073709551615")
+
+	Decimal64Range = mustParseRangesDecimal("min..max", 1)
+)
+
+const (
+	// MaxInt64 corresponds to the maximum value of a signed int64.
+	MaxInt64 = 1<<63 - 1
+	// MinInt64 corresponds to the maximum value of a signed int64.
+	MinInt64 = -1 << 63
+	// AbsMinInt64 is the absolute value of MinInt64.
+	AbsMinInt64 = 1 << 63
+	// MaxEnum is the maximum value of an enumeration.
+	MaxEnum = 1<<31 - 1
+	// MinEnum is the minimum value of an enumeration.
+	MinEnum = -1 << 31
+	// MaxBitfieldSize is the maximum number of bits in a bitfield.
+	MaxBitfieldSize = 1 << 32
+	// MaxFractionDigits is the maximum number of fractional digits as per RFC6020 Section 9.3.4.
+	MaxFractionDigits uint8 = 18
+)
+
+type NumberKind int
+
+const (
+	// Positive indicates that a Number is non-negative.
+	Positive = NumberKind(iota)
+	// Negative indicates that a Number is negative.
+	Negative
+	// MinNumber indicates that the Number is the minimum value allowed for the range.
+	MinNumber
+	// MaxNumber indicates that the Number is the maximum value allowed for the range.
+	MaxNumber
+)
+
+const space18 = "000000000000000000" // used for prepending 0's
+
+// A Number is either an integer the range of [-(1<<64) - 1, (1<<64)-1], or a
+// YANG decimal conforming to https://tools.ietf.org/html/rfc6020#section-9.3.4.
+type Number struct {
+	// Kind is the kind of number (+/-ve, min/max).
+	Kind NumberKind
+	// Absolute value of the number.
+	Value uint64
+	// Number of fractional digits.
+	FractionDigits uint8
+}
+
+var maxNumber = Number{Kind: MaxNumber}
+var minNumber = Number{Kind: MinNumber}
+
+// IsDecimal reports whether n is a decimal number.
+func (n Number) IsDecimal() bool {
+	return n.FractionDigits != 0
+}
+
 // FromInt creates a Number from an int64.
 func FromInt(i int64) Number {
 	if i < 0 {
@@ -466,26 +636,176 @@ func decimalValueFromString(numStr string, fracDigRequired uint8) (n Number, err
 		s = s[:dx] + s[dx+1:]
 	}
 
-	if fracDig > fracDigRequired {
-		return n, fmt.Errorf("%s has too much precision, expect <= %d fractional digits", s, fracDigRequired)
-	}
+	if fracDig > fracDigRequired {
+		return n, fmt.Errorf("%s has too much precision, expect <= %d fractional digits", s, fracDigRequired)
+	}
+
+	s += space18[:fracDigRequired-fracDig]
+
+	v, err := strconv.ParseInt(s, 10, 64)
+	if err != nil {
+		return n, fmt.Errorf("%s is not a valid decimal number: %s", numStr, err)
+	}
+
+	kind := Positive
+	if v < 0 {
+		kind = Negative
+		v = -v
+	}
+
+	return Number{Kind: kind, Value: uint64(v), FractionDigits: fracDigRequired}, nil
+}
+
+// String returns n as a string in decimal.
+func (n Number) String() string {
+	var out string
+	switch n.Kind {
+	case MinNumber:
+		return "min"
+	case MaxNumber:
+		return "max"
+	}
+
+	out += strconv.FormatUint(n.Value, 10)
+
+	if n.IsDecimal() {
+		fd := int(n.FractionDigits)
+		if fd > 0 {
+			ofd := len(out) - fd
+			if ofd <= 0 {
+				// We want 0.1 not .1
+				out = space18[:-ofd+1] + out
+				ofd = 1
+			}
+			out = out[:ofd] + "." + out[ofd:]
+		}
+	}
+	if n.Kind == Negative {
+		out = "-" + out
+	}
+
+	return out
+}
+
+// Int returns n as an int64.  It returns an error if n overflows an int64 or
+// the number is decimal.
+func (n Number) Int() (int64, error) {
+	nv := n.Value
+	if n.IsDecimal() {
+		nv = n.Value / uint64(math.Pow10(int(n.FractionDigits)))
+	}
+	switch n.Kind {
+	case MinNumber:
+		return MinInt64, nil
+	case MaxNumber:
+		return MaxInt64, nil
+	case Negative:
+		switch {
+		case nv == AbsMinInt64:
+			return MinInt64, nil
+		case nv < AbsMinInt64:
+			return -int64(nv), nil
+		}
+	case Positive:
+		if n.Value <= MaxInt64 {
+			return int64(nv), nil
+		}
+		return 0, errors.New("signed integer overflow")
+	default:
+	}
+	return 0, errors.New("unknown number type")
+}
+
+// addQuantum adds the smallest quantum to n without checking overflow.
+func (n Number) addQuantum(i uint64) Number {
+	switch n.Kind {
+	case MinNumber:
+		return n
+	case MaxNumber:
+		return n
+	case Negative:
+		if n.Value <= i {
+			n.Value = i - n.Value
+			n.Kind = Positive
+		} else {
+			n.Value -= i
+		}
+	case Positive:
+		n.Value += i
+	default:
+		panic("add to unknown number type")
+	}
+	return n
+}
+
+// Less returns true if n is less than m. Panics if n and m are a mix of integer
+// and decimal.
+func (n Number) Less(m Number) bool {
+	switch {
+	case m.Kind == MinNumber:
+		return false
+	case n.Kind == MinNumber:
+		return true
+	case n.Kind == MaxNumber:
+		return false
+	case m.Kind == MaxNumber:
+		return true
+	case n.Kind == Negative && m.Kind != Negative:
+		return true
+	case n.Kind != Negative && m.Kind == Negative:
+		return false
+	}
+
+	nt, mt := n.Trunc(), m.Trunc()
+	lt := nt < mt
+	if nt == mt {
+		nf, mf := n.frac(), m.frac()
+		if nf == mf {
+			return false
+		}
+		lt = nf < mf
+	}
+
+	if n.Kind == Negative {
+		return !lt
+	}
+	return lt
+}
+
+// Equal returns true if n is equal to m.
+func (n Number) Equal(m Number) bool {
+	return !n.Less(m) && !m.Less(n)
+}
 
-	s += space18[:fracDigRequired-fracDig]
+// Trunc returns the whole part of abs(n) as a signed integer.
+func (n Number) Trunc() uint64 {
+	nv := n.Value
+	e := pow10(n.FractionDigits)
+	return nv / e
+}
 
-	v, err := strconv.ParseInt(s, 10, 64)
-	if err != nil {
-		return n, fmt.Errorf("%s is not a valid decimal number: %s", numStr, err)
-	}
+// frac returns the fraction part with a precision of 18 fractional digits.
+// E.g. if n is 3.1 then n.frac() returns 100,000,000,000,000,000
+func (n Number) frac() uint64 {
+	frac := n.FractionDigits
+	i := n.Trunc() * pow10(frac)
+	return (n.Value - i) * pow10(uint8(18-frac))
+}
 
-	kind := Positive
-	if v < 0 {
-		kind = Negative
-		v = -v
-	}
+// YRange is a single range of consecutive numbers, inclusive.
+type YRange struct {
+	Min Number
+	Max Number
+}
 
-	return Number{Kind: kind, Value: uint64(v), FractionDigits: fracDigRequired}, nil
+// Valid returns false if r is not a valid range (min > max).
+func (r YRange) Valid() bool {
+	return !r.Max.Less(r.Min)
 }
 
+// A YangRange is a set of non-overlapping ranges.
+type YangRange []YRange
+
 // ParseRangesInt parses s into a series of ranges. Each individual range is in s
 // is separated by the pipe character (|).  The min and max value of a range
 // are separated by "..".  An error is returned if the range is invalid. The
@@ -594,150 +914,125 @@ func mustParseRangesDecimal(s string, fracDigRequired uint8) YangRange {
 	return r
 }
 
-// Frac returns the fractional part of f.
-func Frac(f float64) float64 {
-	return f - math.Trunc(f)
-}
-
-// pow10 returns 10^e without checking for overflow.
-func pow10(e uint8) uint64 {
-	var out uint64 = 1
-	for i := uint8(0); i < e; i++ {
-		out *= 10
+// String returns r as a string using YANG notation, either a simple
+// value if min == max or min..max.
+func (r YRange) String() string {
+	if r.Min.Equal(r.Max) {
+		return r.Min.String()
 	}
-	return out
-}
-
-// A EnumType represents a mapping of strings to integers.  It is used both
-// for enumerations as well as bitfields.
-type EnumType struct {
-	last     int64 // maximum value assigned thus far
-	min      int64 // minimum value allowed
-	max      int64 // maximum value allowed
-	unique   bool  // numeric values must be unique (enums)
-	toString map[int64]string
-	toInt    map[string]int64
+	return r.Min.String() + ".." + r.Max.String()
 }
 
-// NewEnumType returns an initialized EnumType.
-func NewEnumType() *EnumType {
-	return &EnumType{
-		last:     -1, // +1 will start at 0
-		min:      MinEnum,
-		max:      MaxEnum,
-		unique:   true,
-		toString: map[int64]string{},
-		toInt:    map[string]int64{},
+// String returns the ranges r using YANG notation.  Individual ranges
+// are separated by pipes (|).
+func (r YangRange) String() string {
+	s := make([]string, len(r))
+	for i, r := range r {
+		s[i] = r.String()
 	}
+	return strings.Join(s, "|")
 }
 
-// NewBitfield returns an EnumType initialized as a bitfield.  Multiple string
-// values may map to the same numeric values.  Numeric values must be small
-// non-negative integers.
-func NewBitfield() *EnumType {
-	return &EnumType{
-		last:     -1, // +1 will start at 0
-		min:      0,
-		max:      MaxBitfieldSize - 1,
-		toString: map[int64]string{},
-		toInt:    map[string]int64{},
+func (r YangRange) Len() int      { return len(r) }
+func (r YangRange) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
+func (r YangRange) Less(i, j int) bool {
+	switch {
+	case r[i].Min.Less(r[j].Min):
+		return true
+	case r[j].Min.Less(r[i].Min):
+		return false
+	default:
+		return r[i].Max.Less(r[j].Max)
 	}
 }
 
-// Set sets name in e to the provided value.  Set returns an error if the value
-// is invalid, name is already signed, or when used as an enum rather than a
-// bitfield, the value has previousl been used.  When two different names are
-// assigned to the same value, the conversion from value to name will result in
-// the most recently assigned name.
-func (e *EnumType) Set(name string, value int64) error {
-	if _, ok := e.toInt[name]; ok {
-		return fmt.Errorf("field %s already assigned", name)
-	}
-	if oname, ok := e.toString[value]; e.unique && ok {
-		return fmt.Errorf("fields %s and %s conflict on value %d", name, oname, value)
-	}
-	if value < e.min {
-		return fmt.Errorf("value %d for %s too small (minimum is %d)", value, name, e.min)
-	}
-	if value > e.max {
-		return fmt.Errorf("value %d for %s too large (maximum is %d)", value, name, e.max)
+// Validate returns an error if r has either an invalid range or has
+// overlapping ranges.
+// r is expected to be sorted use YangRange.Sort()
+func (r YangRange) Validate() error {
+	if !sort.IsSorted(r) {
+		return errors.New("range not sorted")
 	}
-	e.toString[value] = name
-	e.toInt[name] = value
-	if value >= e.last {
-		e.last = value
+	switch {
+	case len(r) == 0:
+		return nil
+	case !r[0].Valid():
+		return errors.New("invalid number")
 	}
-	return nil
-}
+	p := r[0]
 
-// SetNext sets the name in e using the next possible value that is greater than
-// all previous values.
-func (e *EnumType) SetNext(name string) error {
-	if e.last == MaxEnum {
-		return fmt.Errorf("enum must specify value")
+	for _, n := range r[1:] {
+		if n.Min.Less(p.Max) {
+			return errors.New("overlapping ranges")
+		}
 	}
-	return e.Set(name, e.last+1)
+	return nil
 }
 
-// Name returns the name in e associated with value.  The empty string is
-// returned if no name has been assigned to value.
-func (e *EnumType) Name(value int64) string { return e.toString[value] }
-
-// Value returns the value associated with name in e associated.  0 is returned
-// if name is not in e, or if it is the first value in an unnumbered enum. Use
-// IsDefined to definitively confirm name is in e.
-func (e *EnumType) Value(name string) int64 { return e.toInt[name] }
-
-// IsDefined returns true if name is defined in e, else false.
-func (e *EnumType) IsDefined(name string) bool {
-	_, defined := e.toInt[name]
-	return defined
+// Sort r. Must be called before Validate and coalesce if unsorted
+func (r YangRange) Sort() {
+	sort.Sort(r)
 }
 
-// Names returns the sorted list of enum string names.
-func (e *EnumType) Names() []string {
-	names := make([]string, len(e.toInt))
-	i := 0
-	for name := range e.toInt {
-		names[i] = name
-		i++
+// Equal returns true if ranges r and q are identically equivalent.
+// TODO(borman): should we coalesce ranges in the comparison?
+func (r YangRange) Equal(q YangRange) bool {
+	if len(r) != len(q) {
+		return false
 	}
-	sort.Strings(names)
-	return names
+	for i, r := range r {
+		if r != q[i] {
+			return false
+		}
+	}
+	return true
 }
 
-type int64Slice []int64
-
-func (p int64Slice) Len() int           { return len(p) }
-func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
-func (p int64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+// Contains returns true if all possible values in s are also possible values
+// in r.  An empty range is assumed to be min..max.
+// TODO(wenbli): The current behaviour of this is confusing and unexplained by
+// comments: why does the presence of min/max always cause the range to be
+// considered as contained? Should test on current schema, and if there are no
+// problems, change it to its natural definition.
+func (r YangRange) Contains(s YangRange) bool {
+	// No range means allow everything in range for the type.
+	if len(s) == 0 || len(r) == 0 {
+		return true
+	}
 
-// Values returns the sorted list of enum values.
-func (e *EnumType) Values() []int64 {
-	values := make([]int64, len(e.toInt))
-	i := 0
-	for _, value := range e.toInt {
-		values[i] = value
-		i++
+	// Check if every range in s is subsumed under r.
+	// Both range lists should be in order and non-adjacent (coalesced).
+	ri := 0
+	for _, ss := range s {
+		// min is always within range
+		if ss.Min.Kind != MinNumber {
+			for r[ri].Max.Less(ss.Min) {
+				ri += 1
+				if ri == len(r) {
+					return false
+				}
+			}
+		}
+		if (ss.Max.Kind == MaxNumber) || (ss.Min.Kind == MinNumber) {
+			continue
+		}
+		if ss.Min.Less(r[ri].Min) || r[ri].Max.Less(ss.Max) {
+			return false
+		}
 	}
-	sort.Sort(int64Slice(values))
-	return values
+	return true
 }
 
-// NameMap returns a map of names to values.
-func (e *EnumType) NameMap() map[string]int64 {
-	m := make(map[string]int64, len(e.toInt))
-	for name, value := range e.toInt {
-		m[name] = value
-	}
-	return m
+// Frac returns the fractional part of f.
+func Frac(f float64) float64 {
+	return f - math.Trunc(f)
 }
 
-// ValueMap returns a map of values to names.
-func (e *EnumType) ValueMap() map[int64]string {
-	m := make(map[int64]string, len(e.toString))
-	for name, value := range e.toString {
-		m[name] = value
+// pow10 returns 10^e without checking for overflow.
+func pow10(e uint8) uint64 {
+	var out uint64 = 1
+	for i := uint8(0); i < e; i++ {
+		out *= 10
 	}
-	return m
+	return out
 }
diff --git a/pkg/yang/yangtype.go b/pkg/yang/yangtype.go
deleted file mode 100644
index 37c9bc5..0000000
--- a/pkg/yang/yangtype.go
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright 2021 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package yang
-
-import "fmt"
-
-var (
-	// TypeKindFromName maps the string name used in a YANG file to the enumerated
-	// TypeKind used in this library.
-	TypeKindFromName = map[string]TypeKind{
-		"none":                Ynone,
-		"int8":                Yint8,
-		"int16":               Yint16,
-		"int32":               Yint32,
-		"int64":               Yint64,
-		"uint8":               Yuint8,
-		"uint16":              Yuint16,
-		"uint32":              Yuint32,
-		"uint64":              Yuint64,
-		"binary":              Ybinary,
-		"bits":                Ybits,
-		"boolean":             Ybool,
-		"decimal64":           Ydecimal64,
-		"empty":               Yempty,
-		"enumeration":         Yenum,
-		"identityref":         Yidentityref,
-		"instance-identifier": YinstanceIdentifier,
-		"leafref":             Yleafref,
-		"string":              Ystring,
-		"union":               Yunion,
-	}
-
-	// TypeKindToName maps the enumerated type used in this library to the string
-	// used in a YANG file.
-	TypeKindToName = map[TypeKind]string{
-		Ynone:               "none",
-		Yint8:               "int8",
-		Yint16:              "int16",
-		Yint32:              "int32",
-		Yint64:              "int64",
-		Yuint8:              "uint8",
-		Yuint16:             "uint16",
-		Yuint32:             "uint32",
-		Yuint64:             "uint64",
-		Ybinary:             "binary",
-		Ybits:               "bits",
-		Ybool:               "boolean",
-		Ydecimal64:          "decimal64",
-		Yempty:              "empty",
-		Yenum:               "enumeration",
-		Yidentityref:        "identityref",
-		YinstanceIdentifier: "instance-identifier",
-		Yleafref:            "leafref",
-		Ystring:             "string",
-		Yunion:              "union",
-	}
-
-	// BaseTypedefs is a map of all base types to the Typedef structure manufactured
-	// for the type.
-	BaseTypedefs = map[string]*Typedef{}
-
-	baseTypes = map[string]*YangType{
-		"int8": {
-			Name:  "int8",
-			Kind:  Yint8,
-			Range: Int8Range,
-		},
-		"int16": {
-			Name:  "int16",
-			Kind:  Yint16,
-			Range: Int16Range,
-		},
-		"int32": {
-			Name:  "int32",
-			Kind:  Yint32,
-			Range: Int32Range,
-		},
-		"int64": {
-			Name:  "int64",
-			Kind:  Yint64,
-			Range: Int64Range,
-		},
-		"uint8": {
-			Name:  "uint8",
-			Kind:  Yuint8,
-			Range: Uint8Range,
-		},
-		"uint16": {
-			Name:  "uint16",
-			Kind:  Yuint16,
-			Range: Uint16Range,
-		},
-		"uint32": {
-			Name:  "uint32",
-			Kind:  Yuint32,
-			Range: Uint32Range,
-		},
-		"uint64": {
-			Name:  "uint64",
-			Kind:  Yuint64,
-			Range: Uint64Range,
-		},
-
-		"decimal64": {
-			Name:  "decimal64",
-			Kind:  Ydecimal64,
-			Range: Decimal64Range,
-		},
-		"string": {
-			Name: "string",
-			Kind: Ystring,
-		},
-		"boolean": {
-			Name: "boolean",
-			Kind: Ybool,
-		},
-		"enumeration": {
-			Name: "enumeration",
-			Kind: Yenum,
-		},
-		"bits": {
-			Name: "bits",
-			Kind: Ybits,
-		},
-		"binary": {
-			Name: "binary",
-			Kind: Ybinary,
-		},
-		"leafref": {
-			Name: "leafref",
-			Kind: Yleafref,
-		},
-		"identityref": {
-			Name: "identityref",
-			Kind: Yidentityref,
-		},
-		"empty": {
-			Name: "empty",
-			Kind: Yempty,
-		},
-		"union": {
-			Name: "union",
-			Kind: Yunion,
-		},
-		"instance-identifier": {
-			Name: "instance-identifier",
-			Kind: YinstanceIdentifier,
-		},
-	}
-)
-
-// Install builtin types as know types
-func init() {
-	for k, v := range baseTypes {
-		// Base types are always their own root
-		v.Root = v
-		BaseTypedefs[k] = v.typedef()
-	}
-}
-
-// TypeKind is the enumeration of the base types available in YANG.  It
-// is analogous to reflect.Kind.
-type TypeKind uint
-
-func (k TypeKind) String() string {
-	if s := TypeKindToName[k]; s != "" {
-		return s
-	}
-	return fmt.Sprintf("unknown-type-%d", k)
-}
-
-const (
-	// Ynone represents the invalid (unset) type.
-	Ynone = TypeKind(iota)
-	// Yint8 is an int in the range [-128, 127].
-	Yint8
-	// Yint16 is an int in the range [-32768, 32767].
-	Yint16
-	// Yint32 is an int in the range [-2147483648, 2147483647].
-	Yint32
-	// Yint64 is an int in the range [-9223372036854775808, 9223372036854775807]
-	Yint64
-	// Yuint8 is an int in the range [0, 255]
-	Yuint8
-	// Yuint16 is an int in the range [0, 65535]
-	Yuint16
-	// Yuint32 is an int in the range [0, 4294967295]
-	Yuint32
-	// Yuint64 is an int in the range [0, 18446744073709551615]
-	Yuint64
-
-	// Ybinary stores arbitrary data.
-	Ybinary
-	// Ybits is a named set of bits or flags.
-	Ybits
-	// Ybool is true or false.
-	Ybool
-	// Ydecimal64 is a signed decimal number.
-	Ydecimal64
-	// Yempty has no associated value.
-	Yempty
-	// Yenum stores enumerated strings.
-	Yenum
-	// Yidentityref stores an extensible enumeration.
-	Yidentityref
-	// YinstanceIdentifier stores a reference to a data tree node.
-	YinstanceIdentifier
-	// Yleafref stores a reference to a leaf instance.
-	Yleafref
-	// Ystring is a human readable string.
-	Ystring
-	// Yunion is a choice of types.
-	Yunion
-)
-
-// A YangType is the internal representation of a type in YANG.  It may
-// refer to either a builtin type or type specified with typedef.  Not
-// all fields in YangType are used for all types.
-type YangType struct {
-	Name             string
-	Kind             TypeKind    // Ynone if not a base type
-	Base             *Type       `json:"-"`          // Base type for non-builtin types
-	IdentityBase     *Identity   `json:",omitempty"` // Base statement for a type using identityref
-	Root             *YangType   `json:"-"`          // root of this type that is the same
-	Bit              *EnumType   `json:",omitempty"` // bit position, "status" is lost
-	Enum             *EnumType   `json:",omitempty"` // enum name to value, "status" is lost
-	Units            string      `json:",omitempty"` // units to be used for this type
-	Default          string      `json:",omitempty"` // default value, if any
-	FractionDigits   int         `json:",omitempty"` // decimal64 fixed point precision
-	Length           YangRange   `json:",omitempty"` // this should be processed by section 12
-	OptionalInstance bool        `json:",omitempty"` // !require-instances which defaults to true
-	Path             string      `json:",omitempty"` // the path in a leafref
-	Pattern          []string    `json:",omitempty"` // limiting XSD-TYPES expressions on strings
-	POSIXPattern     []string    `json:",omitempty"` // limiting POSIX ERE on strings (specified by openconfig-extensions:posix-pattern)
-	Range            YangRange   `json:",omitempty"` // range for integers
-	Type             []*YangType `json:",omitempty"` // for unions
-}
-
-// Equal returns true if y and t describe the same type.
-func (y *YangType) Equal(t *YangType) bool {
-	switch {
-	case
-		// Don't check the Name, it contains no information
-		y.Kind != t.Kind,
-		y.Units != t.Units,
-		y.Default != t.Default,
-		y.FractionDigits != t.FractionDigits,
-		y.IdentityBase != t.IdentityBase,
-		len(y.Length) != len(t.Length),
-		!y.Length.Equal(t.Length),
-		y.OptionalInstance != t.OptionalInstance,
-		y.Path != t.Path,
-		!ssEqual(y.Pattern, t.Pattern),
-		!ssEqual(y.POSIXPattern, t.POSIXPattern),
-		len(y.Range) != len(t.Range),
-		!y.Range.Equal(t.Range),
-		!tsEqual(y.Type, t.Type):
-
-		return false
-	}
-	// TODO(borman): Base, Bit, Enum
-	return true
-}
-
-// typedef returns a Typedef created from y for insertion into the BaseTypedefs
-// map.
-func (y *YangType) typedef() *Typedef {
-	return &Typedef{
-		Name:   y.Name,
-		Source: &Statement{},
-		Type: &Type{
-			Name:     y.Name,
-			Source:   &Statement{},
-			YangType: y,
-		},
-		YangType: y,
-	}
-}
-
-// ssEqual returns true if the two slices are equivalent.
-func ssEqual(s1, s2 []string) bool {
-	if len(s1) != len(s2) {
-		return false
-	}
-	for x, s := range s1 {
-		if s != s2[x] {
-			return false
-		}
-	}
-	return true
-}
-
-// tsEqual returns true if the two Type slices are identical.
-func tsEqual(t1, t2 []*YangType) bool {
-	if len(t1) != len(t2) {
-		return false
-	}
-	// For now we compare absolute pointers.
-	// This may be wrong.
-	for x, t := range t1 {
-		if !t.Equal(t2[x]) {
-			return false
-		}
-	}
-	return true
-}


Test patch:
diff --git a/pkg/yang/ast_test.go b/pkg/yang/ast_test.go
index 48a7326..f109dca 100644
--- a/pkg/yang/ast_test.go
+++ b/pkg/yang/ast_test.go
@@ -207,7 +207,7 @@ func TestAST(t *testing.T) {
 	type meta struct {
 		MainNode []*MainNode `yang:"main_node"`
 	}
-	initTypes(reflect.TypeOf(&meta{}), &typeDict)
+	initTypes(reflect.TypeOf(&meta{}))
 
 	for _, tt := range []struct {
 		line int
diff --git a/pkg/yang/entry_test.go b/pkg/yang/entry_test.go
index bb5d847..0f15201 100644
--- a/pkg/yang/entry_test.go
+++ b/pkg/yang/entry_test.go
@@ -274,22 +274,6 @@ module baz {
 		}
 		`,
 	},
-	{
-		name: "qux-augment.yang",
-		in: `
-		submodule qux-augment {
-		  belongs-to qux {
-		    prefix "qux";
-		  }
-
-		  import foo { prefix "f"; }
-
-		  augment "/f:foo-c" {
-			leaf qux-submod-leaf { type string; }
-		  }
-	    }
-		`,
-	},
 }
 
 func TestUsesParent(t *testing.T) {
@@ -360,11 +344,10 @@ func TestEntryNamespace(t *testing.T) {
 	bar, _ := ms.GetModule("bar")
 
 	for _, tc := range []struct {
-		descr        string
-		entry        *Entry
-		ns           string
-		wantMod      string
-		wantModError string
+		descr   string
+		entry   *Entry
+		ns      string
+		wantMod string
 	}{
 		{
 			descr:   "grouping used in foo always have foo's namespace, even if it was defined in bar",
@@ -396,18 +379,6 @@ func TestEntryNamespace(t *testing.T) {
 			ns:      "urn:baz",
 			wantMod: "baz",
 		},
-		{
-			descr:   "leaf directly defined within an augment to foo from submodule baz-augment of baz has baz's namespace",
-			entry:   foo.Dir["foo-c"].Dir["baz-submod-leaf"],
-			ns:      "urn:baz",
-			wantMod: "baz",
-		},
-		{
-			descr:        "leaf directly defined within an augment to foo from orphan submodule qux-augment has empty namespace",
-			entry:        foo.Dir["foo-c"].Dir["qux-submod-leaf"],
-			ns:           "",
-			wantModError: `could not find module "" when retrieving namespace for qux-submod-leaf`,
-		},
 		{
 			descr:   "children of a container within an augment to from baz have baz's namespace",
 			entry:   foo.Dir["foo-c"].Dir["baz-dir"].Dir["aardvark"],
@@ -424,14 +395,7 @@ func TestEntryNamespace(t *testing.T) {
 
 		m, err := tc.entry.InstantiatingModule()
 		if err != nil {
-			if tc.wantModError == "" {
-				t.Errorf("%s: %s.InstantiatingModule(): got unexpected error: %v", tc.descr, tc.entry.Path(), err)
-			} else if got := err.Error(); got != tc.wantModError {
-				t.Errorf("%s: %s.InstantiatingModule(): got error: %q, want: %q", tc.descr, tc.entry.Path(), got, tc.wantModError)
-			}
-			continue
-		} else if tc.wantModError != "" {
-			t.Errorf("%s: %s.InstantiatingModule(): got no error, want: %q", tc.descr, tc.entry.Path(), tc.wantModError)
+			t.Errorf("%s: %s.InstantiatingModule(): got unexpected error: %v", tc.descr, tc.entry.Path(), err)
 			continue
 		}
 
@@ -3289,8 +3253,8 @@ func TestLeafEntry(t *testing.T) {
 			if got, want := e.Mandatory, TSUnset; got != want {
 				t.Errorf("got %d, want %d", got, want)
 			}
-			if got, want := e.Type.Range, (YangRange{Rf(-120, 120, 1)}); !cmp.Equal(got, want) {
-				t.Errorf("Range got: %v, want: %v", got, want)
+			if diff := cmp.Diff(e.Type.Range, YangRange{Rf(-120, 120, 1)}); diff != "" {
+				t.Errorf("Range (-got, +want):\n%s", diff)
 			}
 		},
 	}, {
diff --git a/pkg/yang/identity_test.go b/pkg/yang/identity_test.go
index 536d24a..e8c74d7 100644
--- a/pkg/yang/identity_test.go
+++ b/pkg/yang/identity_test.go
@@ -161,46 +161,14 @@ var basicTestCases = []identityTestCase{
 				`},
 		},
 		identities: []identityOut{
-			{module: "idtest-one", name: "TEST_ID"},
+			// TODO(wenbli): Should this show up?
+			//   The reason this doesn't show up is because
+			//   Entry.merge doesn't merge the Identities field.
+			// {module: "idtest-one", name: "TEST_ID"},
 			{module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}},
 		},
 		err: "basic-test-case-4: could not resolve identities",
 	},
-	{
-		name: "basic-test-case-5: Check identity base is found from module.",
-		in: []inputModule{
-			{
-				name: "idtest-one",
-				content: `
-					module idtest-one {
-					  namespace "urn:idone";
-					  prefix "idone";
-
-					  include "idtest-one-sub";
-
-					  identity TEST_ID;
-					}
-				`},
-			{
-				name: "idtest-one-sub",
-				content: `
-					submodule idtest-one-sub {
-					  belongs-to idtest-one {
-					    prefix "idone";
-					  }
-
-					  identity TEST_ID_DERIVED {
-					    base TEST_ID;
-					  }
-					}
-				`},
-		},
-		identities: []identityOut{
-			{module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}},
-			{module: "idtest-one", name: "TEST_ID"},
-		},
-		err: "basic-test-case-5: could not resolve identities",
-	},
 }
 
 // Test the ability to extract identities from a module with the correct base
@@ -213,7 +181,7 @@ func TestIdentityExtract(t *testing.T) {
 		}
 
 		for _, ti := range tt.identities {
-			_, err := ms.GetModule(ti.module)
+			parsedMod, err := ms.GetModule(ti.module)
 
 			if err != nil {
 				t.Errorf("Could not parse module : %s", ti.module)
@@ -222,21 +190,16 @@ func TestIdentityExtract(t *testing.T) {
 
 			foundIdentity := false
 			var thisID *Identity
-			for _, ri := range identities.dict {
-				// TODO(wenbli): Use definingModule helper from ygot after it's moved to goyang.
-				moduleName := ri.Module.Name
-				if ri.Module.Kind() == "submodule" {
-					moduleName = ri.Module.BelongsTo.Name
-				}
-				if ri.Identity.Name == ti.name && moduleName == ti.module {
+			for _, identity := range parsedMod.Identities {
+				if identity.Name == ti.name {
 					foundIdentity = true
-					thisID = ri.Identity
+					thisID = identity
 					break
 				}
 			}
 
-			if !foundIdentity {
-				t.Errorf("Could not find identity %s in module %s, identity dict:\n%+v", ti.name, ti.module, identities.dict)
+			if foundIdentity == false {
+				t.Errorf("Could not find identity %s in module %s", ti.name, ti.module)
 				continue
 			}
 
diff --git a/pkg/yang/lex_test.go b/pkg/yang/lex_test.go
index 7f4e912..da93225 100644
--- a/pkg/yang/lex_test.go
+++ b/pkg/yang/lex_test.go
@@ -46,64 +46,64 @@ Tests:
 	}{
 		{line(), "", nil},
 		{line(), "bob", []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 		}},
 		{line(), "bob //bob", []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 		}},
 		{line(), "/the/path", []*token{
-			T(tIdentifier, "/the/path"),
+			T(tUnquoted, "/the/path"),
 		}},
 		{line(), "+the/path", []*token{
-			T(tIdentifier, "+the/path"),
+			T(tUnquoted, "+the/path"),
 		}},
 		{line(), "+the+path", []*token{
-			T(tIdentifier, "+the+path"),
+			T(tUnquoted, "+the+path"),
 		}},
 		{line(), "+ the/path", []*token{
-			T(tIdentifier, "+"),
-			T(tIdentifier, "the/path"),
+			T(tUnquoted, "+"),
+			T(tUnquoted, "the/path"),
 		}},
 		{line(), "{bob}", []*token{
 			T('{', "{"),
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T('}', "}"),
 		}},
 		{line(), "bob;fred", []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), "\t bob\t; fred ", []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 	bob;
 	fred
 `, []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 	// This is a comment
 	bob;
 	fred
 `, []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 	/* This is a comment */
 	bob;
 	fred
 `, []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 	/*
@@ -112,28 +112,17 @@ Tests:
 	bob;
 	fred
 `, []*token{
-			T(tIdentifier, "bob"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
-			T(tIdentifier, "fred"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 	bob; // This is bob
 	fred // This is fred
 `, []*token{
-			T(tIdentifier, "bob"),
-			T(';', ";"),
-			T(tIdentifier, "fred"),
-		}},
-		{line(), `
-pattern '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+';
-`, []*token{
-			T(tIdentifier, "pattern"),
-			T(tString, "[a-zA-Z0-9!#$%&"),
-			T(tIdentifier, "+"),
-			T(tString, "'"),
-			T(tIdentifier, "+"),
-			T(tString, "*+/=?^_`{|}~-]+"),
+			T(tUnquoted, "bob"),
 			T(';', ";"),
+			T(tUnquoted, "fred"),
 		}},
 		{line(), `
 // tab indent both lines
@@ -203,7 +192,7 @@ pattern '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+';
 				continue Tests
 			}
 			if len(tt.tokens) > i && !token.Equal(tt.tokens[i]) {
-				t.Errorf("%d, %d: got (%v, %q) want (%v, %q)", tt.line, i, token.code, token.Text, tt.tokens[i].code, tt.tokens[i].Text)
+				t.Errorf("%d: got %v want %v", tt.line, token, tt.tokens[i])
 			}
 		}
 	}
diff --git a/pkg/yang/node_test.go b/pkg/yang/node_test.go
index f90e181..edbea95 100644
--- a/pkg/yang/node_test.go
+++ b/pkg/yang/node_test.go
@@ -303,7 +303,7 @@ func TestNode(t *testing.T) {
 			return nil
 		},
 	}, {
-		desc: "Test matchingExtensions",
+		desc: "Test MatchingExtensions",
 		inFn: func(ms *Modules) (Node, error) {
 
 			m, err := ms.FindModuleByPrefix("t")
@@ -396,7 +396,7 @@ func TestNode(t *testing.T) {
 			}
 
 			var bars []string
-			matches, err := matchingExtensions(n, n.Exts(), "foo", "bar")
+			matches, err := MatchingExtensions(n, "foo", "bar")
 			if err != nil {
 				return err
 			}
@@ -405,13 +405,13 @@ func TestNode(t *testing.T) {
 			}
 
 			if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" {
-				return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff)
+				return fmt.Errorf("MatchingExtensions (-got, +want):\n%s", diff)
 			}
 
 			return nil
 		},
 	}, {
-		desc: "Test matchingExtensions when module is not found",
+		desc: "Test MatchingExtensions when module is not found",
 		inFn: func(ms *Modules) (Node, error) {
 
 			m, err := ms.FindModuleByPrefix("t")
@@ -471,7 +471,7 @@ func TestNode(t *testing.T) {
 			}
 
 			var bars []string
-			matches, err := matchingExtensions(n, n.Exts(), "foo", "bar")
+			matches, err := MatchingExtensions(n, "foo", "bar")
 			if err != nil {
 				return err
 			}
@@ -480,7 +480,7 @@ func TestNode(t *testing.T) {
 			}
 
 			if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" {
-				return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff)
+				return fmt.Errorf("MatchingExtensions (-got, +want):\n%s", diff)
 			}
 
 			return nil
diff --git a/pkg/yang/parse_test.go b/pkg/yang/parse_test.go
index 1386771..e0e1998 100644
--- a/pkg/yang/parse_test.go
+++ b/pkg/yang/parse_test.go
@@ -217,27 +217,6 @@ foo1 {
 			},
 		},
 		{line: line(), in: `
-foo1 {
-    key value1;
-    foo2 {
-      pattern '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+'
-            + '(\.[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+)*'
-            + '@'
-            + '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+'
-            + '(\.[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+)*';
-    }
-}
-`,
-			out: []*Statement{
-				S("foo1",
-					SA("key", "value1"),
-					S("foo2",
-						SA("pattern", "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*"),
-					),
-				),
-			},
-		},
-		{line: line(), in: `
  }
 `,
 			err: `test.yang:2:2: unexpected }`,
@@ -250,18 +229,18 @@ id
 		{line: line(), in: `
    {
 `,
-			err: `test.yang:2:4: {: not an identifier`,
+			err: `test.yang:2:4: {: keyword token not an unquoted string`,
 		},
 		{line: line(), in: `
 ;
 `,
-			err: `test.yang:2:1: ;: not an identifier`,
+			err: `test.yang:2:1: ;: keyword token not an unquoted string`,
 		},
 		{line: line(), in: `
 statement one two { }
 `,
-			err: `test.yang:2:15: two: syntax error
-test.yang:2:19: {: not an identifier
+			err: `test.yang:2:15: two: syntax error, expected ';' or nested statements in {}
+test.yang:2:19: {: keyword token not an unquoted string
 test.yang:2:21: unexpected }`,
 		},
 		{line: line(), in: `
@@ -283,7 +262,7 @@ foo {
 		key3: "value\3;
 	}
 }`,
-			err: `test.yang:2:1: {: not an identifier
+			err: `test.yang:2:1: {: keyword token not an unquoted string
 test.yang:4:1: unexpected }
 test.yang:6:8: invalid escape sequence: \V
 test.yang:9:15: invalid escape sequence: \3
diff --git a/pkg/yang/types_test.go b/pkg/yang/types_test.go
index 6856267..ca8bc6c 100644
--- a/pkg/yang/types_test.go
+++ b/pkg/yang/types_test.go
@@ -82,7 +82,7 @@ func TestTypeResolve(t *testing.T) {
 		// in Type.
 	} {
 		// We can initialize a value to ourself, so to it here.
-		errs := tt.in.resolve(&typeDict)
+		errs := tt.in.resolve()
 
 		// TODO(borman):  Do not hack out Root and Base.  These
 		// are hacked out for now because they can be self-referential,