/// AST definition for VijiN declarative markup. /// /// VijiN scene files are inspired by stage play manuscripts. /// Most of a scene is "spoken" text, written as a speaker name followed by a colon. /// Directives in square brackets indicate how the content should be presented, /// rather than defining the content itself. /// /// The syntax includes some formatting indicators. /// Text between forward slashes is emphasized, with the expectation that it /// will be rendered in italics. /// Double asterisks indicate yelled text, to be rendered in bold or large font. /// Underscores mark whispered text. /// /// Almost all syntactic constraints can be escaped by quoting text in backticks. /// The exception is that backtick-quoted text cannot contain backticks or newlines. module VijiN.Syntax /// Name of a variable to expand. /// /// A variable name may contain any text that doesn't have other syntactic meaning, /// including whitespace. It may be quoted in backticks. type Variable = Variable of string /// A single term of text, as an element of a spoken line, directive argument, /// character name, &c. type Term = TextTerm of string | VarTerm of Variable type DirectivePrefix = string type DirectiveInfix = string type DirectiveSuffix = string /// Fixed, syntactic components of a directive. May be interspersed with arguments. /// Directives in a directive line may be separated with either dots or commas; /// in the former case, the variant is DirectiveOperator, and in the latter /// case, the variant is ContinueDirective with the implication that the prefix /// is the same as that of the previous operator in the block. type DirectiveOperator = | DirectiveOperator of DirectivePrefix * DirectiveInfix option * DirectiveSuffix option | ContinueDirective of DirectiveInfix option * DirectiveSuffix option /// A single argument to a directive. type DirectiveArg = Arg of Term list type DirectivePrefixArg = DirectiveArg type DirectiveInfixArg = DirectiveArg type DirectiveSuffixArg = DirectiveArg type DirectiveListArg = DirectiveArg /// A directive instance, the composition of an operator with any arguments. /// An argument may be absent even if a later argument is provided. type Directive = DirectiveOperator * DirectivePrefixArg * DirectiveInfixArg * DirectiveSuffixArg /// A directive block. Either a single directive with a possibly empty list of /// arguments, or a list of directives type DirectiveBlock = | SingleDirective of Directive * DirectiveListArg list | DirectiveList of Directive list /// The speaker of a spoken line. /// The narrator is syntactically indicated by an empty name, i.e. a line /// beginning with a colon. Narrated lines may also be given as lines spoken /// by "Narrator" when there is no defined character named the same, but that /// is resolved semantically rather than syntactically. type Speaker = | Narrator | Speaker of Term list /// An inline directive appearing between a speaker name and their line. type InlineDirective = Directive /// The text of a spoken line. type SpokenText = /// Plain spoken or narrated text. | Plain of Term list /// Emphasized text written between slashes. Intended to be rendered in italics. | Emphasis of Term list /// Yelled text written between double asterisks. Intended to be rendered /// in bold or large text. | Yell of Term list /// Whispered text written between underscores. Intended to be rendered /// in small text. | Whisper of Term list /// A spoken line. The character is absent for narrated lines. type Spoken = Speaker * InlineDirective list * SpokenText list // TODO: non-empty list /// A comment. Text between both square and round brackets, without nesting. type Comment = Comment of string /// A line of script. type Line = | SpokenLine of Spoken | DirectiveLine of DirectiveBlock | CommentLine of Comment type LineNumber = LineNo of int type PageNumber = PageNo of int /// A complete line of script along with metadata. type ScriptLine = LineNumber * PageNumber * Line // TODO: other diffing metadata /// An entire parsed scene. type Scene = ScriptLine list module private Parse = open FParsec type UserState = { /// The list of all available parsers. /// Never changes within a parsing unit. directiveParsers: seq * Parser * Parser> } type Parser<'t> = Parser<'t, UserState> let ws = unicodeSpaces /// Unquoted terms may contain most characters, including whitespace, but /// not those characters used elsewhere in the syntax: square brackets, /// curly brackets, stops, commas, or colons. let unquotedTerm: Parser<_> = noneOf "[]{}.,:`" |> manyChars /// Quoted terms can be used to escape from syntactic requirements almost /// anywhere, including inside variable names, character names, directive /// arguments, &c. /// /// A quoted term is expressed as arbitrary text between backticks (`). /// They cannot themselves contain backticks or newlines. let quotedTerm: Parser<_> = noneOf "`\n" |> manyChars |> between (pchar '`') (pchar '`') /// Text is the concatenation of quoted and unquoted terms. let text: Parser<_> = quotedTerm <|> unquotedTerm |> manyStrings /// A variable use is text between curly brackets. let variable: Parser<_> = text |> between (pchar '{') (pchar '}') |>> Variable /// A term is either a variable or text, possibly quoted. let term: Parser<_> = (variable |>> VarTerm) <|> (text |>> TextTerm) /// A dot-ended directive contains a fixed-prefix directive instance and /// zero or more continued directives separated by commas. let dotDirective prefix infix suffix: Parser = // TODO: continued directives pipe4 (prefix .>> ws) (((preturn [] .>>. infix) <|> (many term .>>. infix)) .>> ws) (((preturn [] .>>. suffix) <|> (many term .>>. suffix)) .>> ws) (many term .>> ws .>> skipChar '.' .>> ws) (fun pfx (parg, ifx) (iarg, sfx) sarg -> (DirectiveOperator (pfx, ifx, sfx), Arg parg, Arg iarg, Arg sarg)) /// Parse the speaker of a spoken line, through the colon. let speaker = ws >>. ((skipChar ':' >>% Narrator) <|> (many1 term |>> Speaker .>> ws .>> skipChar ':')) "speaker"