diff --git a/Makefile b/Makefile
index b411c734f7c269b8758b2ab18742143f4fe84811..13eeab633e564a1c897704a443c3eac30036830f 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ KCFG_TEST_FILE = lib/kubecfg_test.jsonnet
 GUESTBOOK_FILE = examples/guestbook.jsonnet
 JSONNET_FILES = $(KCFG_TEST_FILE) $(GUESTBOOK_FILE)
 # TODO: Simplify this once ./... ignores ./vendor
-GO_PACKAGES = ./cmd/... ./utils/... ./pkg/... ./metadata/...
+GO_PACKAGES = ./cmd/... ./utils/... ./pkg/... ./metadata/... ./prototype/...
 
 # Default cluster from this config is used for integration tests
 KUBECONFIG = $(HOME)/.kube/config
diff --git a/prototype/snippet/interface.go b/prototype/snippet/interface.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe51230b82ee5becff7126e10fc2e8a88ebbcc41
--- /dev/null
+++ b/prototype/snippet/interface.go
@@ -0,0 +1,58 @@
+// Package snippet provides primitives for parsing and evaluating TextMate
+// snippets. In general, snippets are text with "placeholders" for the user to
+// fill in. For example something like "foo ${bar}" would expect the user to
+// provide a value for the `bar` variable.
+//
+// This code is influenced heavily by the more formal treatment specified by the
+// Language Server Protocol, though (since this does not have to serve an
+// interactive prompt in an IDE) we do omit some features for simplification
+// (e.g., we have limited support for tabstops and builtin variables like
+// `TM_SELECTED_TEXT`).
+//
+// A parsed snippet template is represented as a tree consisting of one of
+// several types:
+//
+//   * Text: representing free text, i.e., text that is not a part of (e.g.) a
+//     variable.
+//   * Variable: Takes the forms `${varName}` and `${varName:defaultValue}`.
+//     When a variable isn't set, an empty string is inserted. If the variable
+//     is undefined, its name is inserted as the default value.
+//   * Tabstop (currently unused by our tool, but implemented anyway): takes the
+//     form of the '$' character followed by a number, e.g., `$1` or `$2`.
+//     Inside an editor, a tabstop represents where to navigate when the user
+//     presses tab or shift-tab.
+//   * Placeholder (currently unused by our tool, but implemented anyway):
+//     representing a tabstop with a default value. These are usually of the
+//     form `${3:someDefaultValue}`. They can also be nested, as in
+//     `${1:firstValue${2:secondValue}`, or recursive, as in `${1:foo$1}`.
+//
+// TextMate does not specify a grammar for this templating language. This parser
+// implements the following grammar for, which we believe is close enough to the
+// intention of TextMate. The characters `$`, `}`, and `\` can be escaped with
+// `\`, but for simplicity we omit them from the grammar.
+//
+//   any         ::= tabstop | placeholder | choice | variable | text
+//   tabstop     ::= '$' int | '${' int '}'
+//   placeholder ::= '${' int ':' any '}'
+//   choice      ::= '${' int '|' text (',' text)* '|}'
+//   variable    ::= '$' var | '${' var }' | '${' var ':' any '}'
+//   var         ::= [_a-zA-Z] [_a-zA-Z0-9]*
+//   int         ::= [0-9]+
+//   text        ::= .*
+package snippet
+
+// Parse takes a TextMate snippet and parses it, producing a `Template`. There
+// is no opportunity for a parse error, since the grammar specifies that
+// malformed placeholders are simply text.
+//
+// The grammar of the parse is formalized in part by the Language Server
+// Protocol, and detailed in the package comments.
+func Parse(template string) Template {
+	return parse(template, false)
+}
+
+// Template represents a parsed TextMate snippet. The template can be evaluated
+// (with respect to some set of variables) using `Evaluate`.
+type Template interface {
+	Evaluate(values map[string]string) (string, error)
+}
diff --git a/prototype/snippet/lexer.go b/prototype/snippet/lexer.go
new file mode 100644
index 0000000000000000000000000000000000000000..acd930f72af7fac757b3f43dd13b9b7c6116b231
--- /dev/null
+++ b/prototype/snippet/lexer.go
@@ -0,0 +1,137 @@
+package snippet
+
+type tokenType int
+
+const (
+	dollar tokenType = iota
+	colon
+	curlyOpen
+	curlyClose
+	backslash
+	number
+	variableName
+	format
+	eof
+)
+
+func (tt tokenType) String() string {
+	s, _ := tokenTypeToString[tt]
+	return s
+}
+
+type token struct {
+	kind tokenType
+	pos  int
+	len  int
+}
+
+var stringToTokenType = map[rune]tokenType{
+	'$':  dollar,
+	':':  colon,
+	'{':  curlyOpen,
+	'}':  curlyClose,
+	'\\': backslash,
+}
+
+var tokenTypeToString = map[tokenType]string{
+	dollar:       "Dollar",
+	colon:        "Colon",
+	curlyOpen:    "CurlyOpen",
+	curlyClose:   "CurlyClose",
+	backslash:    "Backslash",
+	number:       "Int",
+	variableName: "VariableName",
+	format:       "Format",
+	eof:          "EOF",
+}
+
+type lexer struct {
+	value []rune
+	pos   int
+}
+
+func isDigitCharacter(ch rune) bool {
+	return ch >= '0' && ch <= '9'
+}
+
+func isVariableCharacter(ch rune) bool {
+	return ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
+}
+
+func newLexer() *lexer {
+	s := lexer{}
+	s.text("")
+
+	return &s
+}
+
+func (s *lexer) text(value string) {
+	s.value = []rune(value)
+	s.pos = 0
+}
+
+func (s *lexer) tokenText(tok *token) string {
+	return string(s.value[tok.pos : tok.pos+tok.len])
+}
+
+func (s *lexer) next() *token {
+	valueLen := len(s.value)
+	if s.pos >= valueLen {
+		return &token{kind: eof, pos: s.pos, len: 0}
+	}
+
+	pos := s.pos
+	len := 0
+	ch := s.value[pos]
+
+	// Known token types.
+	var t tokenType
+	if t, ok := stringToTokenType[ch]; ok {
+		s.pos++
+		return &token{kind: t, pos: pos, len: 1}
+	}
+
+	// Number token.
+	if isDigitCharacter(ch) {
+		t = number
+		for pos+len < valueLen {
+			ch = s.value[pos+len]
+			if !isDigitCharacter(ch) {
+				break
+			}
+			len++
+		}
+
+		s.pos += len
+		return &token{t, pos, len}
+	}
+
+	// Variable.
+	if isVariableCharacter(ch) {
+		t = variableName
+		for pos+len < valueLen {
+			ch = s.value[pos+len]
+			if !isVariableCharacter(ch) && !isDigitCharacter(ch) {
+				break
+			}
+			len++
+		}
+
+		s.pos += len
+		return &token{t, pos, len}
+	}
+
+	// Formatting characters.
+	t = format
+	for pos+len < valueLen {
+		ch = s.value[pos+len]
+		_, isStaticToken := stringToTokenType[ch]
+		if isStaticToken || isDigitCharacter(ch) || isVariableCharacter(ch) {
+			break
+		}
+		len++
+	}
+
+	s.pos += len
+	return &token{t, pos, len}
+}
diff --git a/prototype/snippet/marker.go b/prototype/snippet/marker.go
new file mode 100644
index 0000000000000000000000000000000000000000..c038f080abcd93a01cc402793e543692b03a823d
--- /dev/null
+++ b/prototype/snippet/marker.go
@@ -0,0 +1,168 @@
+package snippet
+
+import "bytes"
+
+// ----------------------------------------------------------------------------
+// Interfaces.
+// ----------------------------------------------------------------------------
+
+type index int
+
+type indices []index
+
+type marker interface {
+	children() *markers
+	parent() marker
+	setParent(p marker)
+	String() string
+	len() int
+}
+
+type markers []marker
+
+func (ms *markers) append(m ...marker) {
+	*ms = append(*ms, m...)
+}
+
+func (ms *markers) delete(i int) {
+	*ms = append((*ms)[:i], (*ms)[i+1:]...)
+}
+
+func (ms *markers) String() string {
+	var buf bytes.Buffer
+
+	for _, m := range *ms {
+		buf.WriteString(m.String())
+	}
+	return buf.String()
+}
+
+func (ms *markers) setParents(m marker) {
+	for _, child := range *ms {
+		child.setParent(m)
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Base.
+// ----------------------------------------------------------------------------
+
+type markerImpl struct {
+	// _markerBrand: any;
+	_children *markers
+	_parent   marker
+}
+
+func (mi *markerImpl) children() *markers {
+	return mi._children
+}
+
+func (mi *markerImpl) parent() marker {
+	return mi._parent
+}
+
+func (mi *markerImpl) setParent(p marker) {
+	mi._parent = p
+}
+
+func (mi *markerImpl) String() string {
+	return ""
+}
+
+func (mi *markerImpl) len() int {
+	return 0
+}
+
+// ----------------------------------------------------------------------------
+// Text.
+// ----------------------------------------------------------------------------
+
+type text struct {
+	markerImpl
+	data string
+}
+
+func newText(data string) *text {
+	return &text{
+		markerImpl: markerImpl{
+			_children: &markers{},
+		},
+		data: data,
+	}
+}
+
+func (t *text) String() string {
+	return t.data
+}
+
+func (t *text) len() int {
+	return len(t.data)
+}
+
+// ----------------------------------------------------------------------------
+// Placeholder.
+// ----------------------------------------------------------------------------
+
+type placeholder struct {
+	markerImpl
+	index int
+}
+
+func newPlaceholder(index int, children *markers) *placeholder {
+	p := &placeholder{
+		// markerImpl: *newMarkerImplWithChildren(children),
+		markerImpl: markerImpl{
+			_children: children,
+		},
+		index: index,
+	}
+	p._children.setParents(p)
+	return p
+}
+
+func (p *placeholder) String() string {
+	return p._children.String()
+}
+
+func (p *placeholder) isFinalTabstop() bool {
+	return p.index == 0
+}
+
+// ----------------------------------------------------------------------------
+// Variable.
+// ----------------------------------------------------------------------------
+
+type variable struct {
+	markerImpl
+	resolvedValue *string
+	name          string
+}
+
+func newVariable(name string, children *markers) *variable {
+	v := &variable{
+		markerImpl: markerImpl{
+			_children: children,
+		},
+		name: name,
+	}
+	v._children.setParents(v)
+	return v
+}
+
+func (v *variable) isDefined() bool {
+	return v.resolvedValue != nil
+}
+
+func (v *variable) len() int {
+	if v.isDefined() {
+		return len(*v.resolvedValue)
+	}
+	return v.markerImpl.len()
+}
+
+func (v *variable) String() string {
+	if v.isDefined() {
+		return *v.resolvedValue
+	}
+	return v._children.String()
+}
diff --git a/prototype/snippet/parser.go b/prototype/snippet/parser.go
new file mode 100644
index 0000000000000000000000000000000000000000..cee058c146c39fc10cefb29ac78e6e399a2dd8bc
--- /dev/null
+++ b/prototype/snippet/parser.go
@@ -0,0 +1,171 @@
+package snippet
+
+import (
+	"regexp"
+	"strconv"
+)
+
+func parse(template string, enforceFinalTabstop bool) *textmateSnippet {
+	m := newSnippetParser().parse(template, true, enforceFinalTabstop)
+	return newTextmateSnippet(m)
+}
+
+type snippetParser struct {
+	tokenizer lexer
+	currToken *token
+	prevToken *token
+}
+
+func newSnippetParser() *snippetParser {
+	return &snippetParser{
+		tokenizer: *newLexer(),
+	}
+}
+
+func (sp *snippetParser) parse(value string, insertFinalTabstop bool, enforceFinalTabstop bool) *markers {
+	ms := markers{}
+
+	sp.tokenizer.text(value)
+	sp.currToken = sp.tokenizer.next()
+	for sp.parseAny(&ms) || sp.parseText(&ms) {
+		// Consume these tokens.
+	}
+
+	placeholderDefaultValues := map[int]*markers{}
+	walkDefaults(&ms, placeholderDefaultValues)
+
+	_, hasFinalTabstop := placeholderDefaultValues[0]
+	shouldInsertFinalTabstop := insertFinalTabstop && len(placeholderDefaultValues) > 0 || enforceFinalTabstop
+	if !hasFinalTabstop && shouldInsertFinalTabstop {
+		// Insert final tabstop.
+		//
+		// By default, when the user finishes filling out a snippet, they expect
+		// their cursor to be at the end of the snippet. So, here, if the user is
+		// using snippets but there is no final tabstop defined, we simply insert
+		// one.
+		ms.append(newPlaceholder(0, &markers{}))
+	}
+
+	return &ms
+}
+
+func (sp *snippetParser) text(value string) string {
+	return sp.parse(value, false, false).String()
+}
+
+func (sp *snippetParser) accept(kind tokenType) bool {
+	if sp.currToken.kind == kind {
+		sp.prevToken = sp.currToken
+		sp.currToken = sp.tokenizer.next()
+		return true
+	}
+	return false
+}
+
+func (sp *snippetParser) acceptAny() bool {
+	sp.prevToken = sp.currToken
+	sp.currToken = sp.tokenizer.next()
+	return true
+}
+
+func (sp *snippetParser) parseAny(ms *markers) bool {
+	if sp.parseEscaped(ms) {
+		return true
+	} else if sp.parseTM(ms) {
+		return true
+	}
+	return false
+}
+
+func (sp *snippetParser) parseText(ms *markers) bool {
+	if sp.currToken.kind != eof {
+		ms.append(newText(sp.tokenizer.tokenText(sp.currToken)))
+		sp.acceptAny()
+		return true
+	}
+	return false
+}
+
+func (sp *snippetParser) parseTM(ms *markers) bool {
+	if sp.accept(dollar) {
+		if sp.accept(variableName) || sp.accept(number) {
+			// Cases like `$FOO` or `$123`.
+			idOrName := sp.tokenizer.tokenText(sp.prevToken)
+			if i, ok := parseNumber(idOrName); ok {
+				// Cases like `$123`.
+				ms.append(newPlaceholder(i, &markers{}))
+			} else {
+				// Cases like `$FOO`.
+				ms.append(newVariable(idOrName, &markers{}))
+			}
+			return true
+		} else if sp.accept(curlyOpen) {
+			// Cases like `${name:nginx}`.
+			name := markers{}
+			children := &markers{}
+			target := &name
+
+			for {
+				if target != children && sp.accept(colon) {
+					target = children
+					continue
+				}
+
+				if sp.accept(curlyClose) {
+					idOrName := name.String()
+					if i, ok := parseNumber(idOrName); ok {
+						ms.append(newPlaceholder(i, children))
+					} else {
+						ms.append(newVariable(idOrName, children))
+					}
+					return true
+				}
+
+				if sp.parseAny(target) || sp.parseText(target) {
+					continue
+				}
+
+				// fallback
+				if len(*children) > 0 {
+					ms.append(newText("${" + name.String() + ":"))
+					ms.append(*children...)
+				} else {
+					ms.append(newText("${"))
+					ms.append(name...)
+				}
+				return true
+			}
+		}
+
+		ms.append(newText("$"))
+		return true
+	}
+
+	return false
+}
+
+func (sp *snippetParser) parseEscaped(ms *markers) bool {
+	if sp.accept(backslash) {
+		if sp.accept(dollar) || sp.accept(curlyClose) || sp.accept(backslash) {
+			// Do nothing.
+		}
+		ms.append(newText(sp.tokenizer.tokenText(sp.prevToken)))
+		return true
+	}
+	return false
+}
+
+func parseNumber(id string) (int, bool) {
+
+	if matches, err := regexp.MatchString(`^\d+$`, id); err != nil {
+		return 0, false
+	} else if !matches {
+		return 0, false
+	}
+
+	i, err := strconv.ParseInt(id, 0, 0)
+	if err != nil {
+		return 0, false
+	}
+	return int(i), true
+}
diff --git a/prototype/snippet/parser_test.go b/prototype/snippet/parser_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ee8952d294e6dde08e38ab2f46905a056959d244
--- /dev/null
+++ b/prototype/snippet/parser_test.go
@@ -0,0 +1,417 @@
+package snippet
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func assertTokensEqual(t *testing.T, actual, expected tokenType) {
+	if actual != expected {
+		t.Fatalf("Expected token type '%d' but got '%d'", expected, actual)
+	}
+}
+
+func TestLexer(t *testing.T) {
+	lexer := newLexer()
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("a")
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("abc")
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("{{abc}}")
+	assertTokensEqual(t, lexer.next().kind, curlyOpen)
+	assertTokensEqual(t, lexer.next().kind, curlyOpen)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, curlyClose)
+	assertTokensEqual(t, lexer.next().kind, curlyClose)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("abc() ")
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, format)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("abc 123")
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, format)
+	assertTokensEqual(t, lexer.next().kind, number)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("$foo")
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("$foo_bar")
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("$foo-bar")
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, format)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("${foo}")
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, curlyOpen)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, curlyClose)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("${1223:foo}")
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, curlyOpen)
+	assertTokensEqual(t, lexer.next().kind, number)
+	assertTokensEqual(t, lexer.next().kind, colon)
+	assertTokensEqual(t, lexer.next().kind, variableName)
+	assertTokensEqual(t, lexer.next().kind, curlyClose)
+	assertTokensEqual(t, lexer.next().kind, eof)
+
+	lexer.text("\\${}")
+	assertTokensEqual(t, lexer.next().kind, backslash)
+	assertTokensEqual(t, lexer.next().kind, dollar)
+	assertTokensEqual(t, lexer.next().kind, curlyOpen)
+	assertTokensEqual(t, lexer.next().kind, curlyClose)
+}
+
+func assertText(t *testing.T, value, expected string) {
+	p := newSnippetParser()
+	actual := p.text(value)
+	if actual != expected {
+		t.Errorf("Expected text '%s', got '%s'", expected, actual)
+	}
+}
+
+func assertMarkerTypes(t *testing.T, actual marker, expected marker) {
+	actualType, expectedType := reflect.TypeOf(actual), reflect.TypeOf(expected)
+	if actualType != expectedType {
+		t.Errorf("Expected type '%v', got type '%v'", expectedType, actualType)
+	}
+}
+
+func assertEqual(t *testing.T, actual, expected interface{}) {
+	if actual != expected {
+		t.Errorf("Expected '%v', got '%v'", expected, actual)
+	}
+}
+
+func assertMarker(t *testing.T, actual markers, expected ...marker) {
+	if len(actual) != len(expected) {
+		t.Errorf("Number of markers and types are not the same")
+	}
+	for i := range actual {
+		actualType := reflect.TypeOf(actual[i])
+		expectedType := reflect.TypeOf(expected[i])
+		if actualType != expectedType {
+			t.Errorf("Expected type '%v', got type '%v'", expectedType, actualType)
+			return
+		}
+	}
+}
+
+func assertMarkerValue(t *testing.T, value string, ctors ...marker) {
+	p := newSnippetParser()
+	m := p.parse(value, false, false)
+	assertMarker(t, *m, ctors...)
+}
+
+func assertTextAndMarker(t *testing.T, value, escaped string, ctors ...marker) {
+	assertText(t, value, escaped)
+	assertMarkerValue(t, value, ctors...)
+}
+
+func TestParserText(t *testing.T) {
+	assertText(t, `$`, `$`)
+	assertText(t, `\\$`, `\$`)
+	assertText(t, "{", "{")
+	assertText(t, `\}`, `}`)
+	assertText(t, `\abc`, `\abc`)
+	assertText(t, `foo${f:\}}bar`, `foo}bar`)
+	assertText(t, `\{`, `\{`)
+	assertText(t, "I need \\\\\\$", "I need \\$")
+	assertText(t, `\`, `\`)
+	assertText(t, `\{{`, `\{{`)
+	assertText(t, `{{`, `{{`)
+	assertText(t, `{{dd`, `{{dd`)
+	assertText(t, `}}`, `}}`)
+	assertText(t, `ff}}`, `ff}}`)
+
+	assertText(t, "farboo", "farboo")
+	assertText(t, "far{{}}boo", "far{{}}boo")
+	assertText(t, "far{{123}}boo", "far{{123}}boo")
+	assertText(t, "far\\{{123}}boo", "far\\{{123}}boo")
+	assertText(t, "far{{id:bern}}boo", "far{{id:bern}}boo")
+	assertText(t, "far{{id:bern {{basel}}}}boo", "far{{id:bern {{basel}}}}boo")
+	assertText(t, "far{{id:bern {{id:basel}}}}boo", "far{{id:bern {{id:basel}}}}boo")
+	assertText(t, "far{{id:bern {{id2:basel}}}}boo", "far{{id:bern {{id2:basel}}}}boo")
+}
+
+func TestParserTMText(t *testing.T) {
+	assertTextAndMarker(t, "foo${1:bar}}", "foobar}", &text{}, &placeholder{}, &text{})
+	assertTextAndMarker(t, "foo${1:bar}${2:foo}}", "foobarfoo}", &text{}, &placeholder{}, &placeholder{}, &text{})
+
+	assertTextAndMarker(t, "foo${1:bar\\}${2:foo}}", "foobar}foo", &text{}, &placeholder{})
+
+	parse := *newSnippetParser().parse("foo${1:bar\\}${2:foo}}", false, false)
+	ph := *parse[1].(*placeholder)
+	children := *ph._children
+
+	assertEqual(t, ph.index, 1)
+	assertMarkerTypes(t, children[0], &text{})
+	assertEqual(t, children[0].String(), "bar}")
+	assertMarkerTypes(t, children[1], &placeholder{})
+	assertEqual(t, children[1].String(), "foo")
+}
+
+func TestParserPlaceholder(t *testing.T) {
+	assertTextAndMarker(t, "farboo", "farboo", &text{})
+	assertTextAndMarker(t, "far{{}}boo", "far{{}}boo", &text{})
+	assertTextAndMarker(t, "far{{123}}boo", "far{{123}}boo", &text{})
+	assertTextAndMarker(t, "far\\{{123}}boo", "far\\{{123}}boo", &text{})
+}
+
+func TestParserLiteral(t *testing.T) {
+	assertTextAndMarker(t, "far`123`boo", "far`123`boo", &text{})
+	assertTextAndMarker(t, "far\\`123\\`boo", "far\\`123\\`boo", &text{})
+}
+
+func TestParserVariablesTabstop(t *testing.T) {
+	assertTextAndMarker(t, "$far-boo", "-boo", &variable{}, &text{})
+	assertTextAndMarker(t, "\\$far-boo", "$far-boo", &text{})
+	assertTextAndMarker(t, "far$farboo", "far", &text{}, &variable{})
+	assertTextAndMarker(t, "far${farboo}", "far", &text{}, &variable{})
+	assertTextAndMarker(t, "$123", "", &placeholder{})
+	assertTextAndMarker(t, "$farboo", "", &variable{})
+	assertTextAndMarker(t, "$far12boo", "", &variable{})
+}
+
+func TestParserVariablesWithDefaults(t *testing.T) {
+	assertTextAndMarker(t, "${name:value}", "value", &variable{})
+	assertTextAndMarker(t, "${1:value}", "value", &placeholder{})
+	assertTextAndMarker(t, "${1:bar${2:foo}bar}", "barfoobar", &placeholder{})
+
+	assertTextAndMarker(t, "${name:value", "${name:value", &text{})
+	assertTextAndMarker(t, "${1:bar${2:foobar}", "${1:barfoobar", &text{}, &placeholder{})
+}
+
+func TestParserTextmate(t *testing.T) {
+	p := newSnippetParser()
+	assertMarker(t, *p.parse("far{{}}boo", false, false), &text{})
+	assertMarker(t, *p.parse("far{{123}}boo", false, false), &text{})
+	assertMarker(t, *p.parse("far\\{{123}}boo", false, false), &text{})
+
+	assertMarker(t, *p.parse("far$0boo", false, false), &text{}, &placeholder{}, &text{})
+	assertMarker(t, *p.parse("far${123}boo", false, false), &text{}, &placeholder{}, &text{})
+	assertMarker(t, *p.parse("far\\${123}boo", false, false), &text{})
+}
+
+func TestParserRealWorld(t *testing.T) {
+	m := newSnippetParser().parse("console.warn(${1: $TM_SELECTED_TEXT })", false, false)
+
+	assertEqual(t, (*m)[0].String(), "console.warn(")
+	assertMarkerTypes(t, (*m)[1], &placeholder{})
+	assertEqual(t, (*m)[2].String(), ")")
+
+	ph := (*m)[1].(*placeholder)
+	children := *ph.children()
+	// assertEqual(t, placeholder, "false")
+	assertEqual(t, ph.index, 1)
+	assertEqual(t, len(children), 3)
+	assertMarkerTypes(t, children[0], &text{})
+	assertMarkerTypes(t, children[1], &variable{})
+	assertMarkerTypes(t, children[2], &text{})
+	assertEqual(t, children[0].String(), " ")
+	assertEqual(t, children[1].String(), "")
+	assertEqual(t, children[2].String(), " ")
+
+	nestedVariable := children[1].(*variable)
+	assertEqual(t, nestedVariable.name, "TM_SELECTED_TEXT")
+	assertEqual(t, len(*nestedVariable.children()), 0)
+
+	m = newSnippetParser().parse("$TM_SELECTED_TEXT", false, false)
+	assertEqual(t, len(*m), 1)
+	assertMarkerTypes(t, (*m)[0], &variable{})
+}
+
+func TestParserDefaultPlaceholderValues(t *testing.T) {
+	assertMarkerValue(t, "errorContext: `${1:err}`, error: $1", &text{}, &placeholder{}, &text{}, &placeholder{})
+
+	parsed := newSnippetParser().parse("errorContext: `${1:err}`, error:$1", false, false)
+	assertMarkerTypes(t, (*parsed)[1], &placeholder{})
+	assertMarkerTypes(t, (*parsed)[3], &placeholder{})
+	p1, p2 := (*parsed)[1].(*placeholder), (*parsed)[3].(*placeholder)
+
+	assertEqual(t, p1.index, 1)
+	assertEqual(t, len(*p1.children()), 1)
+	assertEqual(t, (*p1.children())[0].(*text).String(), "err")
+
+	assertEqual(t, p2.index, 1)
+	assertEqual(t, len(*p2.children()), 1)
+	assertEqual(t, (*p2.children())[0].(*text).String(), "err")
+}
+
+func TestBackspace(t *testing.T) {
+	actual := newSnippetParser().text("Foo \\\\${abc}bar")
+	assertEqual(t, actual, "Foo \\bar")
+}
+
+func ColonAsVariableValue(t *testing.T) {
+	actual := newSnippetParser().text("${TM_SELECTED_TEXT:foo:bar}")
+	assertEqual(t, actual, "foo:bar")
+
+	actual = newSnippetParser().text("${1:foo:bar}")
+	assertEqual(t, actual, "foo:bar")
+}
+
+func assertLen(t *testing.T, template string, lengths ...int) {
+	children := parse(template, false).children()
+	walk(children, func(m marker) bool {
+		var expected int
+		expected, lengths = lengths[0], lengths[1:]
+		assertEqual(t, m.len(), expected)
+		return true
+	})
+}
+
+func TestMarkerLen(t *testing.T) {
+	assertLen(t, "text$0", 4, 0, 0)
+	assertLen(t, "$1text$0", 0, 4, 0, 0)
+	assertLen(t, "te$1xt$0", 2, 0, 2, 0, 0)
+	assertLen(t, "errorContext: `${1:err}`, error: $0", 15, 0, 3, 10, 0, 0)
+	assertLen(t, "errorContext: `${1:err}`, error: $1$0", 15, 0, 3, 10, 0, 3, 0, 0)
+	assertLen(t, "$TM_SELECTED_TEXT$0", 0, 0, 0)
+	assertLen(t, "${TM_SELECTED_TEXT:def}$0", 0, 3, 0, 0)
+}
+
+func TestParserParent(t *testing.T) {
+	snippet := parse("This ${1:is ${2:nested}}$0", false)
+
+	assertEqual(t, len(snippet.placeholders()), 3)
+	first, second := snippet.placeholders()[0], snippet.placeholders()[1]
+	assertEqual(t, first.index, 1)
+	assertEqual(t, second.index, 2)
+	sp := second.parent()
+	fmt.Println(sp)
+	assertEqual(t, second.parent(), first)
+	fp := first.parent()
+	fmt.Println(fp)
+	assertEqual(t, first.parent(), snippet)
+
+	snippet = parse("${VAR:default${1:value}}$0", false)
+	phs := snippet.placeholders()
+	assertEqual(t, len(phs), 2)
+	first = phs[0]
+	assertEqual(t, first.index, 1)
+
+	firstChild := (*snippet.children())[0]
+	assertMarkerTypes(t, firstChild, &variable{})
+	assertEqual(t, first.parent(), firstChild)
+}
+
+func TestTextmateSnippetEnclosingPlaceholders(t *testing.T) {
+	snippet := parse("This ${1:is ${2:nested}}$0", false)
+	first, second := snippet.placeholders()[0], snippet.placeholders()[1]
+
+	assertEqual(t, len(snippet.enclosingPlaceholders(*first)), 0)
+
+	sndEnclosing := snippet.enclosingPlaceholders(*second)
+	assertEqual(t, len(sndEnclosing), 1)
+	assertEqual(t, sndEnclosing[0], first)
+}
+
+func TestTextmateSnippetOffset(t *testing.T) {
+	snippet := parse("te$1xt", false)
+	snippetChildren := *snippet.children()
+	assertEqual(t, snippet.offset(snippetChildren[0]), 0)
+	assertEqual(t, snippet.offset(snippetChildren[1]), 2)
+	assertEqual(t, snippet.offset(snippetChildren[2]), 2)
+
+	snippet = parse("${TM_SELECTED_TEXT:def}", false)
+	snippetChildren = *snippet.children()
+	assertEqual(t, snippet.offset(snippetChildren[0]), 0)
+	assertMarkerTypes(t, snippetChildren[0], &variable{})
+	assertEqual(t, snippet.offset((*snippetChildren[0].(*variable).children())[0]), 0)
+
+	// forgein marker
+	assertEqual(t, snippet.offset(newText("foo")), -1)
+}
+
+func TextmateSnippetPlaceholder(t *testing.T) {
+	snippet := parse("te$1xt$0", false)
+	placeholders := snippet.placeholders()
+	assertEqual(t, len(placeholders), 2)
+
+	snippet = parse("te$1xt$1$0", false)
+	placeholders = snippet.placeholders()
+	assertEqual(t, len(placeholders), 3)
+
+	snippet = parse("te$1xt$2$0", false)
+	placeholders = snippet.placeholders()
+	assertEqual(t, len(placeholders), 3)
+
+	snippet = parse("${1:bar${2:foo}bar}$0", false)
+	placeholders = snippet.placeholders()
+	assertEqual(t, len(placeholders), 3)
+}
+
+func TextmateSnippetReplace1(t *testing.T) {
+	snippet := parse("aaa${1:bbb${2:ccc}}$0", false)
+
+	assertEqual(t, len(snippet.placeholders()), 3)
+	second := *snippet.placeholders()[1]
+	assertEqual(t, second.index, 2)
+
+	enclosing := snippet.enclosingPlaceholders(second)
+	assertEqual(t, len(enclosing), 1)
+	assertEqual(t, enclosing[0].index, 1)
+
+	nested := parse("ddd$1eee$0", false)
+	snippet.ReplacePlaceholder(2, nested.children())
+
+	snippetPlaceholders := snippet.placeholders()
+	assertEqual(t, snippet.text, "aaabbbdddeee")
+	assertEqual(t, len(snippetPlaceholders), 4)
+	assertEqual(t, snippetPlaceholders[0].index, "1")
+	assertEqual(t, snippetPlaceholders[1].index, "1")
+	assertEqual(t, snippetPlaceholders[2].index, "0")
+	assertEqual(t, snippetPlaceholders[3].index, "0")
+
+	newEnclosing := snippet.enclosingPlaceholders(*snippetPlaceholders[1])
+	assertEqual(t, newEnclosing[0], snippetPlaceholders[0])
+	assertEqual(t, len(newEnclosing), 1)
+	assertEqual(t, newEnclosing[0].index, "1")
+}
+
+func TextmateSnippetReplace2(t *testing.T) {
+	snippet := parse("aaa${1:bbb${2:ccc}}$0", false)
+
+	assertEqual(t, len(snippet.placeholders()), 3)
+	second := snippet.placeholders()[1]
+	assertEqual(t, second.index, 2)
+
+	nested := parse("dddeee$0", false)
+	snippet.ReplacePlaceholder(2, nested.children())
+
+	assertEqual(t, snippet.text, "aaabbbdddeee")
+	assertEqual(t, len(snippet.placeholders()), 3)
+}
+
+func TestSnippetOrderPlaceholders(t *testing.T) {
+	_10 := newPlaceholder(10, &markers{})
+	_2 := newPlaceholder(2, &markers{})
+
+	assertEqual(t, compareByIndex(*_10, *_2), 1)
+}
+
+func TestMaxCallStackExceeded(t *testing.T) {
+	newSnippetParser().parse("${1:${foo:${1}}}", false, false)
+}
diff --git a/prototype/snippet/template.go b/prototype/snippet/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f64f3441c375ae4fa86765cd966888788a40d5b
--- /dev/null
+++ b/prototype/snippet/template.go
@@ -0,0 +1,111 @@
+package snippet
+
+type textmateSnippet struct {
+	markerImpl
+	_placeholders *[]*placeholder
+}
+
+func newTextmateSnippet(children *markers) *textmateSnippet {
+	tms := &textmateSnippet{
+		markerImpl: markerImpl{
+			_children: children,
+		},
+		_placeholders: nil,
+	}
+	tms._children.setParents(tms)
+	return tms
+}
+
+func (tms *textmateSnippet) placeholders() []*placeholder {
+	if tms._placeholders == nil {
+		// Fill in placeholders if they don't exist.
+		tms._placeholders = &[]*placeholder{}
+		walk(tms._children, func(candidate marker) bool {
+			switch candidate.(type) {
+			case *placeholder:
+				{
+					*tms._placeholders = append(*tms._placeholders, candidate.(*placeholder))
+				}
+			}
+			return true
+		})
+	}
+	return *tms._placeholders
+}
+
+func (tms *textmateSnippet) offset(m marker) int {
+	pos := 0
+	found := false
+	walk(tms._children, func(candidate marker) bool {
+		if candidate == m {
+			found = true
+			return false
+		}
+		pos += candidate.len()
+		return true
+	})
+
+	if !found {
+		return -1
+	}
+	return pos
+}
+
+func (tms *textmateSnippet) fullLen(m marker) int {
+	ret := 0
+	walk(&markers{m}, func(m marker) bool {
+		ret += m.len()
+		return true
+	})
+	return ret
+}
+
+func (tms *textmateSnippet) enclosingPlaceholders(ph placeholder) []*placeholder {
+	ret := []*placeholder{}
+	parent := ph._parent
+	for parent != nil {
+		switch parent.(type) {
+		case *placeholder:
+			{
+				ret = append(ret, parent.(*placeholder))
+			}
+		}
+		parent = parent.parent()
+	}
+	return ret
+}
+
+func (tms *textmateSnippet) text() string {
+	return tms._children.String()
+}
+
+func (tms *textmateSnippet) Evaluate(values map[string]string) (string, error) {
+	walk(tms.children(), func(candidate marker) bool {
+		switch casted := candidate.(type) {
+		case *variable:
+			{
+				if resolved, ok := values[casted.name]; ok {
+					casted.resolvedValue = &resolved
+				}
+				if casted.isDefined() {
+					// remove default value from resolved variable
+					casted._children = &markers{}
+				}
+			}
+		}
+		return true
+	})
+
+	// TODO: Explicitly disallow tabstops and empty placeholders. Error out if
+	// present.
+
+	return tms.text(), nil
+}
+
+func (tms *textmateSnippet) ReplacePlaceholder(idx index, replaceWith *markers) {
+	newChildren := make(markers, len(*replaceWith))
+	copy(newChildren, *replaceWith)
+	newChildren.delete(int(idx))
+	tms._children = &newChildren
+	tms._placeholders = nil
+}
diff --git a/prototype/snippet/util.go b/prototype/snippet/util.go
new file mode 100644
index 0000000000000000000000000000000000000000..22109fb1299076c81c7da295f4fd3e2122868bca
--- /dev/null
+++ b/prototype/snippet/util.go
@@ -0,0 +1,82 @@
+package snippet
+
+func compareByIndex(a placeholder, b placeholder) int {
+	if a.index == b.index {
+		return 0
+	} else if a.isFinalTabstop() {
+		return 1
+	} else if b.isFinalTabstop() {
+		return -1
+	} else if a.index < b.index {
+		return -1
+	} else if a.index > b.index {
+		return 1
+	}
+	return 0
+}
+
+func walk(ms *markers, visitor func(m marker) bool) {
+	stack := make(markers, len(*ms))
+	copy(stack, *ms)
+
+	for len(stack) > 0 {
+		// NOTE: Declare `m` separately so that we can use the `=` operator
+		// (rather than `:=`) to make it clear that we're not shadowing `stack`.
+		var m marker
+		m, stack = stack[0], stack[1:]
+		recurse := visitor(m)
+		if !recurse {
+			break
+		}
+		stack = append(*m.children(), stack...)
+	}
+}
+
+// * fill in default for empty placeHolders
+// * compact sibling Text markers
+func walkDefaults(ms *markers, placeholderDefaultValues map[int]*markers) {
+
+	for i := 0; i < len(*ms); i++ {
+		thisMarker := (*ms)[i]
+
+		switch thisMarker.(type) {
+		case *placeholder:
+			{
+				pl := thisMarker.(*placeholder)
+				// fill in default values for repeated placeholders
+				// like `${1:foo}and$1` becomes ${1:foo}and${1:foo}
+				if defaultVal, ok := placeholderDefaultValues[pl.index]; !ok {
+					placeholderDefaultValues[pl.index] = pl._children
+					walkDefaults(pl._children, placeholderDefaultValues)
+
+				} else if len(*pl._children) == 0 {
+					// copy children from first placeholder definition, no need to
+					// recurse on them because they have been visited already
+					children := make(markers, len(*defaultVal))
+					pl._children = &children
+					copy(*pl._children, *defaultVal)
+				}
+			}
+		case *variable:
+			{
+				walkDefaults(thisMarker.children(), placeholderDefaultValues)
+			}
+		case *text:
+			{
+				if i <= 0 {
+					continue
+				}
+
+				prev := (*ms)[i-1]
+				switch prev.(type) {
+				case *text:
+					{
+						(*ms)[i-1].(*text).data += (*ms)[i].(*text).data
+						ms.delete(i)
+						i--
+					}
+				}
+			}
+		}
+	}
+}