Export (0) Print
Expand All

MGrammar in a Nutshell

Microsoft Corporation

February 2009

[This documentation targets the Microsoft "Oslo" SDK January 2009 CTP and is subject to change in future releases. Blank topics are included as placeholders.]

Download the complete “MGrammar”in a Nutshell
XPS Format
PDF Format

This document provides a high level introduction to MGrammar, a language for writing textual, domain-specific languages. This document begins by covering the basics of grammar development using MGrammar before diving into specific features for input and output processing, and programming against compiled grammars. There is also coverage of modularity, and advanced grammar techniques, including parameterization and recursion.

Note: As an accompaniment to this document, you might like to read the MGrammar Language Specification, which is installed with the Microsoft Oslo SDK to the following location:

   <Microsoft "Oslo" SDK Install Folder>\documents\MGrammar Language Specification.docx.

Consider the following simple set of rules for composing a song:

•             The song must begin with a header, represented by the characters, Song.

•             A song is divided into one or more bars.

•             Each bar contains four values.

•             Each value can either be a note or a rest.

•             A note is represented by a character between A and G.

•             Each note can be flat or sharp, represented by the b and # characters, respectively.

•             A rest is represented by the dash character, -.

•             The song can be formatted using whitespace characters (spaces, carriage-returns, and line-feeds).

These rules could then be used by a song writer to compose their greatest opus as simple text:

A G - E
D C# D E
E E - D
A E - E
G F - E
D Cb D E
A E D D
G G F G

The same rules can also be used by a pianist to read the text, interpret the text as a song, and play the song. Collectively, these rules comprise the grammar for a language that is specific to the Song domain and allow composers and musicians to make music together. MGrammar (Mg) is a language that allows programmers to define grammars for textual languages that are specific to their domains of interest.

Fundamentally, grammars defined using Mg are composed from rules that specify both the characters that can be used to write programs in a particular language (e.g. "Song", "A" to "G", "b", "#", "-", and whitespace characters) and the patterns that those characters must conform to (e.g. a song is divided into bars, each of which can have four notes and/or rests). The following shows a simple grammar that demonstrates these fundamentals:

/*
    Song is a grammar that defines a language for
    writing songs composed from a single note, A.
*/
module SongSample
{
    language Song
    {
        // Main rule
        syntax Main = "A";
    }
}

In terms of look and feel, Mg looks like programming languages you may be familiar with, including M, C#, and C/C++. Features common with those languages including brace-style blocks ({...}), single-line (//) and multiple-line (/*...*/) comments, and statement delimiters (;).

The grammar itself contains a single language, Song, which is defined using the language keyword. The module keyword is used to define the name of a package (SongSample) that contains one or more language definitions. A language must be defined within a module.

The Song language contains a single syntax rule, Main. A syntax rule called Main has special meaning: it designates the rule that input must match in order to be considered valid. A language definition can have one Main rule at most.

All syntax rules are composed from one or more productions, each of which includes a pattern and an optional projection. A pattern defines both the pattern that characters must conform to and the characters themselves. A projection defines the shape of the output which, by default, is a representation of the textual input that conforms to the grammar.

The Main rule has a single pattern that is composed from a single literal, "A", to represent the musical note A. This means that the "A" character can occur in the input, and that it can only occur once. If a song is composed from either more than one note (e.g. AAAA) or a note other than A (e.g. B), the rules of the grammar are not satisfied and the song is considered invalid

The easiest way to see the grammar in action is to use Intellipad, which ships with the Microsoft Oslo SDK and is shown in Figure 1.

1: Using Intellipad to author grammars

By default, four panes are visible when authoring a grammar in Mg. You author a grammar using the top-middle (MGrammarMode) pane. You provide input to test your grammars using the top-left (DynamicParserMode) pane. As either a grammar is being written or test input is being provided, Intellipad processes the latter against the former. If both grammar and input are valid, Intellipad displays the output in the top-right (MGrammarPreviewMode) pane.

If either the grammar or the input is invalid, Intellipad displays red-squiggly lines beneath the invalid text and displays corresponding errors in the errors (HyperlinkMode) pane, as shown in Figure 2.

2: Error displayed due to invalid grammar

The next topic uses the main features of Mg to build a complete version of the Song grammar to with the rules defined at the start of this topic.

Real-world grammars need to define languages with rules that are more complex and comprehensive than can be defined using a single character literal as the Song languages currently does. For this reason, Mg provides a host of grammar features that are covered by this topic, including literals, alternatives, groups, ranges, repetition, whitespace, and greediness.

Literals are the atoms from which all rules are built. A literal defines a sequence of one or more characters that can appear in the input. A literal is surrounded by double-quotes and can consist of typical characters such as letters and numbers (e.g. "ABC" and "123"). A literal may also contain escape sequences:

•             Common (e.g. "\r")

•             Unicode (e.g. "\u000D")

Mg also supports verbatim text literals (e.g. @"123") that can extend over multiple lines.

The following example shows both single and multiple character literals:

module SongSample
{
    language Song
    {
        // Notes
        syntax A = "A";

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music A;
    }
}

The Song language now contains a new rule A, with a single pattern composed from a character literal, "A". The A rule specifies that occurrences of the "A" character can be included in the input. The Music rule, on the other hand, specifies that occurrences of the sequence of characters, "M", "u", "s", "i", and "c", can occur in the input

The pattern for the Main rule has been updated with references to the two new rules. A pattern can be composed directly from literal expressions, or indirectly by referencing other rules with patterns that are defined using literal expressions, or some combination of the two. With this in mind, the updated Main rule specifies that input can only contain a single occurrence of the character sequence, "Music", followed directly by a single occurrence of the character sequence, "A".

Unfortunately, allowing "MusicA" as the only valid song is pretty boring. Songs should at least allow a choice of the seven natural notes.

