github.com/ralexstokes/docker@v1.6.2/builder/parser/parser.go (about)

     1  // This package 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/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  }
    33  
    34  var (
    35  	dispatch                map[string]func(string) (*Node, map[string]bool, error)
    36  	TOKEN_WHITESPACE        = regexp.MustCompile(`[\t\v\f\r ]+`)
    37  	TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
    38  	TOKEN_COMMENT           = regexp.MustCompile(`^#.*$`)
    39  )
    40  
    41  func init() {
    42  	// Dispatch Table. see line_parsers.go for the parse functions.
    43  	// The command is parsed and mapped to the line parser. The line parser
    44  	// recieves the arguments but not the command, and returns an AST after
    45  	// reformulating the arguments according to the rules in the parser
    46  	// functions. Errors are propagated up by Parse() and the resulting AST can
    47  	// be incorporated directly into the existing AST as a next.
    48  	dispatch = map[string]func(string) (*Node, map[string]bool, error){
    49  		command.User:       parseString,
    50  		command.Onbuild:    parseSubCommand,
    51  		command.Workdir:    parseString,
    52  		command.Env:        parseEnv,
    53  		command.Label:      parseLabel,
    54  		command.Maintainer: parseString,
    55  		command.From:       parseString,
    56  		command.Add:        parseMaybeJSONToList,
    57  		command.Copy:       parseMaybeJSONToList,
    58  		command.Run:        parseMaybeJSON,
    59  		command.Cmd:        parseMaybeJSON,
    60  		command.Entrypoint: parseMaybeJSON,
    61  		command.Expose:     parseStringsWhitespaceDelimited,
    62  		command.Volume:     parseMaybeJSONToList,
    63  		command.Insert:     parseIgnore,
    64  	}
    65  }
    66  
    67  // parse a line and return the remainder.
    68  func parseLine(line string) (string, *Node, error) {
    69  	if line = stripComments(line); line == "" {
    70  		return "", nil, nil
    71  	}
    72  
    73  	if TOKEN_LINE_CONTINUATION.MatchString(line) {
    74  		line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
    75  		return line, nil, nil
    76  	}
    77  
    78  	cmd, args, err := splitCommand(line)
    79  	if err != nil {
    80  		return "", nil, err
    81  	}
    82  
    83  	node := &Node{}
    84  	node.Value = cmd
    85  
    86  	sexp, attrs, err := fullDispatch(cmd, args)
    87  	if err != nil {
    88  		return "", nil, err
    89  	}
    90  
    91  	node.Next = sexp
    92  	node.Attributes = attrs
    93  	node.Original = line
    94  
    95  	return "", node, nil
    96  }
    97  
    98  // The main parse routine. Handles an io.ReadWriteCloser and returns the root
    99  // of the AST.
   100  func Parse(rwc io.Reader) (*Node, error) {
   101  	root := &Node{}
   102  	scanner := bufio.NewScanner(rwc)
   103  
   104  	for scanner.Scan() {
   105  		scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
   106  		line, child, err := parseLine(scannedLine)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  
   111  		if line != "" && child == nil {
   112  			for scanner.Scan() {
   113  				newline := scanner.Text()
   114  
   115  				if stripComments(strings.TrimSpace(newline)) == "" {
   116  					continue
   117  				}
   118  
   119  				line, child, err = parseLine(line + newline)
   120  				if err != nil {
   121  					return nil, err
   122  				}
   123  
   124  				if child != nil {
   125  					break
   126  				}
   127  			}
   128  			if child == nil && line != "" {
   129  				line, child, err = parseLine(line)
   130  				if err != nil {
   131  					return nil, err
   132  				}
   133  			}
   134  		}
   135  
   136  		if child != nil {
   137  			root.Children = append(root.Children, child)
   138  		}
   139  	}
   140  
   141  	return root, nil
   142  }