github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/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  
    12  // Node is a structure used to represent a parse tree.
    13  //
    14  // In the node there are three fields, Value, Next, and Children. Value is the
    15  // current token's string value. Next is always the next non-child token, and
    16  // children contains all the children. Here's an example:
    17  //
    18  // (value next (child child-next child-next-next) next-next)
    19  //
    20  // This data structure is frankly pretty lousy for handling complex languages,
    21  // but lucky for us the Dockerfile isn't very complicated. This structure
    22  // works a little more effectively than a "proper" parse tree for our needs.
    23  //
    24  type Node struct {
    25  	Value      string          // actual content
    26  	Next       *Node           // the next item in the current sexp
    27  	Children   []*Node         // the children of this sexp
    28  	Attributes map[string]bool // special attributes for this node
    29  	Original   string          // original line used before parsing
    30  }
    31  
    32  var (
    33  	dispatch                map[string]func(string) (*Node, map[string]bool, error)
    34  	TOKEN_WHITESPACE        = regexp.MustCompile(`[\t\v\f\r ]+`)
    35  	TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\\s*$`)
    36  	TOKEN_COMMENT           = regexp.MustCompile(`^#.*$`)
    37  )
    38  
    39  func init() {
    40  	// Dispatch Table. see line_parsers.go for the parse functions.
    41  	// The command is parsed and mapped to the line parser. The line parser
    42  	// recieves the arguments but not the command, and returns an AST after
    43  	// reformulating the arguments according to the rules in the parser
    44  	// functions. Errors are propogated up by Parse() and the resulting AST can
    45  	// be incorporated directly into the existing AST as a next.
    46  	dispatch = map[string]func(string) (*Node, map[string]bool, error){
    47  		"user":       parseString,
    48  		"onbuild":    parseSubCommand,
    49  		"workdir":    parseString,
    50  		"env":        parseEnv,
    51  		"maintainer": parseString,
    52  		"from":       parseString,
    53  		"add":        parseStringsWhitespaceDelimited,
    54  		"copy":       parseStringsWhitespaceDelimited,
    55  		"run":        parseMaybeJSON,
    56  		"cmd":        parseMaybeJSON,
    57  		"entrypoint": parseMaybeJSON,
    58  		"expose":     parseStringsWhitespaceDelimited,
    59  		"volume":     parseMaybeJSONToList,
    60  		"insert":     parseIgnore,
    61  	}
    62  }
    63  
    64  // parse a line and return the remainder.
    65  func parseLine(line string) (string, *Node, error) {
    66  	if line = stripComments(line); line == "" {
    67  		return "", nil, nil
    68  	}
    69  
    70  	if TOKEN_LINE_CONTINUATION.MatchString(line) {
    71  		line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
    72  		return line, nil, nil
    73  	}
    74  
    75  	cmd, args, err := splitCommand(line)
    76  	if err != nil {
    77  		return "", nil, err
    78  	}
    79  
    80  	node := &Node{}
    81  	node.Value = cmd
    82  
    83  	sexp, attrs, err := fullDispatch(cmd, args)
    84  	if err != nil {
    85  		return "", nil, err
    86  	}
    87  
    88  	if sexp.Value != "" || sexp.Next != nil || sexp.Children != nil {
    89  		node.Next = sexp
    90  	}
    91  
    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  }