github.com/sbward/docker@v1.4.2-0.20150114010528-c9dab702bed3/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  	"fmt"
     7  	"io"
     8  	"regexp"
     9  	"strings"
    10  	"unicode"
    11  )
    12  
    13  // Node is a structure used to represent a parse tree.
    14  //
    15  // In the node there are three fields, Value, Next, and Children. Value is the
    16  // current token's string value. Next is always the next non-child token, and
    17  // children contains all the children. Here's an example:
    18  //
    19  // (value next (child child-next child-next-next) next-next)
    20  //
    21  // This data structure is frankly pretty lousy for handling complex languages,
    22  // but lucky for us the Dockerfile isn't very complicated. This structure
    23  // works a little more effectively than a "proper" parse tree for our needs.
    24  //
    25  type Node struct {
    26  	Value      string          // actual content
    27  	Next       *Node           // the next item in the current sexp
    28  	Children   []*Node         // the children of this sexp
    29  	Attributes map[string]bool // special attributes for this node
    30  	Original   string          // original line used before parsing
    31  }
    32  
    33  var (
    34  	dispatch                map[string]func(string) (*Node, map[string]bool, error)
    35  	TOKEN_WHITESPACE        = regexp.MustCompile(`[\t\v\f\r ]+`)
    36  	TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
    37  	TOKEN_COMMENT           = regexp.MustCompile(`^#.*$`)
    38  )
    39  
    40  func init() {
    41  	// Dispatch Table. see line_parsers.go for the parse functions.
    42  	// The command is parsed and mapped to the line parser. The line parser
    43  	// recieves the arguments but not the command, and returns an AST after
    44  	// reformulating the arguments according to the rules in the parser
    45  	// functions. Errors are propogated up by Parse() and the resulting AST can
    46  	// be incorporated directly into the existing AST as a next.
    47  	dispatch = map[string]func(string) (*Node, map[string]bool, error){
    48  		"user":       parseString,
    49  		"onbuild":    parseSubCommand,
    50  		"workdir":    parseString,
    51  		"env":        parseEnv,
    52  		"maintainer": parseString,
    53  		"from":       parseString,
    54  		"add":        parseStringsWhitespaceDelimited,
    55  		"copy":       parseStringsWhitespaceDelimited,
    56  		"run":        parseMaybeJSON,
    57  		"cmd":        parseMaybeJSON,
    58  		"entrypoint": parseMaybeJSON,
    59  		"expose":     parseStringsWhitespaceDelimited,
    60  		"volume":     parseMaybeJSONToList,
    61  		"insert":     parseIgnore,
    62  	}
    63  }
    64  
    65  // parse a line and return the remainder.
    66  func parseLine(line string) (string, *Node, error) {
    67  	if line = stripComments(line); line == "" {
    68  		return "", nil, nil
    69  	}
    70  
    71  	if TOKEN_LINE_CONTINUATION.MatchString(line) {
    72  		line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
    73  		return line, nil, nil
    74  	}
    75  
    76  	cmd, args, err := splitCommand(line)
    77  	if err != nil {
    78  		return "", nil, err
    79  	}
    80  
    81  	if len(args) == 0 {
    82  		return "", nil, fmt.Errorf("Instruction %q is empty; cannot continue", cmd)
    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  	if sexp.Value != "" || sexp.Next != nil || sexp.Children != nil {
    94  		node.Next = sexp
    95  	}
    96  
    97  	node.Attributes = attrs
    98  	node.Original = line
    99  
   100  	return "", node, nil
   101  }
   102  
   103  // The main parse routine. Handles an io.ReadWriteCloser and returns the root
   104  // of the AST.
   105  func Parse(rwc io.Reader) (*Node, error) {
   106  	root := &Node{}
   107  	scanner := bufio.NewScanner(rwc)
   108  
   109  	for scanner.Scan() {
   110  		scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
   111  		line, child, err := parseLine(scannedLine)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  
   116  		if line != "" && child == nil {
   117  			for scanner.Scan() {
   118  				newline := scanner.Text()
   119  
   120  				if stripComments(strings.TrimSpace(newline)) == "" {
   121  					continue
   122  				}
   123  
   124  				line, child, err = parseLine(line + newline)
   125  				if err != nil {
   126  					return nil, err
   127  				}
   128  
   129  				if child != nil {
   130  					break
   131  				}
   132  			}
   133  			if child == nil && line != "" {
   134  				line, child, err = parseLine(line)
   135  				if err != nil {
   136  					return nil, err
   137  				}
   138  			}
   139  		}
   140  
   141  		if child != nil {
   142  			root.Children = append(root.Children, child)
   143  		}
   144  	}
   145  
   146  	return root, nil
   147  }