vijin/Syntax.fs

146 lines
6.3 KiB
Forth
Raw Normal View History

2024-02-05 21:28:41 -06:00
/// 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.
2024-01-12 23:27:48 -06:00
module VijiN.Syntax
2024-02-05 21:28:41 -06:00
/// 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.
2024-01-12 23:27:48 -06:00
type Variable = Variable of string
2024-02-05 21:28:41 -06:00
/// A single term of text, as an element of a spoken line, directive argument,
/// character name, &c.
2024-01-14 20:50:19 -06:00
type Term = TextTerm of string | VarTerm of Variable
2024-01-12 23:27:48 -06:00
2024-01-14 20:50:19 -06:00
type DirectivePrefix = string
type DirectiveInfix = string
2024-02-05 21:28:41 -06:00
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
2024-01-12 23:27:48 -06:00
2024-02-05 21:28:41 -06:00
/// A single argument to a directive.
2024-01-14 20:50:19 -06:00
type DirectiveArg = Arg of Term list
2024-02-05 21:28:41 -06:00
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
2024-01-12 23:27:48 -06:00
2024-02-05 21:28:41 -06:00
/// 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.
2024-01-12 23:27:48 -06:00
type InlineDirective = Directive
2024-02-05 21:28:41 -06:00
/// The text of a spoken line.
2024-01-12 23:27:48 -06:00
type SpokenText =
2024-02-05 21:28:41 -06:00
/// Plain spoken or narrated text.
2024-01-14 20:50:19 -06:00
| Plain of Term list
2024-02-05 21:28:41 -06:00
/// Emphasized text written between slashes. Intended to be rendered in italics.
2024-01-14 20:50:19 -06:00
| Emphasis of Term list
2024-02-05 21:28:41 -06:00
/// Yelled text written between double asterisks. Intended to be rendered
/// in bold or large text.
2024-01-14 20:50:19 -06:00
| Yell of Term list
2024-02-05 21:28:41 -06:00
/// Whispered text written between underscores. Intended to be rendered
/// in small text.
2024-01-14 20:50:19 -06:00
| Whisper of Term list
2024-02-05 21:28:41 -06:00
/// A spoken line. The character is absent for narrated lines.
type Spoken = Speaker * InlineDirective list * SpokenText list // TODO: non-empty list
2024-01-12 23:27:48 -06:00
2024-02-05 21:28:41 -06:00
/// A comment. Text between both square and round brackets, without nesting.
2024-01-12 23:27:48 -06:00
type Comment = Comment of string
2024-02-05 21:28:41 -06:00
/// A line of script.
2024-01-12 23:27:48 -06:00
type Line =
2024-01-14 20:50:19 -06:00
| SpokenLine of Spoken
2024-02-05 21:28:41 -06:00
| DirectiveLine of DirectiveBlock
2024-01-14 20:50:19 -06:00
| CommentLine of Comment
2024-01-12 23:27:48 -06:00
2024-02-05 21:28:41 -06:00
type LineNumber = LineNo of int
type PageNumber = PageNo of int
/// A complete line of script along with metadata.
2024-01-12 23:27:48 -06:00
type ScriptLine = LineNumber * PageNumber * Line
// TODO: other diffing metadata
2024-02-05 21:28:41 -06:00
/// 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<DirectivePrefix, UserState> * Parser<DirectiveInfix option, UserState> * Parser<DirectiveSuffix option, UserState>>
}
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)
2024-01-12 23:27:48 -06:00
2024-02-05 21:28:41 -06:00
/// 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<Directive> =
// 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"