github.com/miqui/docker@v1.9.1/builder/dockerfile/parser/parser.go (about)

     1  // Package parser implements a parser and parse tree dumper for Dockerfiles.
     2  package parser
     3  
     4  import (
     5  	"bufio"
     6  	"io"
     7  	"regexp"
     8  	"strings"
     9  	"unicode"
    10  
    11  	"github.com/docker/docker/builder/dockerfile/command"
    12  )
    13  
    14  // Node is a structure used to represent a parse tree.
    15  //
    16  // In the node there are three fields, Value, Next, and Children. Value is the
    17  // current token's string value. Next is always the next non-child token, and
    18  // children contains all the children. Here's an example:
    19  //
    20  // (value next (child child-next child-next-next) next-next)
    21  //
    22  // This data structure is frankly pretty lousy for handling complex languages,
    23  // but lucky for us the Dockerfile isn't very complicated. This structure
    24  // works a little more effectively than a "proper" parse tree for our needs.
    25  //
    26  type Node struct {
    27  	Value      string          // actual content
    28  	Next       *Node           // the next item in the current sexp
    29  	Children   []*Node         // the children of this sexp
    30  	Attributes map[string]bool // special attributes for this node
    31  	Original   string          // original line used before parsing
    32  	Flags      []string        // only top Node should have this set
    33  }
    34  
    35  var (
    36  	dispatch              map[string]func(string) (*Node, map[string]bool, error)
    37  	tokenWhitespace       = regexp.MustCompile(`[\t\v\f\r ]+`)
    38  	tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
    39  	tokenComment          = regexp.MustCompile(`^#.*$`)
    40  )
    41  
    42  func init() {
    43  	// Dispatch Table. see line_parsers.go for the parse functions.
    44  	// The command is parsed and mapped to the line parser. The line parser
    45  	// receives the arguments but not the command, and returns an AST after
    46  	// reformulating the arguments according to the rules in the parser
    47  	// functions. Errors are propagated up by Parse() and the resulting AST can
    48  	// be incorporated directly into the existing AST as a next.
    49  	dispatch = map[string]func(string) (*Node, map[string]bool, error){
    50  		command.User:       parseString,
    51  		command.Onbuild:    parseSubCommand,
    52  		command.Workdir:    parseString,
    53  		command.Env:        parseEnv,
    54  		command.Label:      parseLabel,
    55  		command.Maintainer: parseString,
    56  		command.From:       parseString,
    57  		command.Add:        parseMaybeJSONToList,
    58  		command.Copy:       parseMaybeJSONToList,
    59  		command.Run:        parseMaybeJSON,
    60  		command.Cmd:        parseMaybeJSON,
    61  		command.Entrypoint: parseMaybeJSON,
    62  		command.Expose:     parseStringsWhitespaceDelimited,
    63  		command.Volume:     parseMaybeJSONToList,
    64  		command.StopSignal: parseString,
    65  		command.Arg:        parseNameOrNameVal,
    66  	}
    67  }
    68  
    69  // parse a line and return the remainder.
    70  func parseLine(line string) (string, *Node, error) {
    71  	if line = stripComments(line); line == "" {
    72  		return "", nil, nil
    73  	}
    74  
    75  	if tokenLineContinuation.MatchString(line) {
    76  		line = tokenLineContinuation.ReplaceAllString(line, "")
    77  		return line, nil, nil
    78  	}
    79  
    80  	cmd, flags, args, err := splitCommand(line)
    81  	if err != nil {
    82  		return "", nil, err
    83  	}
    84  
    85  	node := &Node{}
    86  	node.Value = cmd
    87  
    88  	sexp, attrs, err := fullDispatch(cmd, args)
    89  	if err != nil {
    90  		return "", nil, err
    91  	}
    92  
    93  	node.Next = sexp
    94  	node.Attributes = attrs
    95  	node.Original = line
    96  	node.Flags = flags
    97  
    98  	return "", node, nil
    99  }
   100  
   101  // Parse is the main parse routine.
   102  // It handles an io.ReadWriteCloser and returns the root of the AST.
   103  func Parse(rwc io.Reader) (*Node, error) {
   104  	root := &Node{}
   105  	scanner := bufio.NewScanner(rwc)
   106  
   107  	for scanner.Scan() {
   108  		scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
   109  		line, child, err := parseLine(scannedLine)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		if line != "" && child == nil {
   115  			for scanner.Scan() {
   116  				newline := scanner.Text()
   117  
   118  				if stripComments(strings.TrimSpace(newline)) == "" {
   119  					continue
   120  				}
   121  
   122  				line, child, err = parseLine(line + newline)
   123  				if err != nil {
   124  					return nil, err
   125  				}
   126  
   127  				if child != nil {
   128  					break
   129  				}
   130  			}
   131  			if child == nil && line != "" {
   132  				line, child, err = parseLine(line)
   133  				if err != nil {
   134  					return nil, err
   135  				}
   136  			}
   137  		}
   138  
   139  		if child != nil {
   140  			root.Children = append(root.Children, child)
   141  		}
   142  	}
   143  
   144  	return root, nil
   145  }