github.com/webwurst/docker@v1.7.0/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  	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  	TOKEN_WHITESPACE        = regexp.MustCompile(`[\t\v\f\r ]+`)
    38  	TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
    39  	TOKEN_COMMENT           = 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  	// recieves 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  	}
    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, flags, 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  	node.Flags = flags
    95  
    96  	return "", node, nil
    97  }
    98  
    99  // The main parse routine. Handles an io.ReadWriteCloser and returns the root
   100  // of the AST.
   101  func Parse(rwc io.Reader) (*Node, error) {
   102  	root := &Node{}
   103  	scanner := bufio.NewScanner(rwc)
   104  
   105  	for scanner.Scan() {
   106  		scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
   107  		line, child, err := parseLine(scannedLine)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  
   112  		if line != "" && child == nil {
   113  			for scanner.Scan() {
   114  				newline := scanner.Text()
   115  
   116  				if stripComments(strings.TrimSpace(newline)) == "" {
   117  					continue
   118  				}
   119  
   120  				line, child, err = parseLine(line + newline)
   121  				if err != nil {
   122  					return nil, err
   123  				}
   124  
   125  				if child != nil {
   126  					break
   127  				}
   128  			}
   129  			if child == nil && line != "" {
   130  				line, child, err = parseLine(line)
   131  				if err != nil {
   132  					return nil, err
   133  				}
   134  			}
   135  		}
   136  
   137  		if child != nil {
   138  			root.Children = append(root.Children, child)
   139  		}
   140  	}
   141  
   142  	return root, nil
   143  }