Move the syntax description into its own file
diff --git a/readme.md b/readme.md
index 85dc3d1..3e01846 100644
--- a/readme.md
+++ b/readme.md
@@ -170,163 +170,9 @@
* `-o <filename>` is used to specify the output file, otherwise this is set to
`out.spv`.
-#### Format
+#### Syntax
-The assembly attempts to adhere to the binary form as closely as possible using
-text names from that specification. Here is an example.
-
-```
-OpCapability Shader
-OpMemoryModel Logical Simple
-OpEntryPoint GLCompute %3 "main"
-OpExecutionMode %3 LocalSize 64 64 1
-OpTypeVoid %1
-OpTypeFunction %2 %1
-OpFunction %1 %3 None %2
-OpLabel %4
-OpReturn
-OpFunctionEnd
-```
-
-<a name="assignment-format"></a>
-In order to improve the text's readability, the `<result-id>` generated by an
-instruction can be moved to the beginning of that instruction and followed by
-an `=` sign. This allows us to distinguish between variable defs and uses and
-locate variable defs more easily. So, the above example can also be written as:
-
-```
- OpCapability Shader
- OpMemoryModel Logical Simple
- OpEntryPoint GLCompute %3 "main"
- OpExecutionMode %3 LocalSize 64 64 1
-%1 = OpTypeVoid
-%2 = OpTypeFunction %1
-%3 = OpFunction %1 None %2
-%4 = OpLabel
- OpReturn
- OpFunctionEnd
-```
-
-Each line encapsulates one and only one instruction, or an OpCode and all of its
-operands. OpCodes use the names provided in section 3.28 Instructions of the
-SPIR-V specification, immediate values such as Addressing Model, Memory Model,
-etc. use the names provided in sections 3.2 Source Language through 3.27
-Capability of the SPIR-V specification. Literals strings are enclosed in quotes
-`"<string>"` while literal numbers have no special formatting.
-
-##### ID Definitions & Usage
-
-An ID definition pertains to the `<result-id>` of an OpCode, and ID usage is any
-input to an OpCode. All IDs are prefixed with `%`. To differentiate between
-defs and uses, we suggest using the second format shown in the above example.
-
-##### Named IDs
-
-The assembler also supports named IDs, or virtual IDs, which greatly improves
-the readability of the assembly. The same ID definition and usage prefixes
-apply. Names must begin with an character in the range `[a-z|A-Z]`. The
-following example will result in identical SPIR-V binary as the example above.
-
-```
- OpCapability Shader
- OpMemoryModel Logical Simple
- OpEntryPoint GLCompute %main "main"
- OpExecutionMode %main LocalSize 64 64 1
- %void = OpTypeVoid
-%fnMain = OpTypeFunction %void
- %main = OpFunction %void None %fnMain
-%lbMain = OpLabel
- OpReturn
- OpFunctionEnd
-```
-
-##### Arbitrary Integers
-
-*Warning: Not all of the following has been implemented*
-
-When writing tests it can be useful to emit an invalid 32 bit word into the
-binary stream at arbitrary positions within the assembly. To specify an
-arbitrary word into the stream the prefix `!` is used, this takes the form
-`!<integer>`. Here is an example.
-
-```
-OpCapability !0x0000FF00
-```
-
-Any word in a valid assembly program may be replaced by `!<integer>` -- even
-words that dictate how the rest of the instruction is parsed. Consider, for
-example, the following assembly program:
-
-```
-%4 = OpConstant %1 123 456 789 OpExecutionMode %2 LocalSize 11 22 33
-OpExecutionMode %3 InputLines
-```
-
-The words `OpConstant`, `LocalSize`, and `InputLines` may be replaced by random
-`!<integer>` values, and the assembler will still assemble an output binary with
-three instructions. It will not necessarily be valid SPIR-V, but it will
-faithfully reflect the input text.
-
-You may wonder how the assembler recognizes the instruction structure (including
-instruction boundaries) in the text with certain crucial words replaced by
-arbitrary integers. If, say, `OpConstant` becomes a `!<integer>` whose value
-differs from the binary representation of `OpConstant` (remember that this
-feature is intended for fine-grain control in SPIR-V testing), the assembler
-generally has no idea what that value stands for. So how does it know there is
-exactly one `<id>` and three number literals following in that instruction,
-before the next one begins? And if `LocalSize` is replaced by an arbitrary
-`!<integer>`, how does it know to take the next three words (instead of zero or
-one, both of which are possible in the absence of certainty that `LocalSize`
-provided)? The answer is a simple rule governing the parsing of instructions
-with `!<integer>` in them:
-
-When a word in the assembly program is a `!<integer>`, that integer value is
-emitted into the binary output, and parsing proceeds differently than before:
-each subsequent word not recognized as an OpCode is emitted into the binary
-output without any checking; when a recognizable OpCode is eventually
-encountered, it begins a new instruction and parsing returns to normal. (If a
-subsequent OpCode is never found, then this alternate parsing mode handles all
-the remaining words in the program. If a subsequent OpCode is in an
-[assignment format](#assignment-format), the ID preceding it begins a new
-instruction, even if that ID is itself a `!<integer>`.)
-
-The assembler processes the words encountered in alternate parsing mode as
-follows:
-
-* If the word is a number literal, it outputs that number as one or more words,
- as defined in the SPIR-V specification for Literal Number.
-* If the word is a string literal, it outputs a sequence of words representing
- the string as defined in the SPIR-V specification for Literal String.
-* If the word is an ID, it outputs the ID's internal number. If no such number
- exists yet, a unique new one will be generated. (Uniqueness is at the
- translation-unit level: no other ID in the same translation unit will have the
- same number.)
-* If the word is another `!<integer>`, it outputs that integer.
-* Any other word causes the assembler to quit with an error.
-
-Note that this has some interesting consequences, including:
-
-* When an OpCode is replaced by `!<integer>`, the integer value should encode
- the instruction's word count, as specified in the physical-layout section of
- the SPIR-V specification.
-
-* Consecutive instructions may have their OpCode replaced by `!<integer>` and
- still produce valid SPIR-V. For example, `!262187 %1 %2 "abc" !327739 %1 %3 6
- %2` will successfully assemble into SPIR-V declaring a constant and a
- PrivateGlobal variable.
-
-* Enums (such as `DontInline` or `SubgroupMemory`, for instance) are not handled
- by the alternate parsing mode. They must be replaced by `!<integer>` for
- successful assembly.
-
-* The `=` sign cannot be processed by the alternate parsing mode if the OpCode
- following it is a `!<integer>`.
-
-* When replacing a named ID with `!<integer>`, it is possible to generate
- unintentionally valid SPIR-V. If the integer provided happens to equal a
- number generated for an existing named ID, it will result in a reference to
- that named ID being output. This may be valid SPIR-V, contrary to the
- presumed intention of the writer.
+See [`syntax.md`](syntax.md) for the assembly language syntax.
### Disassembler
diff --git a/syntax.md b/syntax.md
new file mode 100644
index 0000000..246f5b4
--- /dev/null
+++ b/syntax.md
@@ -0,0 +1,157 @@
+# SPIR-V Assembly language syntax
+
+The assembly attempts to adhere to the binary form as closely as possible using
+text names from that specification. Here is an example.
+
+```
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %3 "main"
+OpExecutionMode %3 LocalSize 64 64 1
+OpTypeVoid %1
+OpTypeFunction %2 %1
+OpFunction %1 %3 None %2
+OpLabel %4
+OpReturn
+OpFunctionEnd
+```
+
+<a name="assignment-format"></a>
+In order to improve the text's readability, the `<result-id>` generated by an
+instruction can be moved to the beginning of that instruction and followed by
+an `=` sign. This allows us to distinguish between variable defs and uses and
+locate variable defs more easily. So, the above example can also be written as:
+
+```
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint GLCompute %3 "main"
+ OpExecutionMode %3 LocalSize 64 64 1
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+```
+
+Each line encapsulates one and only one instruction, or an OpCode and all of its
+operands. OpCodes use the names provided in section 3.28 Instructions of the
+SPIR-V specification, immediate values such as Addressing Model, Memory Model,
+etc. use the names provided in sections 3.2 Source Language through 3.27
+Capability of the SPIR-V specification. Literals strings are enclosed in quotes
+`"<string>"` while literal numbers have no special formatting.
+
+## ID Definitions & Usage
+
+An ID definition pertains to the `<result-id>` of an OpCode, and ID usage is any
+input to an OpCode. All IDs are prefixed with `%`. To differentiate between
+defs and uses, we suggest using the second format shown in the above example.
+
+## Named IDs
+
+The assembler also supports named IDs, or virtual IDs, which greatly improves
+the readability of the assembly. The same ID definition and usage prefixes
+apply. Names must begin with an character in the range `[a-z|A-Z]`. The
+following example will result in identical SPIR-V binary as the example above.
+
+```
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 64 64 1
+ %void = OpTypeVoid
+%fnMain = OpTypeFunction %void
+ %main = OpFunction %void None %fnMain
+%lbMain = OpLabel
+ OpReturn
+ OpFunctionEnd
+```
+
+## Arbitrary Integers
+
+*Warning: Not all of the following has been implemented*
+
+When writing tests it can be useful to emit an invalid 32 bit word into the
+binary stream at arbitrary positions within the assembly. To specify an
+arbitrary word into the stream the prefix `!` is used, this takes the form
+`!<integer>`. Here is an example.
+
+```
+OpCapability !0x0000FF00
+```
+
+Any word in a valid assembly program may be replaced by `!<integer>` -- even
+words that dictate how the rest of the instruction is parsed. Consider, for
+example, the following assembly program:
+
+```
+%4 = OpConstant %1 123 456 789 OpExecutionMode %2 LocalSize 11 22 33
+OpExecutionMode %3 InputLines
+```
+
+The words `OpConstant`, `LocalSize`, and `InputLines` may be replaced by random
+`!<integer>` values, and the assembler will still assemble an output binary with
+three instructions. It will not necessarily be valid SPIR-V, but it will
+faithfully reflect the input text.
+
+You may wonder how the assembler recognizes the instruction structure (including
+instruction boundaries) in the text with certain crucial words replaced by
+arbitrary integers. If, say, `OpConstant` becomes a `!<integer>` whose value
+differs from the binary representation of `OpConstant` (remember that this
+feature is intended for fine-grain control in SPIR-V testing), the assembler
+generally has no idea what that value stands for. So how does it know there is
+exactly one `<id>` and three number literals following in that instruction,
+before the next one begins? And if `LocalSize` is replaced by an arbitrary
+`!<integer>`, how does it know to take the next three words (instead of zero or
+one, both of which are possible in the absence of certainty that `LocalSize`
+provided)? The answer is a simple rule governing the parsing of instructions
+with `!<integer>` in them:
+
+When a word in the assembly program is a `!<integer>`, that integer value is
+emitted into the binary output, and parsing proceeds differently than before:
+each subsequent word not recognized as an OpCode is emitted into the binary
+output without any checking; when a recognizable OpCode is eventually
+encountered, it begins a new instruction and parsing returns to normal. (If a
+subsequent OpCode is never found, then this alternate parsing mode handles all
+the remaining words in the program. If a subsequent OpCode is in an
+[assignment format](#assignment-format), the ID preceding it begins a new
+instruction, even if that ID is itself a `!<integer>`.)
+
+The assembler processes the words encountered in alternate parsing mode as
+follows:
+
+* If the word is a number literal, it outputs that number as one or more words,
+ as defined in the SPIR-V specification for Literal Number.
+* If the word is a string literal, it outputs a sequence of words representing
+ the string as defined in the SPIR-V specification for Literal String.
+* If the word is an ID, it outputs the ID's internal number. If no such number
+ exists yet, a unique new one will be generated. (Uniqueness is at the
+ translation-unit level: no other ID in the same translation unit will have the
+ same number.)
+* If the word is another `!<integer>`, it outputs that integer.
+* Any other word causes the assembler to quit with an error.
+
+Note that this has some interesting consequences, including:
+
+* When an OpCode is replaced by `!<integer>`, the integer value should encode
+ the instruction's word count, as specified in the physical-layout section of
+ the SPIR-V specification.
+
+* Consecutive instructions may have their OpCode replaced by `!<integer>` and
+ still produce valid SPIR-V. For example, `!262187 %1 %2 "abc" !327739 %1 %3 6
+ %2` will successfully assemble into SPIR-V declaring a constant and a
+ PrivateGlobal variable.
+
+* Enums (such as `DontInline` or `SubgroupMemory`, for instance) are not handled
+ by the alternate parsing mode. They must be replaced by `!<integer>` for
+ successful assembly.
+
+* The `=` sign cannot be processed by the alternate parsing mode if the OpCode
+ following it is a `!<integer>`.
+
+* When replacing a named ID with `!<integer>`, it is possible to generate
+ unintentionally valid SPIR-V. If the integer provided happens to equal a
+ number generated for an existing named ID, it will result in a reference to
+ that named ID being output. This may be valid SPIR-V, contrary to the
+ presumed intention of the writer.