playfair

Playfair Script Formatter
git clone https://git.stjo.hn/playfair
Log | Files | Refs | README

commit aba8adb35f8d45290a790b4cfd47b71ccaa40b94
parent 9be5f441db9428d286f33b2792694ce9be4c1a26
Author: St John Karp <contact@stjo.hn>
Date:   Mon, 13 Dec 2021 15:46:35 -0500

Rewrite Playfair for v 3.0

Playfair has languished for so long because the code as it was
written was fragile and unmaintainable. It was an early project
for me and I was a novice both at Prolog and as a programmer in
general. It was a good start, but it's long needed modernising.

My first idea was to update the syntax and some high-level stuff
without getting into the weeds of the logic. This became difficult
to do, and as a result this turned into a near complete rewrite.
The code is still a little rough, but it's more readable and
maintainable now. The Playfair-style script code is now separate
from the HTML code, allowing (with any luck) scripts to be parsed
in either direction and for new formats to be added in the future.
The Prolog is now ISO compliant, so it should run with any
interpreter.

The input/output scripts should be exactly the same as in v2.
Playfair supports the same features and should produce the same
HTML output (albeit with different indentation). I'm not yet sure
what else it needs in terms of features, but future work should
be significantly easier now that the code is essentially sane.

Diffstat:
DREADME | 69---------------------------------------------------------------------
AREADME.md | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtml.pl | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplayfair.pl | 215++++++++++++++++---------------------------------------------------------------
Ascript.pl | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 518 insertions(+), 240 deletions(-)