The Song language only defines rules that each contain a single pattern. In the real-world, however, rules are likely to be defined with multiple, logically related patterns to describe alternatives for valid input, that is, to consider input valid if one of the alternatives can be satisfied.

For example, the Song language should at least offer alternatives for which musical note a song can be composed from:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax A = "A";
        syntax B = "B";
        syntax C = "C";
        syntax D = "D";
        syntax E = "E";
        syntax F = "F";
        syntax G = "G";

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main
          = Music Rest
          | Music A
          | Music B
          | Music C
          | Music D
          | Music E
          | Music F
          | Music G;
    }
}

Now the Main rule has eight patterns one of which input must conform to in order for the rule to apply. That means that input is only valid if it is one of the following: "Music-", "MusicA", "MusicB", "MusicC", "MusicD", "MusicE", "MusicF", or "MusicG".

Repetitions like those that appear in the Main rule of the Song language is something most developers would like to normalize. In response, you might In this case, a grouping expression will allow the Main rule to be refactored to express the intention more accurately while considering the same input from the previous example to be valid:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax A = "A";
        syntax B = "B";
        syntax C = "C";
        syntax D = "D";
        syntax E = "E";
        syntax F = "F";
        syntax G = "G";

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax RestOrNote = Rest | A | B | C | D | E | F | G;
        syntax Main = Music RestOrNote;
    }
}

This simple grammar doesn't really require two rules, though. The following is a shorter, legal version:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax A = "A";
        syntax B = "B";
        syntax C = "C";
        syntax D = "D";
        syntax E = "E";
        syntax F = "F";
        syntax G = "G";

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music Rest | A | B | C | D | E | F | G;
    }
}

Unfortunately, it's not expressing our intention. Based on the Main rule, any of the following is valid input: "Music-", "A", "B", "C", "D", "E", "F", or "G". Using grouping solves the problem:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax A = "A";
        syntax B = "B";
        syntax C = "C";
        syntax D = "D";
        syntax E = "E";
        syntax F = "F";
        syntax G = "G";

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music (Rest | A | B | C | D | E | F | G);
    }
}

A grouping expression is surrounded by parentheses and considered a single expression with respect to peer expressions in the same pattern.

Range expressions offer another potential avenue for simplifying a grammar and improving its readability. For example, it's tedious to have to define a literal expression for each and every musical note to express the range of musical notes from "A" to "G". A range expression is a suitable alternative in this case since it more closely represents the intent of the rule that need to be defined. The following shows the Song language with new Note rule which contains a single pattern with a single range expression that is defined using the .. operator:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax Note = "A".."G";
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music (Rest | Note);
    }
}

The start and end values of a range expression must be single characters and the end value must be lexicographically larger than the start value. For example, the following range is illegal in Mg:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        // ILLEGAL: Start value of range is less than the end value
        syntax Note = "G".."A";
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music (Rest | Note);
    }
}

Note: The any keyword represents a single character in the range of all possible input characters, including alphanumeric and whitespace characters.

Mg offers a variety of ways to define grammar that considers zero or more occurrences of either a literal, reference, group, or range. Fundamentally, this equates to allowing input to contain a sequence of zero or more character or string literals. For starters, Mg offers the following repetition operators: ?, +, and *.

The ? operator specifies zero or one occurrences of a particular expression. Consider the following example:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax Note = "A".."G";
        syntax Sharp = "#";
        syntax Flat = "b";
        syntax RestOrNote = Rest | Note (Sharp | Flat)?;
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music RestOrNote;
    }
}

The grammar has been updated with three new rules, Sharp, Flat, and RestOrNote, with Main now including a reference to the latter. The result is that valid input must start with "Music" and be followed by either a rest character ("-") or that a note character ("A" to "G") that is optionally followed by either a sharp ("#") or flat ("b") character. To apply a repetition operator to two or more values, as in this example (e.g. Sharp or Flat), the expressions must be grouped.

The following list includes several examples of valid input with the updated Song language:

"Music-"
"MusicAb"
"MusicA"
"MusicA#"

The * operator extends the range of the ? operator by allowing a grammar to specify zero or more occurrences of a particular expression. By applying the * operator to the Main rule, the Song language can now allow songwriters to compose songs from one or more bars where each bar has four rests or notes:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax Note = "A".."G";
        syntax Sharp = "#";
        syntax Flat = "b";
        syntax RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music Bar*;
    }
}

The following list contains several possible inputs that are now valid:

"Music"
"MusicA"
"MusicAb"
"MusicAbAA#BC-EFG"

The + operator is simply the operator for specifying one or more occurrences of an expression. For example, "Music" is not valid according to the following grammar:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax Note = "A".."G";
        syntax Sharp = "#";
        syntax Flat = "b";
        syntax RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music Bar+;
    }
}

While the Song language requires that a song be divided into bars, it doesn't allow composers to write songs that look like they divided into bars. It's hard to discern bars from the following legal input:

"Music---EDCDEEE-DAE-EGF-EDCDEAEDDDEAC"

Furthermore, it's difficult to read in general. Whitespace is a common technique for introducing readability into text. For example, judicious use of whitespace in song input makes it easier to visualize bars and notes:

"Music
A G – E
D C# D E
E E – D
A E – E
G F – E
D Cb D E
A E D D
G G F G"

Unfortunately, the current grammar doesn't consider this input to be valid. First, it contains space characters that the Song language hasn't defined as being valid in the input. Second, it also contains hidden carriage-return and line-feed characters that the Song language doesn't recognize. To Mg, the previous song input looks a little more like the following:

Music\u000D\u000A
A\u0020G\u0020-\u0020E\u000D\u000A
D\u0020C#\u0020D\u0020E\u000D\u000A
E\u0020E\u0020-\u0020D\u000D\u000A
A\u0020E\u0020-\u0020E\u000D\u000A
G\u0020F\u0020-\u0020E\u000D\u000A
D\u0020Cb\u0020D\u0020E\u000D\u000A
A\u0020E\u0020D\u0020D\u000D\u000A
G\u0020G\u0020F\u0020G\u000D\u000A

