github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/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  	StartLine  int             // the line in the original dockerfile where the node begins
    34  	EndLine    int             // the line in the original dockerfile where the node ends
    35  }
    36  
    37  var (
    38  	dispatch              map[string]func(string) (*Node, map[string]bool, error)
    39  	tokenWhitespace       = regexp.MustCompile(`[\t\v\f\r ]+`)
    40  	tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
    41  	tokenComment          = regexp.MustCompile(`^#.*$`)
    42  )
    43  
    44  func init() {
    45  	// Dispatch Table. see line_parsers.go for the parse functions.
    46  	// The command is parsed and mapped to the line parser. The line parser
    47  	// receives the arguments but not the command, and returns an AST after
    48  	// reformulating the arguments according to the rules in the parser
    49  	// functions. Errors are propagated up by Parse() and the resulting AST can
    50  	// be incorporated directly into the existing AST as a next.
    51  	dispatch = map[string]func(string) (*Node, map[string]bool, error){
    52  		command.User:       parseString,
    53  		command.Onbuild:    parseSubCommand,
    54  		command.Workdir:    parseString,
    55  		command.Env:        parseEnv,
    56  		command.Label:      parseLabel,
    57  		command.Maintainer: parseString,
    58  		command.From:       parseString,
    59  		command.Add:        parseMaybeJSONToList,
    60  		command.Copy:       parseMaybeJSONToList,
    61  		command.Run:        parseMaybeJSON,
    62  		command.Cmd:        parseMaybeJSON,
    63  		command.Entrypoint: parseMaybeJSON,
    64  		command.Expose:     parseStringsWhitespaceDelimited,
    65  		command.Volume:     parseMaybeJSONToList,
    66  		command.StopSignal: parseString,
    67  		command.Arg:        parseNameOrNameVal,
    68  	}
    69  }
    70  
    71  // parse a line and return the remainder.
    72  func parseLine(line string) (string, *Node, error) {
    73  	if line = stripComments(line); line == "" {
    74  		return "", nil, nil
    75  	}
    76  
    77  	if tokenLineContinuation.MatchString(line) {
    78  		line = tokenLineContinuation.ReplaceAllString(line, "")
    79  		return line, nil, nil
    80  	}
    81  
    82  	cmd, flags, args, err := splitCommand(line)
    83  	if err != nil {
    84  		return "", nil, err
    85  	}
    86  
    87  	node := &Node{}
    88  	node.Value = cmd
    89  
    90  	sexp, attrs, err := fullDispatch(cmd, args)
    91  	if err != nil {
    92  		return "", nil, err
    93  	}
    94  
    95  	node.Next = sexp
    96  	node.Attributes = attrs
    97  	node.Original = line
    98  	node.Flags = flags
    99  
   100  	return "", node, nil
   101  }
   102  
   103  // Parse is the main parse routine.
   104  // It handles an io.ReadWriteCloser and returns the root of the AST.
   105  func Parse(rwc io.Reader) (*Node, error) {
   106  	currentLine := 0
   107  	root := &Node{}
   108  	root.StartLine = -1
   109  	scanner := bufio.NewScanner(rwc)
   110  
   111  	for scanner.Scan() {
   112  		scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
   113  		currentLine++
   114  		line, child, err := parseLine(scannedLine)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		startLine := currentLine
   119  
   120  		if line != "" && child == nil {
   121  			for scanner.Scan() {
   122  				newline := scanner.Text()
   123  				currentLine++
   124  
   125  				if stripComments(strings.TrimSpace(newline)) == "" {
   126  					continue
   127  				}
   128  
   129  				line, child, err = parseLine(line + newline)
   130  				if err != nil {
   131  					return nil, err
   132  				}
   133  
   134  				if child != nil {
   135  					break
   136  				}
   137  			}
   138  			if child == nil && line != "" {
   139  				line, child, err = parseLine(line)
   140  				if err != nil {
   141  					return nil, err
   142  				}
   143  			}
   144  		}
   145  
   146  		if child != nil {
   147  			// Update the line information for the current child.
   148  			child.StartLine = startLine
   149  			child.EndLine = currentLine
   150  			// Update the line information for the root. The starting line of the root is always the
   151  			// starting line of the first child and the ending line is the ending line of the last child.
   152  			if root.StartLine < 0 {
   153  				root.StartLine = currentLine
   154  			}
   155  			root.EndLine = currentLine
   156  			root.Children = append(root.Children, child)
   157  		}
   158  	}
   159  
   160  	return root, nil
   161  }