diff --git a/src/__snapshots__/notcl.test.ts.snap b/src/__snapshots__/notcl.test.ts.snap
index 78822d7..d966ca8 100644
--- a/src/__snapshots__/notcl.test.ts.snap
+++ b/src/__snapshots__/notcl.test.ts.snap
@@ -9,10 +9,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
         "enchanted": "h1",
       },
       {
-        "text": ""Hello,",
-      },
-      {
-        "text": "World!"",
+        "text": "Hello, World!",
       },
     ],
     [
@@ -47,7 +44,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
         "enchanted": "-red",
       },
       {
-        "text": ""Beware!"",
+        "text": "Beware!",
       },
     ],
     [
@@ -55,61 +52,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
         "enchanted": "para",
       },
       {
-        "text": ""All",
-      },
-      {
-        "enchanted": "text",
-      },
-      {
-        "enchanted": "should",
-      },
-      {
-        "enchanted": "be",
-      },
-      {
-        "enchanted": "quoted,",
-      },
-      {
-        "enchanted": "it's",
-      },
-      {
-        "enchanted": "clearer",
-      },
-      {
-        "enchanted": "that",
-      },
-      {
-        "enchanted": "way.",
-      },
-      {
-        "enchanted": "&",
-      },
-      {
-        "enchanted": "blockquotes",
-      },
-      {
-        "enchanted": "already",
-      },
-      {
-        "enchanted": "should",
-      },
-      {
-        "enchanted": "contain",
-      },
-      {
-        "enchanted": "paragraphs.",
-      },
-      {
-        "enchanted": "(maybe",
-      },
-      {
-        "enchanted": "normalize",
-      },
-      {
-        "enchanted": "nested",
-      },
-      {
-        "text": "paragraphs)"",
+        "text": "All text should be quoted, it's clearer that way. & blockquotes already should contain paragraphs. (maybe normalize nested paragraphs)",
       },
     ],
     [
diff --git a/src/notcl.test.ts b/src/notcl.test.ts
index b956aef..7912a0f 100644
--- a/src/notcl.test.ts
+++ b/src/notcl.test.ts
@@ -33,12 +33,12 @@ b`)
       expect(
         parse(String.raw`a\\
                          b`)
-      ).toEqual([true, [[{ text: "a\\\\" }], [{ enchanted: "b" }]]]));
+      ).toEqual([true, [[{ text: "a\\" }], [{ enchanted: "b" }]]]));
     it("does not split commands on folded newlines with escaped backslashes", () =>
       expect(
         parse(String.raw`a\\\
                          b`)
-      ).toEqual([true, [[{ text: "a\\\\" }, { enchanted: "b" }]]]));
+      ).toEqual([true, [[{ text: "a\\" }, { enchanted: "b" }]]]));
 
     it("accepts semicolons as command separators", () =>
       expect(parse("a;b")).toEqual([
@@ -100,7 +100,37 @@ b`)
     //   no before folded newline that gives us backslashes
   });
 