diff --git a/README b/README @@ -1,69 +0,0 @@ -Playfair Script Formatter -========================= - -The code for Playfair can be run locally on any machine running [SWI-Prolog](http://www.swi-prolog.org/). Prolog does all the grunt-work of parsing and formatting the play as HTML. You can then use [Prince](http://www.princexml.com/) to convert the HTML output into a PDF file. - -What is the Playfair Script Formatter? --------------------------------------- - -Writers have a colossal plague in the form of scripts. When you're writing a script you don't want to have to think about how every line is going to be formatted — and yet that's exactly what you have to do. Characters' names have to be indented, dialogue is left-aligned, stage and character directions are italicized and indented at different levels — the list is endless. I have spent hours trying to format my scripts correctly, and I certainly don't want to interrupt my writing by having to click 100 different buttons on the word processor. - -And yet the structure of a script is essentially predictable. The text is delineated in neat blocks that conform to regular patterns. Why not just write the play in plain, unformatted text and run it through a program to format it automatically? That's exactly what the Playfair Script Formatter is for. - -How to Use Playfair -------------------- - -Because Playfair expects a predictable document structure, you have to stick pretty closely to what's expected. To help out, here's a quick demo of features and a checklist of things to do before uploading your document. If you don't follow this example, your script will not pass through the formatter correctly. - -### Sample - -First up, here's a little demo of what a typical script might look like: - - @title: Time Passes Like a Gym Teacher's Laxative - @author: Karp, St John - - ACT I Scene 1 - - The stage is decorated in the manner of the Belgian Walloons. A Victrola stands in the background. A giant and a dwarf are onstage. - - The Victrola is playing "Yes! We Have No Bananas". The GIANT is pacing up and down while the DWARF attempts to put toothpaste back into the tube. - - GIANT - (complaining) - I find this entire state of affairs unsatisfactory. Don't you realize that unless we can assassinate King Pumpernickel V, the Duke is going to corner the market in rubber chickens? - (turning to the dwarf) - How can you sit there playing with your tube?! - - The DWARF looks up. - - DWARF - It's extra-minty. (indicating the toothpaste) Look, it has stripes. What's the matter, don't you *care* about stripes? - - … - - The End. - -### The Breakdown - -Let's step through this thing one at a time, shall we? You'll notice that each discrete group of text is separated either by a single new line or a double new line. This is very important, as it helps distinguish a character and their dialogue from the next character. Don't go adding extra new lines or you'll wind up with chaos. - -First up, you give your script's meta-data, such as the title and the author. The author's name must be in the format of "Last Name, First Names". These two tags are the bare minimum of data required to format your script. However, especially if you're writing a stage play, you may also want to include a dramatis personæ — no problem. For a fully-formatted character page, you can include these lines: - - @persona: Dwarf: an expert cheese-maker. - @persona: Giant: a professional baseball-player. - @time: The year three million. - @setting: Space. - -Include an extra line for each new character in your play. If you're doing a character page, you are also required to include a @time and @setting. - -Next, you'll notice a declaration of which act/scene this is. This is the notation for a stage play, but the convention for a screenplay works the same way. Instead of "ACT I Scene 1" you can just as well write "INT. SPACESHIP" or "EXT. GOLF COURSE". - -Skip this if you're writing a screenplay. If writing a stage play, you must include a paragraph of scene directions that describe the setting. - -Now that you're in the body of the script, each block of text (separated by two new lines) is either one of two things — a paragraph of stage directions or a character speaking. Stage directions describe what's happening. Those are simple enough. The character speaking is where things get fun. - -Each character block starts with the character's name followed by a new line. Then you can write the dialogue itself. Any text in parentheses will be rendered as a character direction, whether its on its own new line or whether it's in the middle of a character's dialogue. You can have as many new lines as you like in the middle of dialogue so that you can put character directions on their own line or break the dialogue up into paragraphs. Either way, do not add a double new line until you're ready for the next stage direction or the next character to speak. - -One thing that isn't generally recommended for scripts, but which I find quite handy, is adding emphasis to a word. If you want to add emphasis, enclose the text *between two asterisks*. This format is very human-readable to users who chat online, and the formatter will output the text as underlined. - -Finally, at the very end of your script, you write "The End." followed by one newline — and you're done! Save your script as a plain text, UTF-8 file with a .txt extension and run it through `playfair.pl`. diff --git a/README.md b/README.md @@ -0,0 +1,78 @@ +# Playfair Script Formatter + +The code for Playfair can be run locally on any machine running Prolog. It is written to be compliant with the ISO standard so your choice of interpreter shouldn't matter. Prolog does all the grunt-work of parsing and formatting the play as HTML. You can then use a converter such as [wkhtmltopdf](https://wkhtmltopdf.org/) to convert the HTML output into a PDF file. + +## What is the Playfair Script Formatter? + +Writers have a colossal plague in the form of scripts. When you're writing a script you don't want to have to think about how every line is going to be formatted — and yet that's exactly what you have to do. Characters' names have to be indented, dialogue is left-aligned, stage and character directions are italicized and indented at different levels — the list is endless. I have spent hours trying to format my scripts correctly, and I certainly don't want to interrupt my writing by having to click 100 different buttons on the word processor. + +And yet the structure of a script is essentially predictable. The text is delineated in neat blocks that conform to regular patterns. Why not just write the play in plain, unformatted text and run it through a program to format it automatically? That is what the Playfair Script Formatter is for. + +## How to Use Playfair + +Because Playfair expects a predictable document structure, you have to stick pretty closely to what's expected. To help out, here's a quick demo of features and a checklist of things to do before processing your document. If you don't follow this example, your script will not pass through the formatter correctly. + +### Sample + +First up, here's a little demo of what a typical script might look like: + + @title: Time Passes Like a Gym Teacher's Laxative + @author: Karp, St John + + ACT I Scene 1 + + The stage is decorated in the manner of the Belgian Walloons. A Victrola stands in the background. A second-hand car salesman and a priest are onstage. + + The Victrola is playing "Yes! We Have No Bananas". The PRIEST is pacing up and down while the SALESMAN attempts to put toothpaste back into the tube. + + PRIEST + (complaining) + I find this entire state of affairs unsatisfactory. Don't you realize that unless we can assassinate King Pumpernickel V, the Duke is going to corner the market in rubber chickens? + (turning to the salesman) + How can you sit there playing with your tube?! + + The SALESMAN looks up. + + SALESMAN + It's extra-minty. (indicating the toothpaste) Look, it has stripes. What's the matter, don't you *care* about stripes? + + … + + The End. + +### The Breakdown + +Let's step through this thing one at a time. You'll notice that each discrete group of text is separated either by a single new line or a double new line. This is very important, as it helps distinguish a character and their dialogue from the next character. Don't go adding extra new lines or you'll wind up with chaos. + +First up, you give your script's meta-data, such as the title and the author. These two tags are the bare minimum of data required to format your script. However, especially if you're writing a stage play, you may also want to include a dramatis personæ — no problem. For a fully-formatted character page, you can include these lines: + + @persona: Dwarf: an expert cheese-maker. + @persona: Giant: a professional baseball-player. + @time: The year three million. + @setting: Space. + +Include an extra line for each new character in your play. If you're doing a character page, you are also required to include a @time and @setting. + +Next, you'll notice a declaration of which act/scene this is. This is the notation for a stage play, but the convention for a screenplay works the same way. Instead of "ACT I Scene 1" you can just as well write "INT. SPACESHIP" or "EXT. GOLF COURSE". + +Skip this if you're writing a screenplay. If writing a stage play, you must include a paragraph of scene directions that describe the setting. + +Now that you're in the body of the script, each block of text (separated by two new lines) is either one of two things — a paragraph of stage directions or a character speaking. Stage directions describe what's happening. Those are simple enough. The character speaking is where things get fun. + +Each character block starts with the character's name followed by a new line. Then you can write the dialogue itself. Any text in parentheses will be rendered as a character direction, whether its on its own new line or whether it's in the middle of a character's dialogue. You can have as many new lines as you like in the middle of dialogue so that you can put character directions on their own line or break the dialogue up into paragraphs. Either way, do not add a double new line until you're ready for the next stage direction or the next character to speak. + +One thing that isn't generally recommended for scripts, but which I find quite handy, is adding emphasis to a word. If you want to add emphasis, enclose the text *between two asterisks*. This format is very human-readable and the formatter will output the text as underlined. + +Finally, at the very end of your script, you write "The End." — and you're done! Save your script as a plain text, UTF-8 file with a .txt extension and run it through `playfair.pl`. + +### Example Usage + +``` +swipl --traditional -l playfair.pl -g 'play_to_html' < script.txt +``` + +## To-Do + +- Playfair is *extremely* slow for long scripts after the 3.0 rewrite. I need to look into this and see what can be done. + +- Even after the 3.0 rewrite a lot of the code is scattered and uncommented. It needs prettying up. diff --git a/html.pl b/html.pl @@ -0,0 +1,241 @@ +% Parse an HTML-style script into its component parts. +html(parsed_script(Type, [Title, Author|Variables], Scenes)) --> + "<!DOCTYPE html>", + newline, + "<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">", + newline, + "<head>", + newline, tab, + "<meta charset=\"utf-8\" />", + newline, tab, + "<meta name=\"author\" id=\"authortag\" content=\"", text("\n", Author), "\" />", + newline, tab, + "<link rel=\"stylesheet\" href=\"common.css\" />", + newline, + html_type_css(Type), + newline, tab, + "<title>", text("\n", Title), "</title>", + newline, + "</head>", + newline, + "<body>", + newline, + html_body(Type, Title, Author, Variables, Scenes), + newline, + "</body>", + newline, + "</html>", + newline. + + +html_type_css(screen) --> + tab, + "<link rel=\"stylesheet\" href=\"scriptfrenzy_screen.css\" />". + +html_type_css(stage) --> + tab, + "<link rel=\"stylesheet\" href=\"scriptfrenzy_stage.css\" />". + + +html_body(Type, Title, Author, Variables, Scenes) --> + html_title_page(Title, Author), + newline, + html_meta_page(Variables), + newline, + html_play(Type, Scenes). + + +html_title_page(Title, Author) --> + tab, + "<div id=\"titlepage\">", + newline, tab, tab, + "<h1>", Title, "</h1>", + newline, tab, tab, + "<p id=\"author\">", Author, "</p>", + newline, tab, + "</div>". + + +html_meta_page([]) --> []. + +html_meta_page([Personae, Time, Setting]) --> + tab, + "<div id=\"metapage\">", + newline, tab, tab, + "<div>", + newline, tab, tab, tab, + "<p class=\"level3\">Dramatis Personæ</p>", + html_personae(Personae), + newline, tab, tab, + "</div>", + newline, tab, tab, + "<div>", + newline, tab, tab, tab, + "<p class=\"level3\">Time</p>", + newline, tab, tab, tab, + "<p>", text("\n", Time), "</p>", + newline, tab, tab, + "</div>", + newline, tab, tab, + "<div>", + newline, tab, tab, tab, + "<p class=\"level3\">Setting</p>", + newline, tab, tab, tab, + "<p>", text("\n", Setting), "</p>", + newline, tab, tab, + "</div>", + newline, tab, + "</div>". + + +html_personae([]) --> []. + +html_personae([Persona|Rest]) --> + newline, tab, tab, tab, + "<p>", text("\n", Persona), "</p>", + html_personae(Rest). + + +html_play(Type, Scenes) --> + tab, + "<div id=\"play\">", + newline, + html_scene_repeater(Type, Scenes), + newline, tab, tab, + "<p class=\"character end\">The End.</p>", + newline, tab, + "</div>", + newline. + + +html_scene_repeater(_, []) --> []. + +html_scene_repeater(Type, [Scene|Scenes]) --> + html_scene(Type, Scene), + newline, + html_scene_repeater(Type, Scenes). + + +% Scene definition for stage plays +html_scene(stage, scene(Act, SceneDirections, Island)) --> + tab, tab, + "<div class=\"scene\">", + newline, tab, tab, tab, + "<h3 class=\"actScene\">ACT ", Act, "</h3>", + newline, + html_scene_directions(SceneDirections), + newline, + html_island_repeater(Island), + newline, tab, tab, + "</div>". + +% Scene definition for screenplays +html_scene(screen, scene(Slug, null, Island)) --> + tab, tab, + "<div class=\"scene\">", + newline, + html_slug(Slug), + newline, + html_island_repeater(Island), + tab, tab, + "</div>". + + +html_island_repeater([]) --> []. + +html_island_repeater([Island|Rest]) --> + html_island(Island), + newline, + html_island_repeater(Rest). + + +html_island(character_dialogue(Character, Dialogue)) --> + tab, tab, tab, + "<div class=\"dialogue\">", + newline, tab, tab, tab, tab, + "<p class=\"character\">", text("\n", Character), "</p>", + newline, + html_dialogue_combo(Dialogue), + newline, tab, tab, tab, + "</div>". + +html_island(stage_directions(StageDirections)) --> + tab, tab, tab, + "<div class=\"stageDirections\">", + newline, tab, tab, tab, tab, + "<p class=\"stageDirections\">", text("\n", StageDirections), "</p>", + newline, tab, tab, tab, + "</div>". + + +html_scene_directions([]) --> []. + +html_scene_directions([SceneDirections|Rest]) --> + tab, tab, tab, + "<p class=\"sceneDirections\">", text("\n<", SceneDirections), "</p>", + newline, + html_scene_directions(Rest). + + +html_dialogue_combo([]) --> []. + +html_dialogue_combo([character_stage_directions(Directions)|Rest]) --> + html_character_stage_directions(Directions), + newline, + html_dialogue_combo(Rest). + +html_dialogue_combo([dialogue(Dialogue)|Rest]) --> + tab, tab, tab, tab, + "<p class=\"dialogue\">", + html_dialogue(Dialogue), + "</p>", + newline, + html_dialogue_combo(Rest). + + + +html_dialogue([]) --> []. + +html_dialogue([Unit|Rest]) --> + html_dialogue_unit(Unit), + html_dialogue(Rest). + + +html_dialogue_unit(emphatic(Emphatic)) --> html_emphatic(Emphatic). + +html_dialogue_unit(break(Break)) --> html_line_break(Break). + +html_dialogue_unit(neutral(Text)) --> text("\n<>*", Text). + + +html_character_stage_directions(Text) --> + tab, tab, tab, tab, + "<p class=\"characterStageDirections\">", + "(", + text("\n<>)", Text), + ")", + "</p>". + + +html_emphatic(Text) --> "<em>", text("\n<>()", Text), "</em>". + + +html_line_break([]) --> []. + +html_line_break([br|Rest]) --> + "<br />", + html_line_break(Rest). + + +html_slug(slug(int, Text)) --> + tab, tab, tab, + "<h2 class=\"slug\"> INT. ", + text("\n", Text), + "</h2>". + +html_slug(slug(ext, Text)) --> + tab, tab, tab, + "<h2 class=\"slug\"> EXT. ", + text("\n", Text), + "</h2>". + diff --git a/playfair.pl b/playfair.pl @@ -2,8 +2,8 @@ % % Filename: playfair.pl % Author: St John Karp -% Date: 30 October 2012 -% Version: 2.0 +% Date: 13 December 2021 +% Version: 3.0 % % Purpose: % A program to format stage play scripts. @@ -13,190 +13,64 @@ % %------------------------------------------------------- -play_to_html(Type, File):- - open(File, read, In, [encoding(utf8)]), - process_file(In, Script), - close(In), - script(Type, HTML, Script, []), - xml_write(HTML, [header(false)]), - %write(HTML), - halt. - - -script(Type, [element(html, [], [Head, Body])]) - --> head(Type, Head, Variables), double_break, body(Type, Body, Variables). - - -body(Type, element(body, [], [TitlePage, MetaPage, Play]), [Title, Author, Personae, Time, Setting]) --> title_page(TitlePage, Title, Author), meta_page(MetaPage, Personae, Time, Setting), play(Type, Play). - -body(Type, element(body, [], [TitlePage, Play]), [Title, Author]) --> title_page(TitlePage, Title, Author), play(Type, Play). - - -play(Type, element(div, [id = play], Play)) --> scene_repeater(Type, Scenes), double_break, end(End), single_break, {append(Scenes, [End], Play)}. - - -scene_repeater(Type, [Scene|Scenes]) --> scene(Type, Scene), double_break, scene_repeater(Type, Scenes). - -scene_repeater(Type, [Scene]) --> scene(Type, Scene). - - -title_page(element(div, [id = titlepage], [element(h1, [], [Title]), element(p, [id = author], [Author])]), Title, Author) --> []. - - -meta_page(element(div, [id = metapage], [Personae, Time, Setting]), Personae, Time, Setting) --> []. - - -% Scene definition for stage plays -scene(stage, element(div, [class = scene], Scene)) --> act(Act), double_break, scene_directions(SceneDirections), double_break, island_repeater(Island), - {append([Act|SceneDirections], Island, Scene)}. - -% Scene definition for screenplays -scene(screen, element(div, [class = scene], [Slug|Island])) --> slug(Slug), double_break, island_repeater(Island). - - -island_repeater([Island1|Island2]) --> island(Island1), double_break, island_repeater(Island2). - -island_repeater([Island]) --> island(Island). - - -island(element(div, [class = stageDirections], [StageDirections])) --> stage_directions(StageDirections). - -island(element(div, [class = dialogue], [Character|Dialogue])) --> character(Character), single_break, dialogue_combo(Dialogue). - - -scene_directions([element(p, [class = sceneDirections], [Text])]) --> text(['\n', <], Text). - -scene_directions([element(p, [class = sceneDirections], [Text])|SceneDirections]) --> text(['\n', <], Text), line_break(_), scene_directions(SceneDirections). - - -stage_directions(element(p, [class = stageDirections], [Text])) --> text(['\n'], Text). - - -character(element(p, [class = character], [Text])) --> text(['\n'], Text). - - -dialogue_combo([CharacterStageDirections, Dialogue|DialogueCombo]) --> character_stage_directions(CharacterStageDirections), single_break, dialogue(Dialogue), single_break, dialogue_combo(DialogueCombo). - -dialogue_combo([CharacterStageDirections, Dialogue]) --> character_stage_directions(CharacterStageDirections), single_break, dialogue(Dialogue). - -dialogue_combo([Dialogue|DialogueCombo]) --> dialogue(Dialogue), single_break, dialogue_combo(DialogueCombo). - -dialogue_combo([Dialogue]) --> dialogue(Dialogue). - - -dialogue(element(p, [class = dialogue], [Unit])) --> dialogue_unit(Unit). - -dialogue(element(p, [class = dialogue], [Unit|Dialogue])) --> dialogue_unit(Unit), dialogue(element(p, [class = dialogue], Dialogue)). - - -dialogue_unit(Text) --> text(['\n', <, >, '*'], Text). - -dialogue_unit(Emphatic) --> emphatic(Emphatic). +:- include('html.pl'). +:- include('script.pl'). -dialogue_unit(Break) --> line_break([Break]). - - -character_stage_directions(element(p, [class = characterStageDirections], ['(', Text, ')'])) --> ['('], text(['\n', <, >, ')'], Text), [')']. - - -emphatic(element(em, [], [Text])) --> [<, e, m, >], text(['\n', <, >, '(', ')'], Text), [<, '/', e, m, >]. - -emphatic(element(em, [], [Text])) --> [<, i, >], text(['\n', <, >, '(', ')'], Text), [<, '/', i, >]. - -emphatic(element(em, [], [Text])) --> ['*'], text(['\n', '*'], Text), ['*']. - - -character_directions(element(span, [class = characterDirections], ['(', Text, ')'])) --> ['('], text(['\n', <, >, '(', ')'], Text), [')']. - - -head(Type, element(head, [], [Charset, TitleTag, AuthorTag|Styles]), [Title, Author, Personae, Time, Setting]) --> meta_charset(Charset), styles(Type, Styles), tag_title(TitleTag, Title), single_break, tag_author(AuthorTag, Author), single_break, tag_personae(Personae), single_break, tag_time(Time), single_break, tag_setting(Setting). - -head(Type, element(head, [], [Charset, TitleTag, AuthorTag|Styles]), [Title, Author]) --> meta_charset(Charset), styles(Type, Styles), tag_title(TitleTag, Title), single_break, tag_author(AuthorTag, Author). - - -tag_title(element(title, [], [Text]), Text) --> ['@', t, i, t, l, e, ':', ' '], text(['\n'], Text). - - -tag_author(element(meta, [name = author, id = authortag, content = Full, last = Last], []), Full) --> ['@', a, u, t, h, o, r, ':', ' '], text(['\n', ','], Last), [',', ' '], text(['\n'], First), - {atomic_list_concat([First, ' ', Last], Full)}. - - -tag_time(element(div, [], [element(p, [class = level3], ['Time']), element(p, [], [Text])])) --> ['@', t, i, m, e, ':', ' '], text(['\n'], Text). - - -tag_setting(element(div, [], [element(p, [class = level3], ['Setting']), element(p, [], [Text])])) --> ['@', s, e, t, t, i, n, g, ':', ' '], text(['\n'], Text). - - -tag_personae(element(div, [], [element(p, [class = level3], ['Dramatis Personæ']), Persona])) --> persona(Persona). - -tag_personae(element(div, [], [element(p, [class = level3], ['Dramatis Personæ']), Persona|Personae])) --> persona(Persona), single_break, tag_personae(element(div, [], [_|Personae])). - - -persona(element(p, [], [Text])) --> ['@', p, e, r, s, o, n, a, ':', ' '], text(['\n'], Text). - - -%date(element(meta, [name = date, content = ], [])) --> []. - - -styles(Type, [element(link, [rel = 'stylesheet', type = 'text/css', href = 'common.css'], []), Specific]) --> styles_specific(Type, Specific). - - -styles_specific(stage, element(link, [rel = 'stylesheet', type = 'text/css', href = 'scriptfrenzy_stage.css'], [])) --> []. - -styles_specific(screen, element(link, [rel = 'stylesheet', type = 'text/css', href = 'scriptfrenzy_screen.css'], [])) --> []. - - -meta_charset(element(meta, [charset = 'utf-8'], [])) --> []. - - -single_break --> ['\n']. - - -double_break --> ['\n', '\n']. - - -line_break([element(br, [], [])]) --> [<, b, r, '/', >]. - -line_break([element(br, [], [])|Break]) --> [<, b, r, '/', >], line_break(Break). +% parse_entry. +% Read in a script file from stdin. +play_to_html:- + read_file(user_input, RawScript), + script(ParsedScript, RawScript, []), + html(ParsedScript, HTML, []), + write_codes(user_output, HTML), + halt. -act(element(h3, [class = actScene], ['A', 'C', 'T', Text])) --> ['A', 'C', 'T'], text(['\n'], Text). +% read_file(+Stream, -Codes). +% Read a file to a list of character codes. +read_file(Stream, Codes):- + get_code(Stream, Code), + read_file_next(Code, Stream, Codes). -act(element(h3, [class = actScene], ['A', 'C', 'T', Text])) --> ['A', 'c', 't'], text(['\n'], Text). +read_file_next(-1, _, []). +read_file_next(Code, Stream, [Code|Rest]):- + read_file(Stream, Rest). -slug(element(h2, [class = slug], ['I', 'N', 'T', '.', ' ', Text])) --> ['I', 'N', 'T', '.', ' '], text(['\n'], Text). -slug(element(h2, [class = slug], ['E', 'X', 'T', '.', ' ', Text])) --> ['E', 'X', 'T', '.', ' '], text(['\n'], Text). +% write_codes(+CodesList). +% Loop through a list of character codes, convert each one to a +% character, and write them to the current output stream one at +% a time. This is better than converting the whole list to an atom +% with atom_codes/2, which can trigger a segfault if the atom is too long. +write_codes(_, []). +write_codes(Stream, [X|Rest]):- + char_code(Char, X), + write(Stream, Char), + write_codes(Stream, Rest). -end(element(p, [class = 'character end'], ['The End.'])) --> ['T', 'h', 'e', ' ', 'E', 'n', 'd', '.']. +text(Forbidden, Text) --> + anything(Forbidden, Text), + { Text \= [] }. -text(Forbidden, Text) --> no_funny_business(Forbidden, Text). +anything(_, []) --> []. -no_funny_business(Forbidden, Text, List, Rest):- - not(List = ['A', 'C', 'T'|_]), - not(List = ['I', 'N', 'T', '.'|_]), - not(List = ['E', 'X', 'T', '.'|_]), - forbidden_fruit(Forbidden, List, CharList), - not(CharList = []), - atom_chars(Text, CharList), - append(CharList, Rest, List). +anything(Forbidden, [X|Rest]) --> + [X], + anything(Forbidden, Rest), + { not_forbidden(Forbidden, X) }. -forbidden_fruit(Forbidden, [First|_], []):- - member(First, Forbidden). +not_forbidden([], _). -forbidden_fruit(Forbidden, [First|Rest], [First|List]):- - not(member(First, Forbidden)), - forbidden_fruit(Forbidden, Rest, List). +not_forbidden([A|Rest], X):- + A \= X, + not_forbidden(Rest, X). -process_file(File, []):- - peek_char(File, end_of_file). +newline --> "\n". -process_file(File, [Letter|List]):- - get_char(File, Letter), - process_file(File, List). -\ No newline at end of file +tab --> "\t". diff --git a/script.pl b/script.pl @@ -0,0 +1,155 @@ +% Parse a Playfair-style script into its components parts. +script(parsed_script(Type, Variables, Scenes)) --> + script_head(Variables), + newline, newline, + script_play(Type, Scenes), + newline. + + +script_play(Type, Scenes) --> + script_scene_repeater(Type, Scenes), + script_end. + + +script_scene_repeater(_, []) --> []. + +script_scene_repeater(Type, [Scene|Scenes]) --> + script_scene(Type, Scene), + script_scene_repeater(Type, Scenes). + + +% Scene definition for stage plays +script_scene(stage, scene(Act, SceneDirections, Island)) --> + script_act(Act), + newline, newline, + script_scene_directions(SceneDirections), + newline, newline, + script_island_repeater(Island). + +% Scene definition for screenplays +script_scene(screen, scene(Slug, null, Island)) --> + script_slug(Slug), + newline, newline, + script_island_repeater(Island). + + +script_island_repeater([]) --> []. + +script_island_repeater([Island|Rest]) --> + script_island(Island), + newline, + script_island_repeater(Rest). + + +script_island(stage_directions(StageDirections)) --> + script_stage_directions(StageDirections), + newline. + +script_island(character_dialogue(Character, Dialogue)) --> + script_character(Character), + newline, + script_dialogue_combo(Dialogue). + + +script_scene_directions([Text]) --> text("\n<", Text). + +script_scene_directions([Text|SceneDirections]) --> + text("\n<", Text), + script_line_breaks(_), + scene_directions(SceneDirections). + + +script_stage_directions(Text) --> text("\n", Text). + + +script_character(Text) --> text("\n", Text). + + +script_dialogue_combo([]) --> []. + +script_dialogue_combo([character_stage_directions(Directions)|Rest]) --> + script_character_stage_directions(Directions), + newline, + script_dialogue_combo(Rest). + +script_dialogue_combo([dialogue(Dialogue)|Rest]) --> + script_dialogue(Dialogue), + newline, + script_dialogue_combo(Rest). + + +script_dialogue([]) --> []. + +script_dialogue([Unit|Rest]) --> + script_dialogue_unit(Unit), + script_dialogue(Rest). + + +script_dialogue_unit(emphatic(Emphatic)) --> script_emphatic(Emphatic). + +script_dialogue_unit(break(Break)) --> script_line_breaks(Break). + +script_dialogue_unit(neutral(Text)) --> text("\n", Text). + + +script_character_stage_directions(Text) --> "(", text("\n<>)", Text), ")". + + +script_emphatic(Text) --> "<em>", text("\n<>()", Text), "</em>". + +script_emphatic(Text) --> "<i>", text("\n<>()", Text), "</i>". + +script_emphatic(Text) --> "*", text("\n*", Text), "*". + + +script_head([Title, Author, Personae, Time, Setting]) --> + tag("title", Title), + newline, + tag("author", Author), + newline, + tags("persona", Personae), + newline, + tag("time", Time), + newline, + tag("setting", Setting). + +script_head([Title, Author]) --> + tag("title", Title), + newline, + tag("author", Author). + + +tag(Key, Value) --> "@", Key, ": ", text("\n", Value). + + +tags(Key, [Value]) --> tag(Key, Value). + +tags(Key, [Value|Values]) --> + tag(Key, Value), + newline, + tags(Key, Values). + + +script_line_break --> "<br/>". + +script_line_break --> "<br />". + + +script_line_breaks([br]) --> script_line_break. + +script_line_breaks([br|Break]) --> + script_line_break, + script_line_breaks(Break). + + +script_act(Text) --> "ACT ", text("\n", Text). + +script_act(Text) --> "Act ", text("\n", Text). + + +script_slug(slug(int, Text)) --> "INT. ", text("\n", Text). + +script_slug(slug(ext, Text)) --> "EXT. ", text("\n", Text). + + +script_end --> "The End.".