\u000D, \u000A, and \u0020 are the Unicode characters for the whitespace characters carriage-return, line-feed, and space, respectively. The trick is to allow input to include whitespace while ignoring it because it's purely for formatting and not relevant to the definition of a song. Since ignoring whitespace is a common scenario for many grammars, particularly for grammars that process input from text editors, Mg allows you to identify and ignore whitespace using an interleave rule. Besides removing the use of lists and, consequently, requiring non-whitespace delimiters, the following shows the Whitespace interleave rule:

module SongSample
{
    language Song
    {
        // Notes
        syntax Rest = "-";
        syntax Note = "A".."G";
        syntax Sharp = "#";
        syntax Flat = "b";
        syntax RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music Bar+;
        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

The Whitespace interleave rule is defined using the interleave keyword, and contains three alternative patterns, one for each of the whitespace characters the Song language would like to ignore. The patterns for an interleave rule conform to the same rules as patterns for syntax rules. However, repetition has no effect even though it can be applied; when an interleave rule is defined, Mg will check input for characters that match on a character-by-character basis.

Now, whenever a whitespace character is encountered in the input during processing, it is discarded. Consequently, "C #" is valid input:

"Music
A G – E
D C # D E
E E – D
A E – E
G F – E
D Cb D E
A E D D
G G F G"

Musical purists may cringe with fright when seeing something like this. The next topic explains why they cringe.

In general, a chunk of input is only valid if it matches a sequence of one or more characters defined either directly or indirectly by the pattern or patterns of a rule.

For syntax rules, Mg consumes input, character by character, until either the rule is satisfied by the consumed input or Mg determines that the rule can't be satisfied. In the latter case, Mg prepends the consumed characters to the input and restarts consuming it against another syntax rule.

However, if a character that can be matched by an interleave rule is consumed while a syntax rule is being processed, that character is intercepted and removed by Mg instead of allowing it to be included in the consumption and matching attempt against the syntax rule. Effectively, interleave rules override syntax rules when consuming characters. To the syntax rule, it's as if the interleaved character never existed. For this reason, the Song grammar considers the input "C #" to successfully match the RestOrNote rule: the interleaved space character is removed so that only "C#" ends up being consumed against the RestOrNote rule.

If the interleaving behavior in this scenario needs to be overridden so that, for example, "C #" is considered invalid, you can use a token rule instead.

Token Rules

The Song grammar needs to ignore whitespace in general while not ignoring whitespace when matching musical notes specifically. Really, we just need to let Mg know that interleaving should be temporarily disabled when input characters are being consumed against the RestOrNote rule. Interleaving can be disabled in this way using token rules. The following example shows how by redefining the RestOrNote syntax rule as a token rule:

module SongSample
{
    language Song
    {
        // Notes
        token Rest = "-";
        token Note = "A".."G";
        token Sharp = "#";
        token Flat = "b";
        token RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;
        // Keywords
        syntax Music = "Music";
        // Main rule
        syntax Main = Music Bar+;
        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

The following now happens when Mg attempts to match the RestOrNote rule against "C #", the following happens:

  1. The "C" is consumed against the RestOrNote rule.
  2. The " " is consumed against the RestOrNote rule since interleaving has been temporarily disabled.
  3. Mg considers C to have matched the RestOrNote rule.
  4. The " " is prepended to the input.
  5. The token rule has finished consumption.
  6. The " " is consumed by the Whitespace interleave rule.
  7. The "#" is consumed but cannot be matched by any rule in the Song language.
  8. The song is considered invalid.

Note that the syntax rules that the RestOrNote rule references were also updated to token rules since token rules cannot include references to syntax rules. However, syntax rules can include references to token, interleave, and syntax rules.

Just as Mg offers a host of features for processing text input, it also offers features transforming input text into structured data output. As mentioned earlier, projections define the transformations that take place. Mg infers the shape and content of the output using projections defined by the syntax rules of the language being processed. Each production in each syntax rule allows you to explicitly define an optional projection to influence the shape and content of the output. In the absence of explicit projection definitions, however, Mg reverts to in-built projections to determine the shape and content of the output.

By default, Mg outputs a graph representation of the input that conforms to the grammar used to process the input. For the song grammar, the default output looks like the following:

Main[
  Music[
    "Music"
  ],
  [
    Bar[
      "A",
      "G",
      "-",
      "E"
    ],
    ... elided for brevity ...
    Bar[
      "G",
      "G",
      "F",
      "G"
    ]
  ]
]

The default projection produced by Mg is a graph. The nodes of the graph are derived from the syntax rules of the grammar (e.g. Main, Music, Bar, and Bar+). Literal values or tokens generate string values that are attached to the nodes for the syntax rules that include them (e.g. "Music", RestOrNote).

While the default output graph is suitable for at least testing purposes, the default output graph for many real-world grammars might not be appropriate. Mg offers several options you have for altering the output, including removing and adding nodes as well as naming and renaming them. All custom projection support is enabled by the => operator.

To rename a node, such as from the incomprehensible "Main" node to the "Song" node, you would use the => operator like so:

module SongSample
{
    language Song
    {
        ...
        syntax Main = m:Music b:Bar+ => Song[m, b];
        ...
    }
}

In the production's pattern, the variables m and b are assigned to the Music and Bars references, respectively. In its projection, grammar is used to define a new node, Song, with Music and Bar+ as its child nodes. The new node includes the child nodes by referencing the variables assigned to them. The => operator instructs Mg to replace the default node that would have been generated for the syntax Main rule with the new Song node. The following shows the resulting output:

Song[
  ... elided for brevity ...
]

Note: The => operator can only be used with syntax rules.

Often, nodes represent rules that require input to contain characters for structural or validation purposes, such as keywords like Music, without requiring nodes to be created for them in the projection. A node that Mg is generating by default can be removed by simply not including it in the projection:

module SongSample
{
    language Song
    {
        ...
        syntax Main = Music b:Bar+ => Song[b];
        ...
    }
}

This produces the following output:

Song[
  [
    Bar[
      "A",
      "G",
      "-",
      "E"
    ],
    ... elided for brevity ...
    Bar[
      "G",
      "G",
      "F",
      "G"
    ]
  ]
]

Note that the m variable was removed since it is not used in the projection.

From the output, you'll notice that the Song node has a child node without a name. This node is being created implicitly by Mg due to the use of the + operator in the Main rule. The same will happen for all repetition operators and likewise for groups. You can remove these nodes using the valuesof operator, like so:

module SongSample
{
    language Song
    {
        ...
        syntax Main = Music b:Bar+ => Song[valuesof(b)];
        ...
    }
}

This causes the following output to be generated:

Song[
  Bar[
    "A",
    "G",
    "-",
    "E"
  ],
  ... elided for brevity ...
  Bar[
    "G",
    "G",
    "F",
    "G"
  ]
]

The valuesof operator returns the values that are referenced by the operand passed to valuesof. The references are subsequently added to the parent of the nameless node, thereby circumventing the intermediate implicit node.

Finally, it is relatively easy to add a new node using grammar like the following:

module SongSample
{
    language Song
    {
        ...
        syntax Main = Music b:Bar+ => Song[Bars[valuesof(b)]];
        ...
    }
}

The following shows the new node, Bars:

Song[
  Bars[
    Bar[
      "A",
      "G",
      "-",
      "E"
    ],
    ... elided for brevity ...
    Bar[
      "G",
      "G",
      "F",
      "G"
    ]
  ]
]

In this example, the values returned by valuesof for the nameless node are re-parented to the new Bars node.

Note: Both the default projections and the custom projections we've seen created ordered sets of nodes. Order is derived from the use of square brackets in the projections. You can also project unordered output by using curly braces instead of square brackets. Unordered output is required if you are targeting M. If so, you should use curly braces throughout your projections.

One situation where it's essential to write custom projections is when defining recursive syntax rules. Essentially, a recursive rule is a rule that defines a pattern that includes references to itself. One use for recursive rules is to define repetition without using the repetition operators.

For example, the following shows the recursive version of the Song language:

module SongSample
{
    language Song
    {
        // Notes
        token Rest = "-";
        token Note = "A".."G";
        token Sharp = "#";
        token Flat = "b";

        token RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;

        // One or more bars (recursive technique)
        syntax Bars
          = Bar        // One occurrence
          | Bars Bar   // Two or more occurrences
        ;

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music b:Bars => Song[Bars[valuesof(b)]];

        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

The new Bars rule allows one or more occurrences of Bar as defined by two productions. The first production allows one Bar. The second allows multiple Bar occurrences by augmenting one occurrence of a Bar to the Bars rule.

Conceptually, the new grammar is the same as the previous version, although with differently shaped output:

Song[
  Bars[
    Bars[
      Bars[
        Bars[
          Bars[
            Bars[
              Bars[
                Bars[
                  Bar[
                    "A",
                    "G",
                    "-",
                    "E"
                  ]
                ],
                Bar[
                  "D",
                  "C#",
                  "D",
                  "E"
                ]
              ],
    ... elided for brevity ...
  ]
]

Unlike earlier, the output doesn't contain a single Bars node with multiple child Bar nodes. By default, Mg infers a new node for the Bars syntax rule every time the Bars syntax rule is processed. When its first production is processed, a Bars node with a single child Bar node is generated. When its second production is subsequently processed one or more times, a new Bars node and Bar node are generated and parented to the previously generated Bars node. The result is the lopsided output from above.

A simple technique to reproduce a single Bars node like earlier uses the valuesof operator in the projection for the production with the recursive reference:

module SongSample
{
    language Song
    {
        ...
        syntax Bars
          = b:Bar => Bars[b]
          | bs:Bars b:Bar => Bars[valuesof(bs), b]
        ;
        ...
    }
}

Instead of adding a child Bars node and Bar node to the previous Bars node, the use of valuesof augments a list of Bar nodes with a new Bar node.

This produces the desired output:

Song[
  Bars[
    Bar[
      "A",
      "G",
      "-",
      "E"
    ],
    ... elided for brevity ...
    Bar[
      "G",
      "G",
      "F",
      "G"
    ]
  ]
]

In this example, the recursive rule produces a list of nodes (as * and + do implicitly). Using a recursive technique allows more fine-grained control over the productions and projections of a list rule.

Real-world grammars tend to be complex enough that rules like the list rule from the previous topic may be reused. Mg supports the ability to generalize a rule using parameters in order that the rule can be used by other rules. The following example shows the Song grammar that includes a parameterized rule for lists, List.

module SongSample
{
    language Song
    {
        // Notes
        token Rest = "-";
        token Note = "A".."G";
        token Sharp = "#";
        token Flat = "b";

        token RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;

        syntax List(element)
          = e:element => [e]
          | es:List(element) e:element => [valuesof(es), e]
        ;

        // One or more bars (recursive technique)
        syntax Bars = bs:List(Bar) => Bars[valuesof(bs)];

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music b:Bars => Song[Bars[valuesof(b)]];

        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

The pattern for the Bars rule contains a reference to the parameterized List rule, which expects a parameter (element) that is a reference to the rule that defines the type of the list. When the List rule is processed, Mg replaces the occurrence of the element parameter (element) in the parameterized rule's productions with the passed rule reference (Bar). Additionally, custom projections are defined to produce a flat list. The following example is logically equivalent to the previous example:

module SongSample
{
    language Song
    {
        // Notes
        token Rest = "-";
        token Note = "A".."G";
        token Sharp = "#";
        token Flat = "b";

        token RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;

        syntax ListOfBars
          = b:Bar => [b]
          | bs:ListOfBars b:Bar => [valuesof(bs), b];

        // One or more bars (recursive technique)
        syntax Bars = bs:ListOfBars => Bars[valuesof(bs)];

        // Keywords
        syntax Music = "Music";

        // Main rule
        syntax Main = Music b:Bars => Song[Bars[valuesof(b)]];

        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

The List rule can be reused by any other syntax rules within the Song language definition, such as the new Bars rule shown here:

module SongSample
{
    language Song
    {
        // Notes
        token Rest = "-";
        token Note = "A".."G";
        token Sharp = "#";
        token Flat = "b";

        token RestOrNote = Rest | Note (Sharp | Flat)?;
        syntax Bar = RestOrNote RestOrNote RestOrNote RestOrNote;

        syntax List(element)
          = e:element => [e]
          | es:List(element) e:element => [valuesof(es), e]
        ;

        // One or more bars (recursive technique)
        syntax Bars = bs:List(Bar) => Bars[valuesof(bs)];

        syntax ASong = Music bs:Bars => Song[Bars[valuesof(bs)]];
        syntax Songs = ss:List(ASong) => Songs[valuesof(ss)];

        // Main rule
        syntax Main = Album ss:Songs => Album[ss];

        // Keywords
        syntax Music = "Music";
        syntax Album = "Album";

        // Ignore whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        interleave Whitespace = LF | CR | Space;
    }
}

This version of the Song language allows multiple songs to be provided in the input. Both the Songs and Bars rule reuse the List rule to this end.

Modularity features of Mg, enabled using the language, module, import, and export keywords, support two main scenarios: multiple languages either shared by other languages within the same module or by other languages in other modules. This topic explores both scenarios.

One language can be reused by one or more other languages within the same module. Or, more accurately, the rules that are defined by one language are visible to and usable from rules in other languages within the same module. The following example shows the Common language with rules that are used by other languages in the SongSample module.

module SongSample
{
    language Common
    {
        // Parameterized List rule
        syntax List(element)
          = e:element => [e]
          | es:List(element) e:element => [valuesof(es), e]
        ;

        // Whitespace
        syntax LF = "\u000A";
        syntax CR = "\u000D";
        syntax Space = "\u0020";
        syntax Whitespace = LF | CR | Space;
    }

    language Song
    {
        ...
        // Reference List rule in Common language
        syntax Bars = bs:Common.List(Bar) => Bars[valuesof(bs)];
        syntax Songs = ss:Common.List(ASong) => Songs[valuesof(ss)];
        ...
        // Reference Whitespace rule in Common language
        interleave Whitespace = Common.Whitespace;
    }

    language AnotherLanguage
    {
        ...
        // Reference List rule in Common language
        syntax ListOfAs = Common.List("A");
        ...
    }
}

Within a single module all rules in one language are visible to rules in other languages defined within the same module. A rule defined in one language can reference a rule in another language by qualifying the rule name with the language name (e.g. bs:Common.List(Bar)).

Mg builds on this concept to enable rule and language reuse across multiple modules.

Consider what happens when the previous example is refactored across multiple modules:

module Library
{
    language Common { ... }
}

module SongSample
{
    language Song { ... }
    language AnotherLanguage { ... }
}

The rules of the Common language are no longer visible to the rules in the Song and AnotherLanguage languages because former and latter are defined within entirely different module scopes. Additional work using the export and import keywords is required to make the Common language and its rules visible beyond its module scope.

First, any language that needs to be visible to other modules needs to be explicitly exported using the export keyword:

module Library
{
    // Make the Common language (and its rules) visible to languages
    // in other modules that share the SongSample namespace
    export Common;

    language Common { ... }
}

module SongSample
{
    language Song
    {
        // Can't use rules from Common language in the SongSample module
        ...
    }

    language AnotherLanguage
    {
        // Can't use rules from Common language in the SongSample module
        ...
    }
}

By default, all rules defined by a language are visible when the language is exported.

Second, the exported language and its rules need to be imported by modules whose languages need to use the exported language's rules. The equally appropriate import keyword is used to achieve this:

module Library
{
    // Make the Common language (and its rules) visible to languages
    // in other modules that share the SongSample namespace
    export Common;

    language Common { ... }
}

module SongSample
{
    // Make all languages exported by the SongSample module
    // visible to the languages in the Languages module.
    import Library;

    language Song
    {
        ...
        // Reference List rule in Common language
        syntax Bars = b:Common.List(Bar) => Bars[valuesof(b)];
        ...
        // Reference Whitespace rule in Common language
        interleave Whitespace = Common.Whitespace;
    }

    language AnotherLanguage
    {
        ...
        // Reference List rule in Common language
        syntax ListOfAs = Common.List("A");
        ...
    }
}

By default, all exported languages from one module become visible when that module is imported by another module. However, it is possible to import only languages that are needed:

module Library
{
    // Make the Common language (and its rules) visible to languages
    // in other modules that share the SongSample namespace
    export Common;

    language Common { ... }
    language NotSoCommon { ... }
}

module SongSample
{
    // Make only the common languages exported by the SongSample module
    // visible to the languages in the Languages module.
    import Library { Common };

    language Song
    {
        ...
        // Reference List rule in Common language
        syntax Notes = n:Common.List(Note) => Notes[valuesof(n)];
        ...
        // Reference Whitespace rule in Common language
        interleave Whitespace = Common.Whitespace;
    }

    language AnotherLanguage
    {
        ...
        // Reference List rule in Common language
        syntax ListOfAs = Common.List('A');
        ...
    }
}

Even though Common is explicitly imported, you still need to qualify references to its rules using the language name. Or, you can provide an alias for the language name that you can qualify references with instead:

module Library
{
    // Make the Common language (and its rules) visible to languages
    // in other modules that share the Library namespace
    export Common;

    language Common { ... }
    language NotSoCommon { ... }
}

module SongSample
{
    import Library { Common as C };

    language Song
    {
        ...
        // Reference List rule in Common language
        syntax Notes = n:C.List(Note) => Notes[valuesof(n)];
        ...
        // Reference Whitespace rule in Common language
        interleave Whitespace = C.Whitespace;
    }

    language AnotherLanguage
    {
        ...
        // Reference List rule in Common language
        syntax ListOfAs = C.List('A');
        ...
    }
}

The previous topics explored the key Mg features for developing grammars and the Intellipad tool for editing and testing grammars. In most cases, however, developers will likely need to compile a grammar into some format that enables their own applications to process input, generate corresponding output, and do something with that output.

This topic covers the main options and formats available for compiling grammars into packages that can be programmed against.

Mg grammars are compiled using the Mg compiler: Mg.exe. The following example shows the simplest usage of Mg.exe to compile the Song grammar.

c:\>Mg.exe song.Mg
Microsoft (R) "Codename G" Compiler version 1.0.0907.0
Copyright (C) Microsoft Corporation. All rights reserved.

The default output of a successfully compiled grammar is an Mg image file which has the .mgx extension and, by default, has the same name as the grammar (e.g. song.mgx). An .mgx file is an Open Packaging Convention (OPC) compliant file that contains: the following:

  • One intermediate representation of each language in the source grammar that defines a Main syntax rule.
  • A single manifest.

The manifest is an XML file, always called [Content_Types].xml, which identifies the name of each intermediate representation in the .mgx file.

This manifest specifies that the .mgx file contains a single intermediate representation for the Song language. Each intermediate grammar representation that is embedded in the .mgx image is a XAML file whose name is a concatenation of the source module name (e.g. SongSample) and language name (e.g. Song).

The XAML document is used by Mg to instantiate a .NET Framework-base class for processing input according to the rules of the language.

.mgx files are the building blocks upon which you can process input using either command-line or programmatic options provided by Mg.

Command-Line

Just as Intellipad processes your grammar against input, you can do the same from the command line with an .mgx file using mgx.exe. The following example shows how.

c:\>mgx.exe input.song /reference:song.mgx /language:SongSample.Song
Microsoft (R) "Codename G" Compiler version 1.0.0907.0
Copyright (C) Microsoft Corporation. All rights reserved.

mgx.exe takes as parameters an input file (input.song), an input .mgx file (/reference:song.mgx), and, if the .mgx file contains intermediate representations for more than one language, the case-sensitive name of the language to process the input with (/language:SongSample.Song).

If the grammar successfully processes the input file, a file containing an M representation of the processed input is generated. The name of the generated file is the same as the input file (e.g. input.m). The following example shows the contents of the generated M file.

module input {
    Album {
        [0] {
            Songs {
                [0] {
                    Song {
                        [0] {
                            Bars {
                                [0] {
                                    Bar {
                                        [0] {
                                            "A"
                                        },
                                        [1] {
                                            "G"
                                        },
                                        [2] {
                                            "-"
                                        },
                                        [3] {
                                            "E"
                                        }
                                    }
                                },
                ... elided for brefity ...
            }
        }
    }
}

mgx.exe is a great option for generating M that you can load into the Repository. On the other hand, if you need to create a custom application or runtime to process input against a grammar, you can drop down to the .NET Framework and program with the same assemblies used by both Intellipad and mgx.exe.

Programmatic

Mg ships with a programming framework that consists of two assemblies: System.Dataflow.dll and Microsoft.M.Grammar.dll. System.Dataflow.dll contains the various .NET types that you can use to process grammars.

Recall that a grammar that is compiled into an .mgx image file is XAML which is a script for instantiating a dynamic parser. The dynamic parser is implemented as the DynamicParser class from the System.Dataflow namespace (in System.Dataflow.dll). Your .NET application can load a .mgx image file, extract the XAML, and create an instance of the DynamicParser type that is configured as per the XAML by calling the LoadParserFromMgx method of the MGrammarCompiler from the Microsoft.M.Grammar namespace (in Microsoft.M.Grammar.dll):

using System;
using Microsoft.M.Grammar; // MGrammarCompiler
using System.Dataflow; // DynamicParser
namespace SongProcessor
{
  class Program
  {
    static void Main(string[] args)
    {
      string imageFileName = "song.mgx";
      string languageName = null;
      try
      {
        // Load image, extract XAML representation of grammar, and
        // instantiate dynamic parser
        DynamicParser language =
          MGrammarCompiler.LoadParserFromMgx(imageFileName, languageName);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Processing error: {0}", ex.Message);
      }
      Console.ReadLine();
    }
  }
}

LoadParserFromMgx accepts two parameters: the path to the mgx image file and the name of the language in the mgx image file to create a dynamic parser for. If there is only one language in the image, which occurs when only one language definition in the grammar base defines a syntax Main rule, MGrammarCompiler can imply the language to create a dynamic parser for when null is passed as the language name. If the mgx image file contains multiple languages, the name (concatenation of module name and language name) must be passed:

using System;
using Microsoft.M.Grammar; // MGrammarCompiler
using System.Dataflow; // DynamicParser
namespace SongProcessor
{
  class Program
  {
    static void Main(string[] args)
    {
      ...
      string languageName = "SongSample.Song";
      ...
    }
  }
}

To process input against the dynamic parser, you start by calling the ParseObject method of the DynamicParser object:

using System;
using Microsoft.M.Grammar; // MGrammarCompiler
using System.Dataflow; // DynamicParser
namespace SongProcessor
{
  class Program
  {
    static void Main(string[] args)
    {
      string imageFileName = "song.mgx";
      string languageName = null;
      try
      {
        // Load image, extract XAML representation of grammar, and
        // instantiate dynamic parser
        DynamicParser language =
          MGrammarCompiler.LoadParserFromMgx(imageFileName, languageName);
        // Process the input using the grammar and get the root node
        // from the output
        object rootNode =
          language.ParseObject(input, ErrorReporter.Standard);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Processing error: {0}", ex.Message);
      }
      Console.ReadLine();
    }
  }
}

The output is a graph of objects that represent the non-leaf and leaf nodes of the graph. You can use code like the following to recursively navigate the graph:

using System;
using System.Collections.Generic; // Dictionary<T, U>
using System.Dataflow; // DynamicParser, GraphBuilder
using Microsoft.M.Grammar; // MGrammarCompiler
namespace SongProcessor
{
  class Program
  {
    static void Main(string[] args)
    {
      string imageFileName = "Song.mgx";
      string languageName = "SongSample.Song";
      string input = "input.song";
      try
      {
        // Load image and instantiate a corresponding dynamic parser
        DynamicParser language =
          MGrammarCompiler.LoadParserFromMgx(imageFileName, languageName);
        // Process the input using the grammar and get the root node
        // of the output graph
        object rootNode =
          language.ParseObject(input, ErrorReporter.Standard);
        // Recursively navigate the output graph using GraphBuilder
        EnumerateOutputGraph(new GraphBuilder(), rootNode, 0);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Processing error: {0}", ex.Message);
      }
      Console.ReadLine();
    }
    static void EnumerateOutputGraph(
      GraphBuilder builder, object node, int level)
    {
      // Display label for current syntax rule
      Console.WriteLine("Syntax Rule={0}", builder.GetLabel(node));
      // Enumerate all child values
      foreach (object childNode in builder.GetSequenceElements(node))
      {
        // If the child node is a string, it is matched input so display
        // its value and skip to next value since it can't have children
        if (childNode is string)
        {
          string note = childNode.ToString();
          Console.WriteLine("Matched Input={0}", note);
          continue;
        }
        // Otherwise, the value is a node for a syntax rule, so
        // enumerate its children
        EnumerateOutputGraph(builder, childNode, level);
      }
    }
  }
}

To navigate the output graph, you use a GraphBuilder object from the System.Dataflow namespace. To get the name of a node, you call GraphBuilder.GetLabel. To get all the child nodes of the current node, you call GraphBuilder.GetSequenceElements. You can only call either GetLabel or GetSequenceElements if the node that the GraphBuilder is currently pointing to is a non-leaf node. If the current location in the output graph is a string value, GraphBuilder returns a string object.

A common scenario for Mg developers will be to build a .NET application that includes both grammar and code that operates over the image generated by the grammar, just like we've seen in the previous topics. The SDK ships with support for this scenario and a sample that demonstrates how to use the support.

Fundamentally, .NET applications are built using MSBuild. Each .NET application will have an MSBuild project file that describes application, all the pieces (code files, resources, etc) that need to be compiled, and how they will be compiled. The SDK contains special MSBuild support that allows .Mg files to also be included in an MSBuild project file and be built as part of the MSBuild application. The following is an example of an MSBuild project file for a C# project that includes and builds an Mg file (with typical C# MSBuild data elided for brevity):

<?xml version="1.0" encoding="utf-8"?>
<Project
    DefaultTargets="Build"
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MBinaries32Path Condition="'$(MBinaries32Path)'==''">
      $(ProgramFiles)\Microsoft Oslo SDK 1.0\Bin
    </MBinaries32Path>
    <MGrammarTargetsPath Condition="'$(MGrammarTargetsPath)'==''">
      $(ProgramFiles)\MsBuild\Microsoft\M\Grammar\v1.0
    </MGrammarTargetsPath>
    <RootNamespace>Song</RootNamespace>
    <AssemblyName>Song</AssemblyName>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <MgTarget>Mgx</MgTarget>
    ...
  </PropertyGroup>
  ...
  <ItemGroup>
    <Reference Include="$(MBinaries32Path)\Microsoft.M.Grammar.dll" />
    <Reference Include="$(MBinaries32Path)\System.Dataflow.dll" />
    <Reference Include="$(MBinaries32Path)\Xaml.dll" />
    ...
  </ItemGroup>
  <ItemGroup>
    <MgCompile Include="Song.Mg" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="MusicHelper.cs" />
    <Compile Include="Program.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="Input.song">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
  <Import Project="$(MGrammarTargetsPath)\Microsoft.M.Grammar.targets" />
</Project>

This is a standard C# MSBuild project file (song.csproj) that has been augmented with MSBuild support for Mg. The MSBuild support is introduced by the Microsoft.M.Grammar.targets file. This includes a special MSBuild task for Mg files, called MgCompile, which provides the same ability to compile MGrammar files as Mg.exe. In the sample, the song.Mg file is configured as an MgCompile build item that will be built as an mgx image, as specified by the MgTarget parameter.

The MBinaries32Path and MGrammarTargetsPath properties are included to help MSBuild find the Mg and M files required for Mg in their SDK install locations.

Compiling the MSBuild project is the same as it is for any typical MSBuild-based project:

c:\song>msbuild song.csproj

The song.Mg file is built as an mgx image that is added as a resource to the compiled C# application. To load it into the dynamic parser uses .NET resource management support to load the mgx image as a string that is passed to an overload of the LoadParserFromMgx method that accepts streams:

using System;
using System.Collections.Generic; // Dictionary<T, U>
using System.Reflection;
using System.IO;
using System.Dataflow; // DynamicParser, GraphBuilder
using System.Threading; // Thread
using Microsoft.M.Grammar; // MGrammarCompiler
namespace SongProcessor
{
  class Program
  {
    static void Main(string[] args)
    {
      string imageFileName = "song.mgx";
      string languageName = "SongSample.Song";
      string input = "input.song";
      ...
      // Load image and instantiate a corresponding dynamic parser
      DynamicParser language = null;
      Assembly assembly = Assembly.GetExecutingAssembly();
      using (Stream stream =
        assembly.GetManifestResourceStream(imageFileName))
      {
        // Load image and instantiate a corresponding dynamic parser
        language =
          MGrammarCompiler.LoadParserFromMgx(stream, languageName);
      }
      // Process the input using the grammar and get the root node
      // of the output graph
      object rootNode =
        language.ParseObject(input, ErrorReporter.Standard);
      ...
    }
  }
}

Note: The complete Song sample that includes MSBuild support for Mg is installed with the Microsoft Oslo SDK in the following folder: <Microsoft "Oslo" SDK Install Folder>\Samples\MGrammar\Song.

Mg provides a host of features for authoring and programming with grammars. The Microsoft Oslo SDK includes both the Mg compiler and a variety of additional support for authoring and developing grammars, including a textual IDE, Intellipad, command-line tools (Mg.exe and mgx.exe), and MSBuild support that allows you to integrate Mg development into your existing C# applications.

Beyond building your own grammars to learn Mg, it is recommended that you read the MGrammar Language Specification.

Note: Intellipad is installed to the following location:

   <Microsoft "Oslo" SDK Install Folder>\bin\intellipad\ipad.exe

To use Intellipad to author Mg grammars, you need to open Intellipad and switch the default StandardMode pane to an MGrammarMode pane:

  1. To launch Intellipad, choose Start | All Programs | Microsoft Codename "Oslo" SDK | Tools | Intellipad (Samples Enabled).
  2. To edit text in MGrammarMode, either:
    • Select the current pane (StandardMode) and press Ctrl+Shift+G.
    • Choose File | Open and select an .Mg file from the Open File dialog box.

Once the pane has been switched into MGrammarMode, you can author Mg grammars with full syntax checking, as shown in Figure 3.

3: Editing Mg Grammar in Intellipad

To use Intellipad to both author Mg grammars and dynamically test it against input, you need to do a little more work:

  1. To launch Intellipad, choose Start | All Programs | Microsoft Codename "Oslo" SDK | Tools | Intellipad (Samples Enabled).
  2. To load a, select the default pane (StandardMode) and press Ctrl+Shift+T.
  3. From the Open File dialog box, select an .Mg file.

The result is that Intellipad:

  • Switches the StandardMode pane to a DynamicParserMode pane that is used to edit input.
  • Creates an MGrammarMode pane to load in and edit the selected .Mg file.
  • Creates an MGrammarPreviewMode pane to display projections.
  • Creates an HyperlinkMode pane to display input and grammar errors and warnings.

Figure 4 shows the result:

4: Editing Mg Grammar with Dynamic Input Processing

Note: Mg.exe is installed to the following location:

   <Microsoft "Oslo" SDK Install Folder>\bin\Mg.exe

Usage

Mg [grammarFile(s) ...]

Positional Parameters

[grammarFile(s) ...] Grammar file names, wildcards allowed, e.g.  *.Mg.

  Parameters

Parameter

Alias

Description

[-reference:CLRAssemblyFilename,...]

-r

CLR reference assembly filename (can specify multiple references with multiple switches).

[-recurse:Wildcard,...]

Include all files in the current directory and subdirectories according to the wildcard specifications.

[-tracefile:TraceFilename]

-tr

Specify a trace filename.

[-conflictLogFile:ConflictLogFilename]

-cl

Specify a conflict log filename. 

[-dotParserTableFile:DOTFilename]

-dp

Specify a dot parser table output filename.

[-reportConflicts]

-rc

Print conflicts through reporting.

[-target:
  [None | Mgx | MgxResource | Xaml | Source]
]

-t

Target type.  Legal values: None, Mgx, MgxResource, Xaml, Source.

[-out:
  [Prefix | Filename]
]

-o

For target type (Mg, Xaml, Source), specify output prefix (directory or file prefix) – default is current directory.

For target type (Mgx or MgxResource),  specify output file – default is name of first grammar file.

[-warnaserror:WarningNumber,...]

-w

Treat specific warnings as errors.

[-warnaserror]

-w

Treat all warnings as errors.

[-nowarn:WarningNumber,...]

-nw

Ignore specific warnings.

[-typeCheckActions]

-tc

Currently turns off the type checking of method bodies in code files passed to Mg.

[-define:Symbol]

-d

Define conditional compilation symbol(s), e.g.  /d:DEBUG

[-keyfile:StrongNameKeyFilename]

-kf

Specify a strong name key filename.

[-versionfile:VersionFilename]

-vf

Specify a version filename.

[-?]

-h

Display this usage message.

[-nologo]

Suppress program logo.

[@<file>]

Read <file> for more parameters.

Note: mgx.exe is installed to the following location:

   <Microsoft "Oslo" SDK Install Folder>\bin\mgx.exe

Usage

mgx [dataFile(s) ...]

Positional Parameters

[dataFile(s) ...]

  Parameters

Parameter

Alias

Description

[-reference:MgxFilename,...]

-r

Name of .mgx image with grammar to process against datafile(s).

[-recurse:Wildcard,...]

Include all files in the current directory and subdirectories according to the wildcard specifications

[-target: [M | Xaml]]

-t

Target type.  Legal values: M, Xaml.

[-out:[Prefix | OutputDirectory]]

-o

Specify file prefix or output directory – default is no prefix and files generated in current directory.

[-language:STRING,...]

-l

If multiple language parsers exist in the referenced .mgx file, specifies which language to use to parse input data.

[-?]

-h

Display this usage message.

[-nologo]

Suppress program logo.

[@<file>]

Read <file> for more parameters.

Show:
© 2014 Microsoft