Add blockDynamicHuffman to script/make-artificial
diff --git a/script/make-artificial.go b/script/make-artificial.go
index 930bf0c..23d37bb 100644
--- a/script/make-artificial.go
+++ b/script/make-artificial.go
@@ -118,6 +118,15 @@
 		}
 	}
 
+	if state == nil {
+		return fmt.Errorf("no 'make' line")
+	} else {
+		state, err = state("")
+		if err != nil {
+			return err
+		}
+	}
+
 	_, err = os.Stdout.Write(out)
 	return err
 }
@@ -224,23 +233,177 @@
 	formats["deflate"] = stateDeflate
 }
 
+var deflateCodeOrder = [19]uint32{
+	16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
+}
+
+type deflateHuffmanTable map[uint32]string
+
 var deflateGlobals struct {
 	bncData []byte
 	stream  deflateBitStream
+
+	// Dynamic Huffman state.
+	numLCodes        uint32
+	numDCodes        uint32
+	numCLCodeLengths uint32
+	whichHuffman     uint32
+	// 0=Unused, 1=CodeLength, 2=Length/Literal, 3=Distance.
+	huffmans [4]deflateHuffmanTable
+}
+
+func deflateGlobalsClearDynamicHuffmanState() {
+	deflateGlobals.numLCodes = 0
+	deflateGlobals.numDCodes = 0
+	deflateGlobals.numCLCodeLengths = 0
+	deflateGlobals.whichHuffman = 0
+	deflateGlobals.huffmans = [4]deflateHuffmanTable{}
+}
+
+func deflateGlobalsWriteDynamicHuffmanTables() error {
+	g := &deflateGlobals
+	if (g.numLCodes < 257) || (257+31 < g.numLCodes) {
+		return fmt.Errorf("bad numLCodes: %d", g.numLCodes)
+	}
+	g.stream.writeBits(g.numLCodes-257, 5)
+	if (g.numDCodes < 1) || (1+31 < g.numDCodes) {
+		return fmt.Errorf("bad numDCodes: %d", g.numDCodes)
+	}
+	g.stream.writeBits(g.numDCodes-1, 5)
+	if (g.numCLCodeLengths < 4) || (4+15 < g.numCLCodeLengths) {
+		return fmt.Errorf("bad numCLCodeLengths: %d", g.numCLCodeLengths)
+	}
+	g.stream.writeBits(g.numCLCodeLengths-4, 4)
+
+	// Write the Huffman table for CodeLength.
+	{
+		for i := uint32(0); i < g.numCLCodeLengths; i++ {
+			n := len(g.huffmans[1][deflateCodeOrder[i]])
+			g.stream.writeBits(uint32(n), 3)
+		}
+		for i := g.numCLCodeLengths; i < uint32(len(deflateCodeOrder)); i++ {
+			n := len(g.huffmans[1][deflateCodeOrder[i]])
+			if n > 0 {
+				return fmt.Errorf("short numCLCodeLengths: %d", g.numCLCodeLengths)
+			}
+		}
+	}
+
+	// Write the Huffman tables for Length/Literal and Distance.
+	{
+		numZeroes := uint32(0)
+		for i := uint32(0); i < g.numLCodes+g.numDCodes; i++ {
+			s := ""
+			if i < g.numLCodes {
+				s = g.huffmans[2][i]
+			} else {
+				s = g.huffmans[3][i-g.numLCodes]
+			}
+			if s == "" {
+				numZeroes++
+				continue
+			}
+
+			if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
+				return err
+			}
+			numZeroes = 0
+
+			deflateGlobalsWriteDynamicHuffmanBits(g.huffmans[1][uint32(len(s))])
+		}
+		if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes uint32) error {
+	g := &deflateGlobals
+	if numZeroes == 0 {
+		return nil
+	}
+
+	if s := g.huffmans[1][18]; s != "" {
+		for numZeroes >= 11 {
+			extra := numZeroes - 11
+			if extra > 127 {
+				extra = 127
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+			g.stream.writeBits(extra, 7)
+			numZeroes -= 11 + extra
+		}
+	}
+
+	if s := g.huffmans[1][17]; s != "" {
+		for numZeroes >= 3 {
+			extra := numZeroes - 3
+			if extra > 7 {
+				extra = 7
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+			g.stream.writeBits(extra, 3)
+			numZeroes -= 3 + extra
+		}
+	}
+
+	if s := g.huffmans[1][0]; s != "" {
+		for ; numZeroes > 0; numZeroes-- {
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+		}
+	}
+
+	if numZeroes > 0 {
+		return fmt.Errorf("could not write a run of zero-valued code lengths")
+	}
+	return nil
+}
+
+func deflateGlobalsWriteDynamicHuffmanBits(s string) {
+	g := &deflateGlobals
+	for i := 0; i < len(s); i++ {
+		g.stream.writeBits(uint32(s[i]&1), 1)
+	}
+}
+
+func parseDeflateWhichHuffman(s string) (num uint32, remaining string, ok bool) {
+	if i := strings.IndexByte(s, ' '); i >= 0 {
+		s, remaining = s[:i], s[i+1:]
+		for len(remaining) > 0 && remaining[0] == ' ' {
+			remaining = remaining[1:]
+		}
+	}
+
+	switch s {
+	case "CodeLength":
+		return 1, remaining, true
+	case "Length/Literal":
+		return 2, remaining, true
+	case "Distance":
+		return 3, remaining, true
+	}
+	return 0, "", false
 }
 
 func stateDeflate(line string) (stateFunc, error) {
 	g := &deflateGlobals
 	const (
 		cmdB   = "bytes "
-		cmdBNC = "blockNoCompression "
+		cmdBDH = "blockDynamicHuffman "
 		cmdBFH = "blockFixedHuffman "
+		cmdBNC = "blockNoCompression "
 	)
 	bits := uint32(0)
 	s := ""
 
 	retState := stateFunc(nil)
 	switch {
+	case line == "":
+		g.stream.flush()
+		return stateDeflate, nil
+
 	case strings.HasPrefix(line, cmdB):
 		s := line[len(cmdB):]
 		for s != "" {
@@ -261,6 +424,10 @@
 		s = line[len(cmdBFH):]
 		retState = stateDeflateFixedHuffman
 		bits |= 1 << 1
+	case strings.HasPrefix(line, cmdBDH):
+		s = line[len(cmdBDH):]
+		retState = stateDeflateDynamicHuffman
+		bits |= 2 << 1
 	default:
 		return nil, fmt.Errorf("bad stateDeflate command: %q", line)
 	}
@@ -304,7 +471,6 @@
 func stateDeflateFixedHuffman(line string) (stateFunc, error) {
 	g := &deflateGlobals
 	if line == "}" {
-		g.stream.flush()
 		return stateDeflate, nil
 	}
 
@@ -314,16 +480,15 @@
 	}
 
 	if lit, ok := deflateParseLiteral(line); ok {
-		// TODO: support "\xAB" escape codes in the script?
 		for i := 0; i < len(lit); i++ {
-			g.stream.writeLCode(uint32(lit[i]))
+			g.stream.writeFixedHuffmanLCode(uint32(lit[i]))
 		}
 		return stateDeflateFixedHuffman, nil
 	}
 
 	if line == "len 3 distCode 31" {
 		lCode, lExtra, lNExtra := deflateEncodeLength(3)
-		g.stream.writeLCode(lCode)
+		g.stream.writeFixedHuffmanLCode(lCode)
 		g.stream.writeBits(lExtra, lNExtra)
 		dCode, dExtra, dNExtra := uint32(31), uint32(0), uint32(0)
 		g.stream.writeBits(reverse(dCode, 5), 5)
@@ -333,7 +498,7 @@
 
 	if l, d, ok := deflateParseLenDist(line); ok {
 		lCode, lExtra, lNExtra := deflateEncodeLength(l)
-		g.stream.writeLCode(lCode)
+		g.stream.writeFixedHuffmanLCode(lCode)
 		g.stream.writeBits(lExtra, lNExtra)
 		dCode, dExtra, dNExtra := deflateEncodeDistance(d)
 		g.stream.writeBits(reverse(dCode, 5), 5)
@@ -344,6 +509,120 @@
 	return nil, fmt.Errorf("bad stateDeflateFixedHuffman command: %q", line)
 }
 
+func stateDeflateDynamicHuffman(line string) (stateFunc, error) {
+	g := &deflateGlobals
+	const (
+		cmdH     = "huffman "
+		cmdNCLCL = "numCLCodeLengths "
+		cmdNDC   = "numDCodes "
+		cmdNLC   = "numLCodes "
+	)
+	switch {
+	case line == "}":
+		deflateGlobalsClearDynamicHuffmanState()
+		return stateDeflate, nil
+
+	case strings.HasPrefix(line, cmdH):
+		s := line[len(cmdH):]
+		n, s, ok := parseDeflateWhichHuffman(s)
+		if !ok {
+			break
+		}
+		g.whichHuffman = n
+		return stateDeflateDynamicHuffmanHuffman, nil
+
+	case strings.HasPrefix(line, cmdNLC):
+		s := line[len(cmdNLC):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numLCodes = n
+		return stateDeflateDynamicHuffman, nil
+
+	case strings.HasPrefix(line, cmdNDC):
+		s := line[len(cmdNDC):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numDCodes = n
+		return stateDeflateDynamicHuffman, nil
+
+	case strings.HasPrefix(line, cmdNCLCL):
+		s := line[len(cmdNCLCL):]
+		n, s, ok := parseNum(s)
+		if !ok {
+			break
+		}
+		g.numCLCodeLengths = n
+		return stateDeflateDynamicHuffman, nil
+	}
+
+	if lit, ok := deflateParseLiteral(line); ok {
+		for i := 0; i < len(lit); i++ {
+			s := g.huffmans[2][uint32(lit[i])]
+			if s == "" {
+				return nil, fmt.Errorf("no code for literal %q (%d)", lit[i:i+1], lit[i])
+			}
+			deflateGlobalsWriteDynamicHuffmanBits(s)
+		}
+		return stateDeflateDynamicHuffman, nil
+	} else if line == "endOfBlock" {
+		s := g.huffmans[2][256]
+		if s == "" {
+			return nil, fmt.Errorf("no code for end-of-block (256)")
+		}
+		deflateGlobalsWriteDynamicHuffmanBits(s)
+		return stateDeflateDynamicHuffman, nil
+	}
+
+	return nil, fmt.Errorf("bad stateDeflateDynamicHuffman command: %q", line)
+}
+
+func stateDeflateDynamicHuffmanHuffman(line string) (stateFunc, error) {
+	g := &deflateGlobals
+outer:
+	switch {
+	case line == "}":
+		g.whichHuffman = 0
+
+		// If we have all three Huffman tables, write them.
+		for i := 1; ; i++ {
+			if i == 4 {
+				if err := deflateGlobalsWriteDynamicHuffmanTables(); err != nil {
+					return nil, err
+				}
+				break
+			}
+			if g.huffmans[i] == nil {
+				break
+			}
+		}
+
+		return stateDeflateDynamicHuffman, nil
+
+	default:
+		s := line
+		n, s, ok := parseNum(s)
+		if !ok || s == "" {
+			break
+		}
+		for i := 0; i < len(s); i++ {
+			if c := s[i]; c != '0' && c != '1' {
+				break outer
+			}
+		}
+		if g.huffmans[g.whichHuffman] == nil {
+			g.huffmans[g.whichHuffman] = deflateHuffmanTable{}
+		}
+		g.huffmans[g.whichHuffman][n] = s
+		return stateDeflateDynamicHuffmanHuffman, nil
+	}
+
+	return nil, fmt.Errorf("bad stateDeflateDynamicHuffmanHuffman command: %q", line)
+}
+
 type deflateBitStream struct {
 	bits  uint32
 	nBits uint32 // Always within [0, 7].
@@ -368,7 +647,7 @@
 	out = append(out, b...)
 }
 
-func (z *deflateBitStream) writeLCode(lCode uint32) {
+func (z *deflateBitStream) writeFixedHuffmanLCode(lCode uint32) {
 	switch {
 	case lCode < 144: // 0b._0011_0000 through 0b._1011_1111
 		lCode += 0x030
@@ -472,6 +751,7 @@
 }
 
 func deflateParseLiteral(s string) (lit string, ok bool) {
+	// TODO: support "\xAB" escape codes in the script?
 	const (
 		prefix = `literal "`
 		suffix = `"`
@@ -534,6 +814,9 @@
 	)
 outer:
 	switch {
+	case line == "":
+		return stateGif, nil
+
 	case line == "frame {":
 		gifGlobals.localPalette = nil
 		out = append(out, 0x2C)
diff --git a/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt
index ffd4ded..ba90dd8 100644
--- a/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt
+++ b/test/data/artificial/deflate-degenerate-huffman-unused.deflate.make-artificial.txt
@@ -2,6 +2,30 @@
 
 make deflate
 
-# TODO: blockDynamicHuffman
-# See deflate-degenerate-huffman.deflate.commentary.txt for details.
-bytes 0x05 0xC0 0x21 0x01 0x00 0x00 0x00 0x80 0xA0 0xB7 0x56 0xFE 0x37 0x89 0x01
+blockDynamicHuffman (final) {
+	numLCodes 257
+	numDCodes 1
+	numCLCodeLengths 18
+
+	huffman CodeLength {
+		1  00
+		2  01
+		17 10
+		18 11
+	}
+
+	huffman Length/Literal {
+		# 102='f', 111='o', 256=EOB
+		111 0
+		102 10
+		256 11
+	}
+
+	huffman Distance {
+		# Incomplete. There is no key/value pair whose value starts with "1".
+		0 0
+	}
+
+	literal "foo"
+	endOfBlock
+}