-  describe("interpolated words", () => {});
+  describe("interpolated words", () => {
+    it("accepts empty quotes", () =>
+      expect(parse('""')).toEqual([true, [[{ text: "" }]]]));
+    it("accepts quoted words", () =>
+      expect(parse('"a"')).toEqual([true, [[{ text: "a" }]]]));
+    it("accepts quoted words with spaces", () =>
+      expect(parse('"a b"')).toEqual([true, [[{ text: "a b" }]]]));
+    it("allows escaped quotes inside a quote", () =>
+      expect(parse('"a\\"b"')).toEqual([true, [[{ text: 'a"b' }]]]));
+
+    it("does not allow trailing characters after a closing quote", () =>
+      expect(parse('""a')).toMatchObject([false, {}]));
+
+    it("accepts escaped spaces", () =>
+      expect(parse("a\\ b")).toEqual([true, [[{ text: "a b" }]]]));
+
+    it("treats a non-leading quote as a plain character", () =>
+      expect(parse('a"')).toEqual([true, [[{ text: 'a"' }]]]));
+    it("treats a non-leading brace as a plain character", () =>
+      expect(parse("a{")).toEqual([true, [[{ text: "a{" }]]]));
+    it("treats an escaped quote as a plain character", () =>
+      expect(parse('\\"')).toEqual([true, [[{ text: '"' }]]]));
+    it("treats an escaped brace as a plain character", () =>
+      expect(parse("\\{")).toEqual([true, [[{ text: "{" }]]]));
+    it("treats a quoted brace as a plain character", () =>
+      expect(parse('"{"')).toEqual([true, [[{ text: "{" }]]]));
+
+    // Interpolated words
+    //    variables- bare, brace
+    //    command subst
+  });
 
   describe("brace words", () => {
     it("can parse empty braces", () =>
@@ -144,15 +174,6 @@ b`)
       ).toEqual([true, [[{ text: "\\\\ " }]]]));
   });
 
-  // "Quotes"
-  //   no trailing chars
-
-  // Interpolated words
-  //    bare
-  //    quotes
-  //    variables- bare, brace
-  //    command subst
-
   describe("Misc", () => {
     test("Big mess of markup", () => {
       expect(
diff --git a/src/notcl.ts b/src/notcl.ts
index abf92a7..dcb10e5 100644
--- a/src/notcl.ts
+++ b/src/notcl.ts
@@ -4,6 +4,7 @@ import {
   Word as WordType,
   TextWord,
   EnchantedWord as EnchantedWordType,
+  SimplifyWord,
 } from "./words";
 
 export type Command = WordType[];
@@ -19,9 +20,35 @@ const EnchantedWord = Regex(/[^\]\[\}\{$\\";\s]+(?=[\s;]|$)/y)
   .map(([enchanted]) => ({ enchanted } as EnchantedWordType))
   .expects("ENCHANTED_WORD");
 
-const BasicWord = Regex(/(?!\{)[^\s;]+/y)
-  .map(([text]) => ({ text } as TextWord))
-  .expects("BASIC_WORD");
+const BackslashEscape = Regex(/\\(.)/y)
+  .expects("\\")
+  .map(([, char]) => ({ text: char }));
+
+const BareWord = Sequence(
+  AtLeast(
+    1,
+    Choose(
+      BackslashEscape,
+      Regex(/[^\s\\;]+/y)
+        .expects("CHAR")
+        .map(([text]) => ({ text }))
+    )
+  )
+).map(([pieces]) => SimplifyWord(pieces));
+
+const QuotedWord = Sequence(
+  Regex(/"/y).expects('"'),
+  AtLeast(
+    0,
+    Choose(
+      BackslashEscape,
+      Regex(/[^"\\]+/y)
+        .expects("CHAR")
+        .map(([text]) => ({ text }))
+    )
+  ),
+  Regex(/"/y).expects('"')
+).map(([, pieces]) => SimplifyWord(pieces));
 
 const Brace: Pattern<string> = Sequence(
   Regex(/\{/y).expects("{"),
@@ -40,12 +67,13 @@ const Brace: Pattern<string> = Sequence(
     )
   ),
   Regex(/\}/y).expects("}")
-).map(([_left, fragments, _right]) => fragments.join(""));
+).map(([, fragments]) => fragments.join(""));
 
 const Word = Choose<WordType>(
   EnchantedWord,
-  BasicWord,
-  Brace.map((text) => ({ text } as TextWord))
+  Brace.map((text) => ({ text } as TextWord)),
+  QuotedWord,
+  BareWord
 );
 
 const CommandTerminator = Regex(/[\n;]/y)
diff --git a/src/words.ts b/src/words.ts
index cc441da..894a826 100644
--- a/src/words.ts
+++ b/src/words.ts
@@ -29,12 +29,48 @@ export type HtmlWord = {
   html: string;
 };
 
+export type TextPiece = TextWord;
+export type VariablePiece = { variable: string };
+export type CommandPiece = { command: unknown };
+export type InterpolatedPiece = TextPiece | VariablePiece | CommandPiece;
+
 /**
  * A word whose value needs to be determined by evaluating some combination of variable and command
  * substitutions, and concatenating the results with any literal spans.
  */
 export type InterpolatedWord = {
-  pieces: [];
+  pieces: InterpolatedPiece[];
 };
 
+function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece {
+  return piece ? "text" in piece : false;
+}
+
+// safely concatenate text pieces, converting as needed
+export function Concat(left: TextPiece, right: TextPiece) {
+  return { text: left.text + right.text };
+}
+
+export function SimplifyWord(
+  pieces: InterpolatedPiece[]
+): InterpolatedWord | TextWord {
+  const consolidated: InterpolatedPiece[] = [];
+  for (const piece of pieces) {
+    const top = consolidated[consolidated.length - 1];
+    if (IsTextPiece(top) && IsTextPiece(piece)) {
+      consolidated[consolidated.length - 1] = Concat(top, piece);
+    } else {
+      consolidated.push(piece);
+    }
+  }
+
+  if (consolidated.length == 0) {
+    return { text: "" };
+  } else if (consolidated.length == 1 && IsTextPiece(consolidated[0])) {
+    return consolidated[0];
+  } else {
+    return { pieces: consolidated };
+  }
+}
+
 export type Word = EnchantedWord | TextWord | HtmlWord | InterpolatedWord;