diff options
author | Ralph Amissah <ralph@amissah.com> | 2016-10-01 14:12:13 -0400 |
---|---|---|
committer | Ralph Amissah <ralph@amissah.com> | 2019-04-10 15:14:13 -0400 |
commit | ba1712e77b31704fd9ba16d14e15518e7a7dd104 (patch) | |
tree | 1a0d3233fb611b68dbf43e098a41a0d9378e9ace | |
parent | update sdlang, start looking to using dub remote dependencies (diff) |
0.7.0 using dub remote dependencies (local src related to sdlang removed)
60 files changed, 4528 insertions, 13076 deletions
diff --git a/data/pod/sisu-manual-0.3.3_/conf/sisu_document_make b/data/pod/sisu-manual-0.3.3_/conf/sisu_document_make new file mode 100644 index 0000000..1e75412 --- /dev/null +++ b/data/pod/sisu-manual-0.3.3_/conf/sisu_document_make @@ -0,0 +1,12 @@ +[make] +breaks = "break=1" +home_button_text = "{doc-reform}http://doc-reform.org; {sources / git}http://git.sisudoc.org/; {SiSU}http://sisudoc.org" + +# footer = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +# home_button_text = "{sisu.png }http://sisudoc.org" + +links = [ + "{doc-reform}http://doc-reform.org", + "{sources / git}http://git.sisudoc.org/", + "{SiSU}http://sisudoc.org" +] diff --git a/data/pod/sisu-manual-0.3.3_/media/text/en/sisu_markup.sst b/data/pod/sisu-manual-0.3.3_/media/text/en/sisu_markup.sst new file mode 100644 index 0000000..f98ace8 --- /dev/null +++ b/data/pod/sisu-manual-0.3.3_/media/text/en/sisu_markup.sst @@ -0,0 +1,1961 @@ +# SiSU 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +#substitute = [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +bold = "Debian|SiSU" +italics = "Linux|GPL|LaTeX|SQL" +breaks = "new=:B; break=1" +home_button_text = [ + "{doc-reform}http://doc-reform.org", + "{sources / git}http://git.sisudoc.org/", + "{SiSU}http://sisudoc.org" +] +footer = [ + "{SiSU}http://sisudoc.org", + "{git}http://git.sisudoc.org" +] + +# home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +# footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" +# substitute = "/[$]{2}[{]sisudoc[}]/,'www.sisudoc.org'" +# substitute = "/${sisudoc}/,'www.sisudoc.org'" +# home_button_image = "{won_benkler.png }http://cyber.law.harvard.edu/wealth_of_networks/Main_Page" + +:A~ @title @creator + +:B~ SiSU Markup +={ SiSU markup:test } + +1~markup Introduction to SiSU Markup~{ From sometime after SiSU 0.58 it should be possible to describe SiSU markup using SiSU, which though not an original design goal is useful. }~ + +2~ Summary + +SiSU source documents are plaintext (UTF-8)~{ files should be prepared using UTF-8 character encoding }~ files +={ SiSU markup:description } + +All paragraphs are separated by an empty line. + +Markup is comprised of: *~markup-summary { * }#internal-links + +_* at the top of a document, the document header made up of semantic meta-data about the document and if desired additional processing instructions (such an instruction to automatically number headings from a particular level down) + +_* followed by the prepared substantive text of which the most important single characteristic is the markup of different heading levels, which define the primary outline of the document structure. Markup of substantive text includes: + +_1* heading levels defines document structure + +_1* text basic attributes, italics, bold etc. + +_1* grouped text (objects), which are to be treated differently, such as code blocks or poems. + +_1* footnotes/endnotes + +_1* linked text and images + +_1* paragraph actions, such as indent, bulleted, numbered-lists, etc. + +2~ Markup Rules, document structure and metadata requirements +={ SiSU markup:rules and requirements } + +minimal content/structure requirement, minimum being: + +[metadata] + +``` code +title "SiSU" \ + subtitle="Markup" + +creator \ + author="Amissah, Ralph" +``` + +[levels] + +``` code +A~ (level A [title]) + +1~ (at least one level 1 [segment/(chapter)]) +``` +={ output:code markup example;SiSU markup output:code block (tic syntax);code block:tic syntax } + +structure rules (document heirarchy, heading levels): + +there are two sets of heading levels ABCD (title & parts if any) and 123 (segment & subsegments if any) +={ SiSU markup:heading levels } + +sisu has the fllowing levels: + +``` code +A~ [title] + required (== 1) followed by B~ or 1~ +B~ [part] + followed by C~ or 1~ +C~ [subpart] + followed by D~ or 1~ +D~ [subsubpart] + followed by 1~ +1~ [segment (chapter)] + required (>= 1) followed by text or 2~ +text + followed by more text or 1~, 2~ + or relevant part +2~ [subsegment] + followed by text or 3~ +text + followed by more text or 1~, 2~ or 3~ + or relevant part +3~ [subsubsegment] + followed by text +text + followed by more text or 1~, 2~ or 3~ or relevant part, see *() +``` + +Rules: + +``` code +- level A~ is the title and is mandatory +- there can only be one level A~ +- heading levels BCD, are optional and there may be several of each + (where all three are used corresponding to e.g. Book Part Section) + - sublevels that are used must follow each other sequentially + (alphabetically), +- heading levels A~ B~ C~ D~ are followed by other heading levels rather + than substantive text + which may be the subsequent sequential (alphabetic) heading part level + or a heading (segment) level 1~ +- there must be at least one heading (segment) level 1~ + (the level on which the text is segmented, in a book would correspond + to the Chapter level) +- additional heading levels 1~ 2~ 3~ are optional and there may be several + of each +- heading levels 1~ 2~ 3~ are followed by text (which may be followed by + the same heading level) + and/or the next lower numeric heading level (followed by text) + or indeed return to the relevant part level + (as a corollary to the rules above substantive text/ content + must be preceded by a level 1~ (2~ or 3~) heading) +``` + +2~ Markup Examples +={ SiSU markup:locating examples } + +3~ Online +={ SiSU markup:examples online } + +Online markup examples are available together with the respective outputs produced from http://www.jus.uio.no/sisu/SiSU/examples.html or from http://www.jus.uio.no/sisu/sisu_examples/ + +There is of course this document, which provides a cursory overview of sisu markup and the respective output produced: http://www.jus.uio.no/sisu/sisu_markup/ + +an alternative presentation of markup syntax: /usr/share/doc/sisu/on_markup.txt.gz + +3~ Installed +={ SiSU markup:examples installed } + +With SiSU installed sample skins may be found in: /usr/share/doc/sisu/markup-samples (or equivalent directory) and if sisu-markup-samples is installed also under: /usr/share/doc/sisu/markup-samples-non-free + +1~headers Markup of Headers +={ SiSU markup:headers} + +Headers contain either: semantic meta-data about a document, which can be used by any output module of the program, or; processing instructions. + +Note: the first line of a document may include information on the markup version used in the form of a comment. Comments are a percentage mark at the start of a paragraph (and as the first character in a line of text) followed by a space and the comment: + +code{ + +% this would be a comment + +}code +={ output:code markup example;SiSU markup output:code block (curly brace syntax);code block:curly brace syntax } + +2~ Sample Header +={ SiSU markup:sample header} + +This current document is loaded by a master document that has a header similar to this one: + +``` code +# SiSU 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" +``` + +Looking back a bit: + +code{ + +# SiSU master 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +bold = "Debian|SiSU" +italics = "Linux|GPL|LaTeX|SQL" +breaks = "new=:B; break=1" +home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" + +}code + +2~ Available Headers +={ SiSU markup:headers available } + +Header tags appear at the beginning of a document and provide meta information on the document (such as the Dublin Core), or information as to how the document as a whole is to be processed. All header instructions take the form @headername: or on the next line and indented by once space :subheadername: All Dublin Core meta tags are available + +!_ @identifier: +information or instructions + +where the "identifier" is a tag recognised by the program, and the "information" or "instructions" belong to the tag/identifier specified + +Note: a header where used should only be used once; all headers apart from @title: are optional; the @structure: header is used to describe document structure, and can be useful to know. + +This is a sample header + +% (Dublin Core in fuschia, other information headers in cyan, markup instructions in red): + +code{ + +# SiSU 8.0 + +}code + +code{ + +[title] +main = "SiSU" +subtitle = "Markup" +language = "English" + +}code + +code{ + +[creator] +author = [Lastname, First names] +illustrator = [Lastname, First names] +translator = [Lastname, First names] +prepared_by = [Lastname, First names] + +}code + +code{ + +[date] +created = [year or yyyy-mm-dd] +issued = [year or yyyy-mm-dd] +available = [year or yyyy-mm-dd] +published = [year or yyyy-mm-dd] +modified = [year or yyyy-mm-dd] +valid = [year or yyyy-mm-dd] +added_to_site = [year or yyyy-mm-dd] +translated = [year or yyyy-mm-dd] + +}code + +code{ + +[rights] +copyright = "Copyright (C) [Year and Holder]" +license = "[Use License granted]" +text = "[Name, Year]" +translation = "[Name, Year]" +illustrations = "[Name, Year]" + +# check rest + +}code + +code{ + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" +keywords = "list" +loc = "[Library of Congress classification]" +dewey = "[Dewey classification]" + +}code + +code{ + +[identify] +isbn = "[ISBN]" +oclc = "" + +}code + + +code{ + +links = [ + "{SiSU }http://www.sisudoc.org", + "{ FSF }http://www.fsf.org", +] + +}code + +code{ + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +bold = "Debian|SiSU" # [regular expression of words/phrases to be made bold] +italics = "Linux|GPL|LaTeX|SQL" # [regular expression of words/phrases to italicise] +breaks = "new=:B; break=1" +home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" +headings = text to match for each level + (e.g. PART; Chapter; Section; Article; or another: none; BOOK|FIRST|SECOND; none; CHAPTER;) + +}code + +% [original] +% language = [language] + +% [notes] +% comment: +% prefix: [prefix is placed just after table of contents] + +% header ends here, NB only @title: is mandatory [this would be a comment] +% NOTE: headings/levels below refer to 0.38 expermental markup (a conversion script provided in sisu-examples, modify.rb makes conversion between 0.37 and 0.38 markup simple) + +1~ Markup of Substantive Text +={ SiSU markup:substantive text } + +2~heading_levels Heading Levels +={ SiSU markup:heading levels } + +Heading levels are :A~ ,:B~ ,:C~ ,1~ ,2~ ,3~ ... :A - :C being part / section headings, followed by other heading levels, and 1 -6 being headings followed by substantive text or sub-headings. :A~ usually the title :A~? conditional level 1 heading (used where a stand-alone document may be imported into another) + +!_ :A~ [heading text] +Top level heading [this usually has similar content to the title @title: ] +NOTE: the heading levels described here are in 0.38 notation, see heading + +!_ :B~ [heading text] +Second level heading [this is a heading level divider] + +!_ :C~ [heading text] +Third level heading [this is a heading level divider] + +!_ 1~ [heading text] +Top level heading preceding substantive text of document or sub-heading 2, the heading level that would normally be marked 1. or 2. or 3. etc. in a document, and the level on which sisu by default would break html output into named segments, names are provided automatically if none are given (a number), otherwise takes the form 1~my_filename_for_this_segment + +!_ 2~ [heading text] +Second level heading preceding substantive text of document or sub-heading 3 , the heading level that would normally be marked 1.1 or 1.2 or 1.3 or 2.1 etc. in a document. + +!_ 3~ [heading text] +Third level heading preceding substantive text of document, that would normally be marked 1.1.1 or 1.1.2 or 1.2.1 or 2.1.1 etc. in a document + +code{ + +1~filename level 1 heading, + +% the primary division such as Chapter that is followed by substantive text, and may be further subdivided (this is the level on which by default html segments are made) + +}code + +2~ Font Attributes +={ SiSU markup:font attributes } + +!_ markup example: + +code{ + +normal text, *{emphasis}*, !{bold text}!, /{italics}/, _{underscore}_, "{citation}", +^{superscript}^, ,{subscript},, +{inserted text}+, -{strikethrough}-, #{monospace}# + +normal text + +*{emphasis}* [note: can be configured to be represented by bold, italics or underscore] + +!{bold text}! + +/{italics}/ + +_{underscore}_ + +"{citation}" + +^{superscript}^ + +,{subscript}, + ++{inserted text}+ + +-{strikethrough}- + +#{monospace}# + +}code + +!_ resulting output: + +normal text, *{emphasis}*, !{bold text}!, /{italics}/, _{underscore}_, "{citation}", +^{superscript}^, ,{subscript},, +{inserted text}+, -{strikethrough}-, #{monospace}# + +normal text + +*{emphasis}* [note: can be configured to be represented by bold, italics or underscore] + +!{bold text}! + +/{italics}/ + +_{underscore}_ + +"{citation}" + +^{superscript}^ + +,{subscript}, + ++{inserted text}+ + +-{strikethrough}- + +#{monospace}# + +2~ Indentation and bullets +={ SiSU markup:indentation and bullets } + +!_ markup example: +={ SiSU markup:indentation } + +code{ + +ordinary paragraph + +_1 indent paragraph one step + +_2 indent paragraph two steps + +_9 indent paragraph nine steps + +}code + +!_ resulting output: + +ordinary paragraph + +_1 indent paragraph one step + +_2 indent paragraph two steps + +_9 indent paragraph nine steps + +!_ markup example: +={ SiSU markup:bullets } + +code{ + +_* bullet text + +_1* bullet text, first indent + +_2* bullet text, two step indent + +}code + +!_ resulting output: + +_* bullet text + +_1* bullet text, first indent + +_2* bullet text, two step indent + +Numbered List (not to be confused with headings/titles, (document structure)) + +!_ markup example: + +code{ + +# numbered list numbered list 1., 2., 3, etc. + +_# numbered list numbered list indented a., b., c., d., etc. + +}code + +2~ Hanging Indents +={ SiSU markup:hanging indents;indented text:hanging } + +!_ markup example: + +code{ + +_0_1 first line no indent, +rest of paragraph indented one step + +_1_0 first line indented, +rest of paragraph no indent + +in each case level may be 0-9 + +}code + +!_ resulting output: + +_0_1 first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; + +A regular paragraph. + +_1_0 first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent + +in each case level may be 0-9 + +_0_1 *{live-build}* A collection of scripts used to build customized Debian Livesystems. /{live-build}/ was formerly known as live-helper, and even earlier known as live-package. + +_0_1 *{live-build}* \\ +A collection of scripts used to build customized Debian Livesystems. /{live-build}/ was formerly known as live-helper, and even earlier known as live-package. + +2~ Footnotes / Endnotes +={ SiSU markup:hanging indents;footnotes;endnotes } + +Footnotes and endnotes are marked up at the location where they would be indicated within a text. They are automatically numbered. The output type determines whether footnotes or endnotes will be produced + +!_ markup example: + +code{ + +~{ a footnote or endnote }~ + +}code + +!_ resulting output: + +~{ a footnote or endnote }~ + +!_ markup example: + +code{ + +normal text~{ self contained endnote marker & endnote in one }~ continues + +}code + +!_ resulting output: + +normal text~{ self contained endnote marker & endnote in one }~ continues + +!_ markup example: + +code{ + +normal text ~{* unnumbered asterisk footnote/endnote, insert multiple asterisks if required }~ continues + +normal text ~{** another unnumbered asterisk footnote/endnote }~ continues + +}code + +!_ resulting output: + +normal text ~{* unnumbered asterisk footnote/endnote, insert multiple asterisks if required }~ continues + +normal text ~{** another unnumbered asterisk footnote/endnote }~ continues + +!_ markup example: + +code{ + +normal text ~[* editors notes, numbered asterisk footnote/endnote series ]~ continues + +normal text ~[+ editors notes, numbered plus symbol footnote/endnote series ]~ continues + +}code + +!_ resulting output: + +normal text ~[* editors notes, numbered asterisk footnote/endnote series ]~ continues + +normal text ~[+ editors notes, numbered plus symbol footnote/endnote series ]~ continues + +!_ [discontinued] Alternative binary endnote notation (endnote pair) for footnotes/endnotes: + +code{ + +% note the endnote marker "~^" + +normal text~^ continues + +^~ endnote text following the paragraph in which the marker occurs + +}code + +standard (inline) and pair (binary) notation could not be mixed in the same document. + +The reason binary notation was provided as an option was for the conversion of documents to sisu markup. Many documents were prepared in such a way that endnotes had been previously marked up in a binary fashion, and this provided a convenient and faster way to make the document conversion, just reflect those markup practices. The reason it has been dropped is it adds a slowing step to something that needs to be done at most once and it prove to be flakey, unnecessarily so even when kept under version control. It is preferable to do a two step conversion of the previously marked up document to sisu: first to the binary/paired footnote markup, then; convert it to the proper form of inline endnote markup with a dedicated helper conversion program, keeping the resulting properly marked up text. + +2~ Links +={ SiSU markup:links (text, images);links:images|text } + +3~ Naked URLs within text, dealing with urls + +urls found within text are marked up automatically. A url within text is automatically hyperlinked to itself and by default decorated with angled braces, unless they are contained within a code block (in which case they are passed as normal text), or escaped by a preceding underscore (in which case the decoration is omitted). + +!_ markup example: + +code{ + +normal text http://www.sisudoc.org/ continues + +}code + +!_ resulting output: + +normal text http://www.sisudoc.org/ continues + +An escaped url without decoration + +!_ markup example: + +code{ + +normal text _http://www.sisudoc.org/ continues + +deb _http://www.jus.uio.no/sisu/archive unstable main non-free + +}code + +!_ resulting output: + +normal text _http://www.sisudoc.org/ continues + +deb _http://www.jus.uio.no/sisu/archive unstable main non-free + +where a code block is used there is neither decoration nor hyperlinking, code blocks are discussed later in this document + +!_ resulting output: + +code{ + +deb http://www.jus.uio.no/sisu/archive unstable main non-free +deb-src http://www.jus.uio.no/sisu/archive unstable main non-free + +}code + +3~link_text Linking Text +={ SiSU markup:links (text);links:text } + +To link text or an image to a url the markup is as follows + +!_ markup example: + +code{ + +about { SiSU }http://url.org markup + +}code + +!_ resulting output: + +about { SiSU }http://www.sisudoc.org/ markup + +A shortcut notation is available so the url link may also be provided automatically as a footnote + +!_ markup example: + +code{ + +about {~^ SiSU }http://url.org markup + +}code + +!_ resulting output: + +about {~^ SiSU }http://www.sisudoc.org/ markup + +Internal document links to a named (anchor) tagged location, including named headings named inline anchor tags *~an-inline-anchor-tag or an ocn +the heading: + +code{ + +1~markup Markup + +}code + +can be linked to as follows: + +code{ + +to find out more see { Markup }#markup + +}code + +to find out more see { Markup }#markup + +an inline anchor tag is made with the following markup *~internal-links + +code{ + +named inline anchor tags *~an-inline-anchor-tag + +}code + +and linked to the same way + +code{ + +the link { an inline anchor tag }#an-inline-anchor-tag + +}code + +the link { an inline anchor tag }#an-inline-anchor-tag or to another part of the document: { markup summary }#markup-summary + +!_ markup example: + +code{ + +about { text links }#link_text + +}code + +!_ resulting output: + +about { text links }#link_text + +Shared document collection link + +!_ markup example: + +code{ + +about { SiSU book markup examples }:SiSU/examples.html + +}code + +!_ resulting output: + +about { SiSU book markup examples }:SiSU/examples.html + +3~ Linking Images +={ SiSU markup:links (images);links:images } + +!_ markup example: + +code{ + +{ sm_tux.png 64x80 }image + +% various url linked images + +{sm_tux.png 64x80 "a better way" }http://www.sisudoc.org/ + +{sm_GnuDebianLinuxRubyBetterWay.png 100x101 "Way Better - with Gnu/Linux, Debian and Ruby" }http://www.sisudoc.org/ + +{~^ sm_ruby_logo.png "Ruby" }http://www.ruby-lang.org/en/ + +}code + +!_ resulting output: + +{ sm_tux.png }image + +{ sm_tux.png 64x80 }image + +{ sm_tux.png 64x80 "test" }image + +{ sm_tux.png }http://www.sisudoc.org/ + +{ sm_tux.png 64x80 }http://www.sisudoc.org/ + +{ sm_tux.png 64x80 "Gnu/Linux - a better way" }http://www.sisudoc.org/ + +{ sm_GnuDebianLinuxRubyBetterWay.png 100x101 "Way Better - with Gnu/Linux, Debian and Ruby" }http://www.sisudoc.org/ + +{~^ sm_ruby_logo.png "Ruby" }http://www.ruby-lang.org/en/ + +{ sm_d_image.jpg 82x128 "D for me" }https://github.com/dlang-community/d-mans + +{~^ sm_d_strip.png "D, hey no fair" }https://github.com/dlang-community/d-mans + +!_ linked url footnote shortcut + +code{ + +{~^ [text to link] }http://url.org + +% maps to: { [text to link] }http://url.org ~{ http://url.org }~ + +% which produces hyper-linked text within a document/paragraph, with an endnote providing the url for the text location used in the hyperlink + +}code + +code{ + +text marker *~name + +}code + +note at a heading level the same is automatically achieved by providing names to headings 1, 2 and 3 i.e. 2~[name] and 3~[name] or in the case of auto-heading numbering, without further intervention. + +3~ Link shortcut for multiple versions of a sisu document in the same directory tree + +!_ markup example: + +code{ + +!_ /{"Viral Spiral"}/, David Bollier + +{ "Viral Spiral", David Bollier [3sS]}viral_spiral.david_bollier.sst + +}code + + +!_ /{"Viral Spiral"}/, David Bollier + +{ "Viral Spiral", David Bollier [3sS]}viral_spiral.david_bollier.sst + +2~ Grouped Text / blocked text +={ SiSU markup:grouped text;grouped text;blocked text;text blocks } + +There are two markup syntaxes for blocked text, using curly braces or using tics + +3~ blocked text curly brace syntax +={ SiSU markup:grouped text;grouped text:curly brace syntax;blocked text:curly brace syntax;text blocks:curly brace syntax } + +at the start of a line on its own use name of block type with an opening curly brace, follow with the content of the block, and close with a closing curly brace and the name of the block type, e.g. + +``` code +code{ + +this is a code block + +}code +``` + +``` code +poem{ + +this here is a poem + +}poem +``` + +3~ blocked text tic syntax +={ SiSU markup:grouped text;grouped text:tic syntax;blocked text:tic syntax;text blocks:tic syntax } + +code{ + +``` code +this is a code block +``` + +``` poem +this here is a poem +``` + +}code + +start a line with three backtics, a space followed by the name of the name of block type, follow with the content of the block, and close with three back ticks on a line of their own, e.g. + +3~ Group +={ SiSU markup:group text;group text } + +The "group" is different from the "block" mark in that "group" does not preserve whitespace, the "block" mark does. The text falling within the block is a single object. + +!_ basic markup: + +code{ + +group{ + + Your grouped text here + +}group + +A group is treated as an object and given a single object number. + +}code + +!_ resulting group text output: + +group{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath."~{ endnote test }~ + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole~{ stress test }~ + cause, + and + condemn + you + to + death."' + +}group +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +!_ resulting group text output: + +group{ + +The Road Not Taken Related Poem Content Details + +BY ROBERT FROST + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}group +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Block +={ SiSU markup:block text;block text } + +The "block" is different from the "group" mark in that the "block" mark (like the "poem" mark) preserves whitespace, the "group" mark does not. The text falling within the "block" is a single object, which is different from the "poem" mark where each identified verse is an object. + +!_ basic markup: + +code{ + +block{ + + Your block text here + +}block + +A block is treated as an object and given a single object number. + +}code + +!_ resulting block text output: + +block{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath."~{ endnote test }~ + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole~{ stress test }~ + cause, + and + condemn + you + to + death."' + +}block +={ output:block block markup example;SiSU markup output:block block (curly brace syntax) } + +block{ + +The Road Not Taken Related Poem Content Details + +BY ROBERT FROST + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}block +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Poem +={ SiSU markup:poem;poems } + +The "poem" mark like the "block" preserves whitespace. Text followed by two newlines are identified as verse and each verse is an object i.e. a poem may consist of multiple verse each of which is identified as an object, unlike a text "block" which is identified as a single object. + +!_ basic markup: + +code{ + +poem{ + + Your poem here + +}poem + +Each verse in a poem is given an object number. + +}code + +!_ curly brace delimiter, resulting group text output: + +poem{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' +}poem + +% ={ output:poem markup example;SiSU markup output:poem (curly brace syntax) } + +!_ curly brace delimiter, resulting group text output: + +poem{ + +*{The Road Not Taken}*~{ published in 1916 as the first poem in the collection Mountain Interval. }~ + +by Robert Frost + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}poem +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +!_ tics delimiter, resulting group text output: + +``` poem +!{The Road Not Taken}!~{ published in 1916 as the first poem in the collection Mountain Interval. }~ + +by Robert Frost + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. +``` +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Code +={ SiSU markup:code block;code block } + +"Code" blocks are a single text object, in which the original text is preserved. + +Code tags #{ code{ ... }code }# (used as with other group tags described above) are used to escape regular sisu markup, and have been used extensively within this document to provide examples of SiSU markup. You cannot however use code tags to escape code tags. They are however used in the same way as group or poem tags. + +A code-block is treated as an object and given a single object number. [an option to number each line of code may be considered at some later time] + +!_ use of code tags instead of poem compared, resulting output: + +code{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' + +}code + +From SiSU 2.7.7 on you can number codeblocks by placing a hash after the opening code tag #{ code{# }# as demonstrated here: + +code{# + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' + +}code +={ output:code markup example;SiSU markup output:code block (curly brace syntax);code block:curly brace syntax } + +3~ Tables +={ SiSU markup:tables;tables } + +Tables may be prepared in two either of two forms + +!_ markup example: + +code{ + +table{ c3; 40; 30; 30; + +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on + +}table + +}code + +!_ resulting output: + +table{ c3; 40; 30; 30; + +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on + +}table + +% ={ output:table markup example;SiSU markup output:table (curly brace syntax) } + +Same as a tic table + +``` table c3; 40; 30; 30; +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on +``` + +Without instruction + +``` table +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on +``` + +a second form may be easier to work with in cases where there is not much information in each column + +*{markup example:}*~{ Table from the Wealth of Networks by Yochai Benkler \\ http://www.jus.uio.no/sisu/the_wealth_of_networks.yochai_benkler }~ + +code{ + +!_ Table 3.1: Contributors to Wikipedia, January 2001 - June 2005 + +{table~h 24; 12; 12; 12; 12; 12; 12;} + |Jan. 2001|Jan. 2002|Jan. 2003|Jan. 2004|July 2004|June 2006 +Contributors* | 10| 472| 2,188| 9,653| 25,011| 48,721 +Active contributors** | 9| 212| 846| 3,228| 8,442| 16,945 +Very active contributors*** | 0| 31| 190| 692| 1,639| 3,016 +No. of English language articles| 25| 16,000| 101,000| 190,000| 320,000| 630,000 +No. of articles, all languages | 25| 19,000| 138,000| 490,000| 862,000|1,600,000 + +* Contributed at least ten times; ** at least 5 times in last month; *** more than 100 times in last month. + +}code + +!_ resulting output: + + +!_ Table 3.1: Contributors to Wikipedia, January 2001 - June 2005 + +{table~h 24; 12; 12; 12; 12; 12; 12;} + |Jan. 2001|Jan. 2002|Jan. 2003|Jan. 2004|July 2004|June 2006 +Contributors* | 10| 472| 2,188| 9,653| 25,011| 48,721 +Active contributors** | 9| 212| 846| 3,228| 8,442| 16,945 +Very active contributors*** | 0| 31| 190| 692| 1,639| 3,016 +No. of English language articles| 25| 16,000| 101,000| 190,000| 320,000| 630,000 +No. of articles, all languages | 25| 19,000| 138,000| 490,000| 862,000|1,600,000 + +* Contributed at least ten times; ** at least 5 times in last month; *** more than 100 times in last month. + +% code{ + +% test + +% ~# + +% % unnumbered paragraph (place marker at end of paragraph) + +% -# + +% % unnumbered paragraph, delete when not required (place marker at end of paragraph) [used in dummy headings, eg. for segmented html] + +% % add a comment to text, that will be removed prior to processing (place marker at beginning of line) + +% }code + +2~ Additional breaks - linebreaks within objects, column and page-breaks +={ SiSU markup:breaks (page and line);breaks } + +3~ line-breaks +={ SiSU markup:line break;line break } + +To break a line within a "paragraph object", two backslashes \\\\ \\ with a space before and a space or newline after them \\ may be used. + +code{ + +To break a line within a "paragraph object", +two backslashes \\ with a space before +and a space or newline after them \\ +may be used. + +}code + +The html break br enclosed in angle brackets (though undocumented) is available in versions prior to 3.0.13 and 2.9.7 (it remains available for the time being, but is depreciated). + +To draw a dividing line dividing paragraphs, see the section on page breaks. + +3~ page breaks +={ SiSU markup:page break;page break } + +Page breaks are only relevant and honored in some output formats. A page break or a new page may be inserted manually using the following markup on a line on its own: + +page new =\\= breaks the page, starts a new page. + +page break -\\- breaks a column, starts a new column, if using columns, else breaks the page, starts a new page. + +page break line across page -..- draws a dividing line, dividing paragraphs + +page break: + +code{ + +-\\- + +}code + +page (break) new: + +code{ + +=\\= + +}code + +page (break) line across page (dividing paragraphs): + +code{ + +-..- + +}code + + +2~ Excluding Object Numbers + +Object numbers can be switched off by adding a ~# to the end of a text object. + +Sometimes it is wished to switch off object numbers for a larger group of text. In this case it is possible before the group, body of text to be without object numbers on a new line with nothing else on it to open the un-numbered object block with --~# and to close the un-numbered block, and restart object numbering with on a similarly otherwise empty new-line with --+# + +code{ + +--~# + +un-numbered object block of text contained here + +still un-numbered + +--+# + +object numbering returns here and for subsequent text objects + +to switch of object numbering for a single objct, to the end of the object add ~# like so:~# + +}code + +2~ Bibliography / References +={ SiSU markup:references|bibliography|citations;references } + +There are three ways to prepare a bibliography using sisu (which are mutually exclusive): (i) manually preparing and marking up as regular text in sisu a list of references, this is treated as a regular document segment (and placed before endnotes if any); (ii) preparing a bibliography, marking a heading level 1~!biblio (note the exclamation mark) and preparing a bibliography using various metadata tags including for author: title: year: a list of which is provided below, or; (iii) as an assistance in preparing a bibliography, marking a heading level 1~!biblio and tagging citations within footnotes for inclusion, identifying citations and having a parser attempt to extract them and build a bibliography of the citations provided. + +For the heading/section sequence: endnotes, bibliography then book index to occur, the name biblio or bibliography must be given to the bibliography section, like so: + +code{ + +1~!biblio + +}code + +3~ a markup tagged metadata bibliography section + +Here instead of writing your full citations directly in footnotes, each time you have new material to cite, you add it to your bibliography section (if it has not been added yet) providing the information you need against an available list of tags (provided below). + +The required tags are au: ti: and year: ~{for which you may alternatively use the full form author: title: and year: }~ an short quick example might be as follows: + +code{ + +1~!biblio + +au: von Hippel, E. +ti: Perspective: User Toolkits for Innovation +lng: (language) +jo: Journal of Product Innovation Management +vo: 18 +ed: (editor) +yr: 2001 +note: +sn: Hippel, /{User Toolkits}/ (2001) +id: vHippel_2001 +% form: + +au: Benkler, Yochai +ti: The Wealth of Networks +st: How Social Production Transforms Markets and Freedom +lng: (language) +pb: Harvard University Press +edn: (edition) +yr: 2006 +pl: U.S. +url: http://cyber.law.harvard.edu/wealth_of_networks/Main_Page +note: +sn: Benkler, /{Wealth of Networks}/ (2006) +id: Benkler2006 + +au: Quixote, Don; Panza, Sancho +ti: Taming Windmills, Keeping True +jo: Imaginary Journal +yr: 1605 +url: https://en.wikipedia.org/wiki/Don_Quixote +note: made up to provide an example of author markup for an article with two authors +sn: Quixote & Panza, /{Taming Windmills}/ (1605) +id: quixote1605 + +}code + +Note that the section name !biblio (or !bibliography) is required for the bibliography to be treated specially as such, and placed after the auto-generated endnote section. + +Using this method, work goes into preparing the bibliography, the tags author or editor, year and title are required and will be used to sort the bibliography that is placed under the Bibliography section + +The metadata tags may include shortname (sn:) and id, if provided, which are used for substitution within text. Every time the given id is found within the text it will be replaced by the given short title of the work (it is for this reason the short title has sisu markup to italicize the title), it should work with any page numbers to be added, the short title should be one that can easily be used to look up the full description in the bibliography. + +code{ + +The following footnote~{ quixote1605, pp 1000 - 1001, also Benkler2006 p 1. }~ + +}code + +would be presented as: + +Quixote and Panza, /{Taming Windmills}/ (1605), pp 1000 - 1001 also, Benkler, /{Wealth of Networks}/, (2006) p 1 or rather~{ Quixote and Panza, /{Taming Windmills}/ (1605), pp 1000 - 1001 also, Benkler, /{Wealth of Networks}/ (2006), p 1 }~ + +code{ + +au: author Surname, FirstNames (if multiple semi-colon separator) + (required unless editor to be used instead) +ti: title (required) +st: subtitle +jo: journal +vo: volume +ed: editor (required if author not provided) +tr: translator +src: source (generic field where others are not appropriate) +in: in (like src) +pl: place/location (state, country) +pb: publisher +edn: edition +yr: year (yyyy or yyyy-mm or yyyy-mm-dd) (required) +pg: pages +url: http://url +note: note +id: create_short_identifier e.g. authorSurnameYear + (used in substitutions: when found within text will be + replaced by the short name provided) +sn: short name e.g. Author, /{short title}/, Year + (used in substitutions: when an id is found within text + the short name will be used to replace it) + +}code + +3~ Tagging citations for inclusion in the Bibliography + +Here whenever you make a citation that you wish be included in the bibliography, you tag the citation as such using special delimiters (which are subsequently removed from the final text produced by sisu) + +Here you would write something like the following, either in regular text or a footnote + +code{ + +See .: Quixote, Don; Panza, Sancho /{Taming Windmills, Keeping True}/ (1605) :. + +}code + +SiSU will parse for a number of patterns within the delimiters to try make out the authors, title, date etc. and from that create a Bibliography. This is more limited than the previously described method of preparing a tagged bibliography, and using an id within text to identify the work, which also lends itself to greater consistency. + +2~ Glossary +={ SiSU markup:glossary|Glossary } + +Using the section name 1~!glossary results in the Glossary being treated specially as such, and placed after the auto-generated endnote section (before the bibliography/list of references if there is one). + +The Glossary is ordinary text marked up in a manner deemed suitable for that purpose. e.g. with the term in bold, possibly with a hanging indent. + +code{ + +1~!glossary + +_0_1 *{GPL}* An abbreviation that stands for "General Purpose License." ... + +_0_1 [provide your list of terms and definitions] + +}code + +In the given example the first line is not indented subsequent lines are by one level, and the term to be defined is in bold text. + +2~ Book index +={ SiSU markup:book index;book index } + +To make an index append to paragraph the book index term relates to it, using an equal sign and curly braces. + +Currently two levels are provided, a main term and if needed a sub-term. Sub-terms are separated from the main term by a colon. + +code{ + + Paragraph containing main term and sub-term. + ={Main term:sub-term} + +}code + +The index syntax starts on a new line, but there should not be an empty line between paragraph and index markup. + +The structure of the resulting index would be: + +code{ + + Main term, 1 + sub-term, 1 + +}code + +Several terms may relate to a paragraph, they are separated by a semicolon. If the term refers to more than one paragraph, indicate the number of paragraphs. + +code{ + + Paragraph containing main term, second term and sub-term. + ={first term; second term: sub-term} + +}code + +The structure of the resulting index would be: + +code{ + + First term, 1, + Second term, 1, + sub-term, 1 + +}code + +If multiple sub-terms appear under one paragraph, they are separated under the main term heading from each other by a pipe symbol. + +code{ + + Paragraph containing main term, second term and sub-term. + ={Main term: + sub-term+2|second sub-term; + Another term + } + + A paragraph that continues discussion of the first sub-term + +}code + +The plus one in the example provided indicates the first sub-term spans one additional paragraph. The logical structure of the resulting index would be: + +code{ + + Main term, 1, + sub-term, 1-3, + second sub-term, 1, + Another term, 1 + +}code + +1~ Composite documents markup +={ SiSU markup:composite documents;composite documents } + +It is possible to build a document by creating a master document that requires other documents. The documents required may be complete documents that could be generated independently, or they could be markup snippets, prepared so as to be easily available to be placed within another text. If the calling document is a master document (built from other documents), it should be named with the suffix *{.ssm}* Within this document you would provide information on the other documents that should be included within the text. These may be other documents that would be processed in a regular way, or markup bits prepared only for inclusion within a master document *{.sst}* regular markup file, or *{.ssi}* (insert/information) A secondary file of the composite document is built prior to processing with the same prefix and the suffix *{._sst}* + +basic markup for importing a document into a master document + +code{ + +<< filename1.sst + +<< filename2.ssi + +}code + +The form described above should be relied on. Within the Vim editor it results in the text thus linked becoming hyperlinked to the document it is calling in which is convenient for editing. + +1~ Substitutions +={ SiSU markup:substitutions;substitutions } + +!_ markup example: + +code{ + +The current Debian is ${debian_stable} the next debian will be ${debian_testing} + +Configure substitution in _sisu/sisu_document_make + +@make: + :substitute: /${debian_stable}/,'*{Wheezy}*' /${debian_testing}/,'*{Jessie}*' + +}code + +!_ resulting output: + +The current Debian is ${debian_stable} the next debian will be ${debian_testing} + +Another test ${sisudoc} ok? + +Configure substitution in _sisu/sisu_document_make + +1~ Footnote, endnote stress test + +Globalisation is to be observed as a trend intrinsic to the world economy.~{ As Maria Cattaui Livanos suggests in /{The global economy - an opportunity to be seized}/ in /{Business World}/ the Electronic magazine of the International Chamber of Commerce (Paris, July 1997) at http://www.iccwbo.org/html/globalec.htm \\ "Globalization is unstoppable. Even though it may be only in its early stages, it is already intrinsic to the world economy. We have to live with it, recognize its advantages and learn to manage it. \\ That imperative applies to governments, who would be unwise to attempt to stem the tide for reasons of political expediency. It also goes for companies of all sizes, who must now compete on global markets and learn to adjust their strategies accordingly, seizing the opportunities that globalization offers."}~ Rudimentary economics explains this runaway process, as being driven by competition within the business community to achieve efficient production, and to reach and extend available markets.~{To remain successful, being in competition, the business community is compelled to take advantage of the opportunities provided by globalisation.}~ Technological advancement particularly in transport and communications has historically played a fundamental role in the furtherance of international commerce, with the Net, technology's latest spatio-temporally transforming offering, linchpin of the "new-economy", extending exponentially the global reach of the business community. The Net covers much of the essence of international commerce providing an instantaneous, low cost, convergent, global and borderless: information centre, marketplace and channel for communications, payments and the delivery of services and intellectual property. The sale of goods, however, involves the separate element of their physical delivery. The Net has raised a plethora of questions and has frequently offered solutions. The increased transparency of borders arising from the Net's ubiquitous nature results in an increased demand for the transparency of operation. As economic activities become increasingly global, to reduce transaction costs, there is a strong incentive for the "law" that provides for them, to do so in a similar dimension. The appeal of transnational legal solutions lies in the potential reduction in complexity, more widely dispersed expertise, and resulting increased transaction efficiency. The Net reflexively offers possibilities for the development of transnational legal solutions, having in a similar vein transformed the possibilities for the promulgation of texts, the sharing of ideas and collaborative ventures. There are however, likely to be tensions within the legal community protecting entrenched practices against that which is new, (both in law and technology) and the business community's goal to reduce transaction costs. This here http://sisudoc.org/now is a test and repeat { does this work? }http://www.sisudoc.com/ok.html + +Within commercial law an analysis of law and economics may assist in +developing a better understanding of the relationship between commercial +law and the commercial sector it serves.~{ Realists would contend that law +is contextual and best understood by exploring the interrelationships +between law and the other social sciences, such as sociology, psychology, +political science, and economics.}~ "...[T]he importance of the +interrelations between law and economics can be seen in the twin facts +that legal change is often a function of economic ideas and conditions, +which necessitate and/or generate demands for legal change, and that +economic change is often governed by legal change."~{ Part of a section +cited in Mercuro and Steven G. Medema, /{Economics and the Law: from +Posner to Post-Modernism}/ (Princeton, 1997) p. 11, with reference to Karl +N. Llewellyn The Effect of Legal Institutions upon Economics, American +Economic Review 15 (December 1925) pp 655-683, Mark M. Litchman Economics, +the Basis of Law, American Law Review 61 (May-June 1927) pp 357-387, and +W. S. Holdsworth A Neglected Aspect of the Relations between Economic and +Legal History, Economic History Review 1 (January 1927-1928) pp 114-123.}~ +In doing so, however, it is important to be aware that there are several +competing schools of law and economics, with different perspectives, +levels of abstraction, and analytical consequences of and for the world +that they model.~{ For a good introduction see Nicholas Mercuro and Steven +G. Medema, /{Economics and the Law: from Posner to Post-Modernism}/ +(Princeton, 1997). These include: Chicago law and economics (New law and +economics); New Haven School of law and economics; Public Choice Theory; +Institutional law and economics; Neoinstitutional law and economics; +Critical Legal Studies.}~ This sentence trails test endnote. + +Difference?~{ puzzle away }~ + +* !glossary + +head + +header +document header, containing document specific (i) metadata information or (ii) make instructions + +(document) structure +relationship between headings and sub-headings, and the objects they contain. Document structure is extracted from heading levels, which are either: explicitly marked up, or; determined from a make regex provided in the document header. Use of document structure allow for the meaningful representation of documents in alternative ways and the use of ocn permits easy reference across different output formats. + +heading +document heading, each heading is marked indicating its level (in relation to other headings), and this is used as basis for determininge document structure. There are 8 levels, which are can be distinguesed as being one of three types: (i) 1 title level (marked up A or numeric 0); (ii) 3 optional document division levels, above text separating headings (marked up B - D, or numeric 1 to 3); (iii) 4 text headings (marked up 1 - 4, or numeric 4 to 7) + +levels == heading levels +document heading level, see heading and structure + +marked up headings / mark up level + +collapsed headings / collapsed levels + +numeric levels + +dummy heading +a markup level 1 / dummy level 4 that does not exist in the original text that is manually inserted to maintain the documents structure rule that text follows a heading of markup level 1 (rather than A to D) (numeric level 4 rather than 0 to 3) + +relatives? see ancestors and descendants + +document ... + +ancestors +heading levels above the current heading level which it logically falls under and to which it belongs (headings preceding current level under which it occurs) + +decendants +decendant headings are sub-headings beneath the current heading level, heading levels below the current heading level which are derived from it and belong to it (sub-headings contained beneath current level); decendant objects are the range of objects contained by a heading (ocn ranges for each heading in document body) + +(document) sections +a document can be divided into 3 parts: front; body and; back. Front matter includes the table of contents (which is generated from headings) and any parts of the document that are presented before the document body (this might include a copyright notice for example). The document body, the substantive part of the document, all its substantive objects, including: headings, paragraphs, tables, verse etc. This is followed by optional backmatter: endnotes, generated from inline markup; glossary, from section using a subset of regular markup, with an indication that section is to be treated as glossary. Note two things glossary might do that it does not, there is: no automatic (sorting) alphabetisation of listing; no creation of term anchor tags (perhaps it should); bibliography, created from a specially marked up section, with indication that section is to be treated as bibliography; bookindex generated from dedicated markup appended to objects providing index terms and the relevant range; blurb made up of ordinary markup, with indication that section is to be treated as blurb + +segment, segmented text +certain forms of output are conveniently segmented, e.g. epub and segmented html. The document is broken into chunks indicated by markup level 1 heading (numeric level 4 headings) as the significant level at which the document should be segmented, and including all decendant objects of that level. For a longer text/book this will usually the chapter level. (this is significant in e.g. for epub and segmented html, which are broken by segment, usually chosen to be chapter) + +scroll +the document as a "scroll", e.g. as a single text file, or continuous html document + +object +a unit of text. Objects include: headings; paragraphs; code blocks; grouped text; verse of poems; tables. Each substantive object is given an object number, that should make it citable. + +ocn (object citation number / citation number) +numbers assigned sequentially to each substantive object of a document. An ocn has the characteristic of remaining identical across output formats. Translations should be prepared so number remains identical across objects in different languages + +citation number (see ocn / object citation number) + +document abstraction (== internal representation) intermediate step, +preprocessing of document, into abstraction / representation that is used by all downstream processing, i.e. for all output formats. This allows normalisation, reducing alternative markup options to common representations, e.g. code blocks (open and close), tables, ways of instructing that text be bold, shortuct way of providing and endnote reference to a link + +(document) internal representation (== document abstraction) +see document abstraction + +node representation + +attribute (object attributes) +when the document is abstracted attributes associated with an object, for example for a: paragraph, indent (hang ... check & add), bulleted, for a: code block, the language syntax, whether the block is numbered + +inline markup +when the document is abstracted, markup that remains embedded in the text, such as its font face (bold, italic, emphasis, underscore, strike, superscript, subscript), links, endnotes + +sequential all objects backkeeping number? + +1~commands Sample Commands + +2~ general + +~sdp/bin/sdp-ldc -v --epub --html --sqlite-update --output-dir=tmp/program-output data/sisupod/sisu-manual + +time ( ~sdp/bin/sdp-ldc -v --epub --html --sqlite-update --output-dir=tmp/program-output data/sisupod/* ) + +2~ source & sisupod + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisudir/media/text/sisu-manual.sst + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisupod/* + +2~ sqlite + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-create --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --sqlite-insert --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --sqlite-update --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-dmd -v --epub --html --output-dir=tmp/program-output data/sisudir/media/text/sisu_markup.sst + +1~!blurb On SiSU + +SiSU was started in 1997, open-sourced in 2005. diff --git a/data/pod/sisu-manual-0.4.0_/conf/sisu_document_make b/data/pod/sisu-manual-0.4.0_/conf/sisu_document_make new file mode 100644 index 0000000..1e75412 --- /dev/null +++ b/data/pod/sisu-manual-0.4.0_/conf/sisu_document_make @@ -0,0 +1,12 @@ +[make] +breaks = "break=1" +home_button_text = "{doc-reform}http://doc-reform.org; {sources / git}http://git.sisudoc.org/; {SiSU}http://sisudoc.org" + +# footer = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +# home_button_text = "{sisu.png }http://sisudoc.org" + +links = [ + "{doc-reform}http://doc-reform.org", + "{sources / git}http://git.sisudoc.org/", + "{SiSU}http://sisudoc.org" +] diff --git a/data/pod/sisu-manual-0.4.0_/media/text/en/sisu_markup.sst b/data/pod/sisu-manual-0.4.0_/media/text/en/sisu_markup.sst new file mode 100644 index 0000000..5b7dac3 --- /dev/null +++ b/data/pod/sisu-manual-0.4.0_/media/text/en/sisu_markup.sst @@ -0,0 +1,1961 @@ +# SiSU 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +#substitute = [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +bold = "Debian|SiSU" +italics = "Linux|GPL|LaTeX|SQL" +breaks = "new=:B; break=1" +home_button_text = [ + "{doc-reform}http://doc-reform.org", + "{sources / git}http://git.sisudoc.org/", + "{SiSU}http://sisudoc.org" +] +footer = [ + "{SiSU}http://sisudoc.org", + "{git}http://git.sisudoc.org" +] + +# home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +# footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" +# substitute = "/[$]{2}[{]sisudoc[}]/,'www.sisudoc.org'" +# substitute = "/${sisudoc}/,'www.sisudoc.org'" +# home_button_image = "{won_benkler.png }http://cyber.law.harvard.edu/wealth_of_networks/Main_Page" + +:A~ @title @creator + +:B~ SiSU Markup +={ SiSU markup:test } + +1~markup Introduction to SiSU Markup~{ From sometime after SiSU 0.58 it should be possible to describe SiSU markup using SiSU, which though not an original design goal is useful. }~ + +2~ Summary + +SiSU source documents are plaintext (UTF-8)~{ files should be prepared using UTF-8 character encoding }~ files +={ SiSU markup:description } + +All paragraphs are separated by an empty line. + +Markup is comprised of: *~markup-summary { * }#internal-links + +_* at the top of a document, the document header made up of semantic meta-data about the document and if desired additional processing instructions (such an instruction to automatically number headings from a particular level down) + +_* followed by the prepared substantive text of which the most important single characteristic is the markup of different heading levels, which define the primary outline of the document structure. Markup of substantive text includes: + +_1* heading levels defines document structure + +_1* text basic attributes, italics, bold etc. + +_1* grouped text (objects), which are to be treated differently, such as code blocks or poems. + +_1* footnotes/endnotes + +_1* linked text and images + +_1* paragraph actions, such as indent, bulleted, numbered-lists, etc. + +2~ Markup Rules, document structure and metadata requirements +={ SiSU markup:rules and requirements } + +minimal content/structure requirement, minimum being: + +[metadata] + +``` code +title "SiSU" \ + subtitle="Markup" + +creator \ + author="Amissah, Ralph" +``` + +[levels] + +``` code +A~ (level A [title]) + +1~ (at least one level 1 [segment/(chapter)]) +``` +={ output:code markup example;SiSU markup output:code block (tic syntax);code block:tic syntax } + +structure rules (document heirarchy, heading levels): + +there are two sets of heading levels ABCD (title & parts if any) and 123 (segment & subsegments if any) +={ SiSU markup:heading levels } + +sisu has the fllowing levels: + +``` code +A~ [title] + required (== 1) followed by B~ or 1~ +B~ [part] + followed by C~ or 1~ +C~ [subpart] + followed by D~ or 1~ +D~ [subsubpart] + followed by 1~ +1~ [segment (chapter)] + required (>= 1) followed by text or 2~ +text + followed by more text or 1~, 2~ + or relevant part +2~ [subsegment] + followed by text or 3~ +text + followed by more text or 1~, 2~ or 3~ + or relevant part +3~ [subsubsegment] + followed by text +text + followed by more text or 1~, 2~ or 3~ or relevant part, see *() +``` + +Rules: + +``` code +- level A~ is the title and is mandatory +- there can only be one level A~ +- heading levels BCD, are optional and there may be several of each + (where all three are used corresponding to e.g. Book Part Section) + - sublevels that are used must follow each other sequentially + (alphabetically), +- heading levels A~ B~ C~ D~ are followed by other heading levels rather + than substantive text + which may be the subsequent sequential (alphabetic) heading part level + or a heading (segment) level 1~ +- there must be at least one heading (segment) level 1~ + (the level on which the text is segmented, in a book would correspond + to the Chapter level) +- additional heading levels 1~ 2~ 3~ are optional and there may be several + of each +- heading levels 1~ 2~ 3~ are followed by text (which may be followed by + the same heading level) + and/or the next lower numeric heading level (followed by text) + or indeed return to the relevant part level + (as a corollary to the rules above substantive text/ content + must be preceded by a level 1~ (2~ or 3~) heading) +``` + +2~ Markup Examples +={ SiSU markup:locating examples } + +3~ Online +={ SiSU markup:examples online } + +Online markup examples are available together with the respective outputs produced from http://www.jus.uio.no/sisu/SiSU/examples.html or from http://www.jus.uio.no/sisu/sisu_examples/ + +There is of course this document, which provides a cursory overview of sisu markup and the respective output produced: http://www.jus.uio.no/sisu/sisu_markup/ + +an alternative presentation of markup syntax: /usr/share/doc/sisu/on_markup.txt.gz + +3~ Installed +={ SiSU markup:examples installed } + +With SiSU installed sample skins may be found in: /usr/share/doc/sisu/markup-samples (or equivalent directory) and if sisu-markup-samples is installed also under: /usr/share/doc/sisu/markup-samples-non-free + +1~headers Markup of Headers +={ SiSU markup:headers} + +Headers contain either: semantic meta-data about a document, which can be used by any output module of the program, or; processing instructions. + +Note: the first line of a document may include information on the markup version used in the form of a comment. Comments are a percentage mark at the start of a paragraph (and as the first character in a line of text) followed by a space and the comment: + +code{ + +% this would be a comment + +}code +={ output:code markup example;SiSU markup output:code block (curly brace syntax);code block:curly brace syntax } + +2~ Sample Header +={ SiSU markup:sample header} + +This current document is loaded by a master document that has a header similar to this one: + +``` code +# SiSU 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" +``` + +Looking back a bit: + +code{ + +# SiSU master 8.0 + +[title] +main = "SiSU" +subtitle = "Markup" + +[creator] +author = "Amissah, Ralph" + +[date] +created = "2002-08-28" +issued = "2002-08-28" +available = "2002-08-28" +published = "2008-05-22" +modified = "2012-10-03" + +[rights] +copyright = "Copyright (C) Ralph Amissah 2007" +license = "GPL 3 (part of SiSU documentation)" + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +bold = "Debian|SiSU" +italics = "Linux|GPL|LaTeX|SQL" +breaks = "new=:B; break=1" +home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" + +}code + +2~ Available Headers +={ SiSU markup:headers available } + +Header tags appear at the beginning of a document and provide meta information on the document (such as the Dublin Core), or information as to how the document as a whole is to be processed. All header instructions take the form @headername: or on the next line and indented by once space :subheadername: All Dublin Core meta tags are available + +!_ @identifier: +information or instructions + +where the "identifier" is a tag recognised by the program, and the "information" or "instructions" belong to the tag/identifier specified + +Note: a header where used should only be used once; all headers apart from @title: are optional; the @structure: header is used to describe document structure, and can be useful to know. + +This is a sample header + +% (Dublin Core in fuschia, other information headers in cyan, markup instructions in red): + +code{ + +# SiSU 8.0 + +}code + +code{ + +[title] +main = "SiSU" +subtitle = "Markup" +language = "English" + +}code + +code{ + +[creator] +author = [Lastname, First names] +illustrator = [Lastname, First names] +translator = [Lastname, First names] +prepared_by = [Lastname, First names] + +}code + +code{ + +[date] +created = [year or yyyy-mm-dd] +issued = [year or yyyy-mm-dd] +available = [year or yyyy-mm-dd] +published = [year or yyyy-mm-dd] +modified = [year or yyyy-mm-dd] +valid = [year or yyyy-mm-dd] +added_to_site = [year or yyyy-mm-dd] +translated = [year or yyyy-mm-dd] + +}code + +code{ + +[rights] +copyright = "Copyright (C) [Year and Holder]" +license = "[Use License granted]" +text = "[Name, Year]" +translation = "[Name, Year]" +illustrations = "[Name, Year]" + +# check rest + +}code + +code{ + +[classify] +topic_register = "electronic documents:SiSU:document:markup;SiSU:document:markup;SiSU:document:markup;SiSU:manual:markup;electronic documents:SiSU:manual:markup" +subject = "ebook, epublishing, electronic book, electronic publishing, electronic document, electronic citation, data structure, citation systems, search" +keywords = "list" +loc = "[Library of Congress classification]" +dewey = "[Dewey classification]" + +}code + +code{ + +[identify] +isbn = "[ISBN]" +oclc = "" + +}code + + +code{ + +links = [ + "{SiSU }http://www.sisudoc.org", + "{ FSF }http://www.fsf.org", +] + +}code + +code{ + +[make] +auto_num_top_at_level = "1" +substitute = [ + [ "[$]{2}\\{sisudoc\\}", "www.sisudoc.org" ] +] +bold = "Debian|SiSU" # [regular expression of words/phrases to be made bold] +italics = "Linux|GPL|LaTeX|SQL" # [regular expression of words/phrases to italicise] +breaks = "new=:B; break=1" +home_button_text = "{SiSU}http://sisudoc.org; {sources / git}http://git.sisudoc.org/gitweb/" +footer = "{SiSU}http://sisudoc.org; {git}http://git.sisudoc.org" +headings = text to match for each level + (e.g. PART; Chapter; Section; Article; or another: none; BOOK|FIRST|SECOND; none; CHAPTER;) + +}code + +% [original] +% language = [language] + +% [notes] +% comment: +% prefix: [prefix is placed just after table of contents] + +% header ends here, NB only @title: is mandatory [this would be a comment] +% NOTE: headings/levels below refer to 0.38 expermental markup (a conversion script provided in sisu-examples, modify.rb makes conversion between 0.37 and 0.38 markup simple) + +1~ Markup of Substantive Text +={ SiSU markup:substantive text } + +2~heading_levels Heading Levels +={ SiSU markup:heading levels } + +Heading levels are :A~ ,:B~ ,:C~ ,1~ ,2~ ,3~ ... :A - :C being part / section headings, followed by other heading levels, and 1 -6 being headings followed by substantive text or sub-headings. :A~ usually the title :A~? conditional level 1 heading (used where a stand-alone document may be imported into another) + +!_ :A~ [heading text] +Top level heading [this usually has similar content to the title @title: ] +NOTE: the heading levels described here are in 0.38 notation, see heading + +!_ :B~ [heading text] +Second level heading [this is a heading level divider] + +!_ :C~ [heading text] +Third level heading [this is a heading level divider] + +!_ 1~ [heading text] +Top level heading preceding substantive text of document or sub-heading 2, the heading level that would normally be marked 1. or 2. or 3. etc. in a document, and the level on which sisu by default would break html output into named segments, names are provided automatically if none are given (a number), otherwise takes the form 1~my_filename_for_this_segment + +!_ 2~ [heading text] +Second level heading preceding substantive text of document or sub-heading 3 , the heading level that would normally be marked 1.1 or 1.2 or 1.3 or 2.1 etc. in a document. + +!_ 3~ [heading text] +Third level heading preceding substantive text of document, that would normally be marked 1.1.1 or 1.1.2 or 1.2.1 or 2.1.1 etc. in a document + +code{ + +1~filename level 1 heading, + +% the primary division such as Chapter that is followed by substantive text, and may be further subdivided (this is the level on which by default html segments are made) + +}code + +2~ Font Attributes +={ SiSU markup:font attributes } + +!_ markup example: + +code{ + +normal text, *{emphasis}*, !{bold text}!, /{italics}/, _{underscore}_, "{citation}", +^{superscript}^, ,{subscript},, +{inserted text}+, -{strikethrough}-, #{monospace}# + +normal text + +*{emphasis}* [note: can be configured to be represented by bold, italics or underscore] + +!{bold text}! + +/{italics}/ + +_{underscore}_ + +"{citation}" + +^{superscript}^ + +,{subscript}, + ++{inserted text}+ + +-{strikethrough}- + +#{monospace}# + +}code + +!_ resulting output: + +normal text, *{emphasis}*, !{bold text}!, /{italics}/, _{underscore}_, "{citation}", +^{superscript}^, ,{subscript},, +{inserted text}+, -{strikethrough}-, #{monospace}# + +normal text + +*{emphasis}* [note: can be configured to be represented by bold, italics or underscore] + +!{bold text}! + +/{italics}/ + +_{underscore}_ + +"{citation}" + +^{superscript}^ + +,{subscript}, + ++{inserted text}+ + +-{strikethrough}- + +#{monospace}# + +2~ Indentation and bullets +={ SiSU markup:indentation and bullets } + +!_ markup example: +={ SiSU markup:indentation } + +code{ + +ordinary paragraph + +_1 indent paragraph one step + +_2 indent paragraph two steps + +_9 indent paragraph nine steps + +}code + +!_ resulting output: + +ordinary paragraph + +_1 indent paragraph one step + +_2 indent paragraph two steps + +_9 indent paragraph nine steps + +!_ markup example: +={ SiSU markup:bullets } + +code{ + +_* bullet text + +_1* bullet text, first indent + +_2* bullet text, two step indent + +}code + +!_ resulting output: + +_* bullet text + +_1* bullet text, first indent + +_2* bullet text, two step indent + +Numbered List (not to be confused with headings/titles, (document structure)) + +!_ markup example: + +code{ + +# numbered list numbered list 1., 2., 3, etc. + +_# numbered list numbered list indented a., b., c., d., etc. + +}code + +2~ Hanging Indents +={ SiSU markup:hanging indents;indented text:hanging } + +!_ markup example: + +code{ + +_0_1 first line no indent, +rest of paragraph indented one step + +_1_0 first line indented, +rest of paragraph no indent + +in each case level may be 0-9 + +}code + +!_ resulting output: + +_0_1 first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; first line no indent, rest of paragraph indented one step; + +A regular paragraph. + +_1_0 first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent first line indented, rest of paragraph no indent + +in each case level may be 0-9 + +_0_1 *{live-build}* A collection of scripts used to build customized Debian Livesystems. /{live-build}/ was formerly known as live-helper, and even earlier known as live-package. + +_0_1 *{live-build}* \\ +A collection of scripts used to build customized Debian Livesystems. /{live-build}/ was formerly known as live-helper, and even earlier known as live-package. + +2~ Footnotes / Endnotes +={ SiSU markup:hanging indents;footnotes;endnotes } + +Footnotes and endnotes are marked up at the location where they would be indicated within a text. They are automatically numbered. The output type determines whether footnotes or endnotes will be produced + +!_ markup example: + +code{ + +~{ a footnote or endnote }~ + +}code + +!_ resulting output: + +~{ a footnote or endnote }~ + +!_ markup example: + +code{ + +normal text~{ self contained endnote marker & endnote in one }~ continues + +}code + +!_ resulting output: + +normal text~{ self contained endnote marker & endnote in one }~ continues + +!_ markup example: + +code{ + +normal text ~{* unnumbered asterisk footnote/endnote, insert multiple asterisks if required }~ continues + +normal text ~{** another unnumbered asterisk footnote/endnote }~ continues + +}code + +!_ resulting output: + +normal text ~{* unnumbered asterisk footnote/endnote, insert multiple asterisks if required }~ continues + +normal text ~{** another unnumbered asterisk footnote/endnote }~ continues + +!_ markup example: + +code{ + +normal text ~[* editors notes, numbered asterisk footnote/endnote series ]~ continues + +normal text ~[+ editors notes, numbered plus symbol footnote/endnote series ]~ continues + +}code + +!_ resulting output: + +normal text ~[* editors notes, numbered asterisk footnote/endnote series ]~ continues + +normal text ~[+ editors notes, numbered plus symbol footnote/endnote series ]~ continues + +!_ [discontinued] Alternative binary endnote notation (endnote pair) for footnotes/endnotes: + +code{ + +% note the endnote marker "~^" + +normal text~^ continues + +^~ endnote text following the paragraph in which the marker occurs + +}code + +standard (inline) and pair (binary) notation could not be mixed in the same document. + +The reason binary notation was provided as an option was for the conversion of documents to sisu markup. Many documents were prepared in such a way that endnotes had been previously marked up in a binary fashion, and this provided a convenient and faster way to make the document conversion, just reflect those markup practices. The reason it has been dropped is it adds a slowing step to something that needs to be done at most once and it prove to be flakey, unnecessarily so even when kept under version control. It is preferable to do a two step conversion of the previously marked up document to sisu: first to the binary/paired footnote markup, then; convert it to the proper form of inline endnote markup with a dedicated helper conversion program, keeping the resulting properly marked up text. + +2~ Links +={ SiSU markup:links (text, images);links:images|text } + +3~ Naked URLs within text, dealing with urls + +urls found within text are marked up automatically. A url within text is automatically hyperlinked to itself and by default decorated with angled braces, unless they are contained within a code block (in which case they are passed as normal text), or escaped by a preceding underscore (in which case the decoration is omitted). + +!_ markup example: + +code{ + +normal text http://www.sisudoc.org/ continues + +}code + +!_ resulting output: + +normal text http://www.sisudoc.org/ continues + +An escaped url without decoration + +!_ markup example: + +code{ + +normal text _http://www.sisudoc.org/ continues + +deb _http://www.jus.uio.no/sisu/archive unstable main non-free + +}code + +!_ resulting output: + +normal text _http://www.sisudoc.org/ continues + +deb _http://www.jus.uio.no/sisu/archive unstable main non-free + +where a code block is used there is neither decoration nor hyperlinking, code blocks are discussed later in this document + +!_ resulting output: + +code{ + +deb http://www.jus.uio.no/sisu/archive unstable main non-free +deb-src http://www.jus.uio.no/sisu/archive unstable main non-free + +}code + +3~link_text Linking Text +={ SiSU markup:links (text);links:text } + +To link text or an image to a url the markup is as follows + +!_ markup example: + +code{ + +about { SiSU }http://url.org markup + +}code + +!_ resulting output: + +about { SiSU }http://www.sisudoc.org/ markup + +A shortcut notation is available so the url link may also be provided automatically as a footnote + +!_ markup example: + +code{ + +about {~^ SiSU }http://url.org markup + +}code + +!_ resulting output: + +about {~^ SiSU }http://www.sisudoc.org/ markup + +Internal document links to a named (anchor) tagged location, including named headings named inline anchor tags *~an-inline-anchor-tag or an ocn +the heading: + +code{ + +1~markup Markup + +}code + +can be linked to as follows: + +code{ + +to find out more see { Markup }#markup + +}code + +to find out more see { Markup }#markup + +an inline anchor tag is made with the following markup *~internal-links + +code{ + +named inline anchor tags *~an-inline-anchor-tag + +}code + +and linked to the same way + +code{ + +the link { an inline anchor tag }#an-inline-anchor-tag + +}code + +the link { an inline anchor tag }#an-inline-anchor-tag or to another part of the document: { markup summary }#markup-summary + +!_ markup example: + +code{ + +about { text links }#link_text + +}code + +!_ resulting output: + +about { text links }#link_text + +Shared document collection link + +!_ markup example: + +code{ + +about { SiSU book markup examples }:SiSU/examples.html + +}code + +!_ resulting output: + +about { SiSU book markup examples }:SiSU/examples.html + +3~ Linking Images +={ SiSU markup:links (images);links:images } + +!_ markup example: + +code{ + +{ sm_tux.png 64x80 }image + +% various url linked images + +{sm_tux.png 64x80 "a better way" }http://www.sisudoc.org/ + +{sm_GnuDebianLinuxRubyBetterWay.png 100x101 "Way Better - with Gnu/Linux, Debian and Ruby" }http://www.sisudoc.org/ + +{~^ sm_ruby_logo.png "Ruby" }http://www.ruby-lang.org/en/ + +}code + +!_ resulting output: + +{ sm_tux.png }image + +{ sm_tux.png 64x80 }image + +{ sm_tux.png 64x80 "test" }image + +{ sm_tux.png }http://www.sisudoc.org/ + +{ sm_tux.png 64x80 }http://www.sisudoc.org/ + +{ sm_tux.png 64x80 "Gnu/Linux - a better way" }http://www.sisudoc.org/ + +{ sm_GnuDebianLinuxRubyBetterWay.png 100x101 "Way Better - with Gnu/Linux, Debian and Ruby" }http://www.sisudoc.org/ + +{~^ sm_ruby_logo.png "Ruby" }http://www.ruby-lang.org/en/ + +{ sm_d_image.jpg 82x128 "D for me" }https://github.com/dlang-community/d-mans + +{~^ sm_d_strip.png "D, hey no fair" }https://github.com/dlang-community/d-mans + +!_ linked url footnote shortcut + +code{ + +{~^ [text to link] }http://url.org + +% maps to: { [text to link] }http://url.org ~{ http://url.org }~ + +% which produces hyper-linked text within a document/paragraph, with an endnote providing the url for the text location used in the hyperlink + +}code + +code{ + +text marker *~name + +}code + +note at a heading level the same is automatically achieved by providing names to headings 1, 2 and 3 i.e. 2~[name] and 3~[name] or in the case of auto-heading numbering, without further intervention. + +3~ Link shortcut for multiple versions of a sisu document in the same directory tree + +!_ markup example: + +code{ + +!_ /{"Viral Spiral"}/, David Bollier + +{ "Viral Spiral", David Bollier [3sS]}viral_spiral.david_bollier.sst + +}code + + +!_ /{"Viral Spiral"}/, David Bollier + +{ "Viral Spiral", David Bollier [3sS]}viral_spiral.david_bollier.sst + +2~ Grouped Text / blocked text +={ SiSU markup:grouped text;grouped text;blocked text;text blocks } + +There are two markup syntaxes for blocked text, using curly braces or using tics + +3~ blocked text curly brace syntax +={ SiSU markup:grouped text;grouped text:curly brace syntax;blocked text:curly brace syntax;text blocks:curly brace syntax } + +at the start of a line on its own use name of block type with an opening curly brace, follow with the content of the block, and close with a closing curly brace and the name of the block type, e.g. + +``` code +code{ + +this is a code block + +}code +``` + +``` code +poem{ + +this here is a poem + +}poem +``` + +3~ blocked text tic syntax +={ SiSU markup:grouped text;grouped text:tic syntax;blocked text:tic syntax;text blocks:tic syntax } + +code{ + +``` code +this is a code block +``` + +``` poem +this here is a poem +``` + +}code + +start a line with three backtics, a space followed by the name of the name of block type, follow with the content of the block, and close with three back ticks on a line of their own, e.g. + +3~ Group +={ SiSU markup:group text;group text } + +The "group" is different from the "block" mark in that "group" does not preserve whitespace, the "block" mark does. The text falling within the block is a single object. + +!_ basic markup: + +code{ + +group{ + + Your grouped text here + +}group + +A group is treated as an object and given a single object number. + +}code + +!_ resulting group text output: + +group{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath."~{ endnote test }~ + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole~{ stress test }~ + cause, + and + condemn + you + to + death."' + +}group +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +!_ resulting group text output: + +group{ + +The Road Not Taken Related Poem Content Details + +BY ROBERT FROST + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}group +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Block +={ SiSU markup:block text;block text } + +The "block" is different from the "group" mark in that the "block" mark (like the "poem" mark) preserves whitespace, the "group" mark does not. The text falling within the "block" is a single object, which is different from the "poem" mark where each identified verse is an object. + +!_ basic markup: + +code{ + +block{ + + Your block text here + +}block + +A block is treated as an object and given a single object number. + +}code + +!_ resulting block text output: + +block{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath."~{ endnote test }~ + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole~{ stress test }~ + cause, + and + condemn + you + to + death."' + +}block +={ output:block block markup example;SiSU markup output:block block (curly brace syntax) } + +block{ + +The Road Not Taken Related Poem Content Details + +BY ROBERT FROST + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}block +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Poem +={ SiSU markup:poem;poems } + +The "poem" mark like the "block" preserves whitespace. Text followed by two newlines are identified as verse and each verse is an object i.e. a poem may consist of multiple verse each of which is identified as an object, unlike a text "block" which is identified as a single object. + +!_ basic markup: + +code{ + +poem{ + + Your poem here + +}poem + +Each verse in a poem is given an object number. + +}code + +!_ curly brace delimiter, resulting group text output: + +poem{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' +}poem + +% ={ output:poem markup example;SiSU markup output:poem (curly brace syntax) } + +!_ curly brace delimiter, resulting group text output: + +poem{ + +*{The Road Not Taken}*~{ published in 1916 as the first poem in the collection Mountain Interval. }~ + +by Robert Frost + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +}poem +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +!_ tics delimiter, resulting group text output: + +``` poem +!{The Road Not Taken}!~{ published in 1916 as the first poem in the collection Mountain Interval. }~ + +by Robert Frost + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. +``` +={ output:group block markup example;SiSU markup output:group block (curly brace syntax) } + +3~ Code +={ SiSU markup:code block;code block } + +"Code" blocks are a single text object, in which the original text is preserved. + +Code tags #{ code{ ... }code }# (used as with other group tags described above) are used to escape regular sisu markup, and have been used extensively within this document to provide examples of SiSU markup. You cannot however use code tags to escape code tags. They are however used in the same way as group or poem tags. + +A code-block is treated as an object and given a single object number. [an option to number each line of code may be considered at some later time] + +!_ use of code tags instead of poem compared, resulting output: + +code{ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' + +}code + +From SiSU 2.7.7 on you can number codeblocks by placing a hash after the opening code tag #{ code{# }# as demonstrated here: + +code(#){ + + `Fury said to a + mouse, That he + met in the + house, + "Let us + both go to + law: I will + prosecute + YOU. --Come, + I'll take no + denial; We + must have a + trial: For + really this + morning I've + nothing + to do." + Said the + mouse to the + cur, "Such + a trial, + dear Sir, + With + no jury + or judge, + would be + wasting + our + breath." + "I'll be + judge, I'll + be jury," + Said + cunning + old Fury: + "I'll + try the + whole + cause, + and + condemn + you + to + death."' + +}code +={ output:code markup example;SiSU markup output:code block (curly brace syntax);code block:curly brace syntax } + +3~ Tables +={ SiSU markup:tables;tables } + +Tables may be prepared in two either of two forms + +!_ markup example: + +code{ + +table(c3: 40, 30, 30){ + +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on + +}table + +}code + +!_ resulting output: + +table(c3: 40, 30, 30){ + +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on + +}table + +% ={ output:table markup example;SiSU markup output:table (curly brace syntax) } + +Same as a tic table + +``` table(c3: 40, 30, 30) +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on +``` + +Without instruction + +``` table +This is a table +this would become column two of row one +column three of row one is here + +And here begins another row +column two of row two +column three of row two, and so on +``` + +a second form may be easier to work with in cases where there is not much information in each column + +*{markup example:}*~{ Table from the Wealth of Networks by Yochai Benkler \\ http://www.jus.uio.no/sisu/the_wealth_of_networks.yochai_benkler }~ + +code{ + +!_ Table 3.1: Contributors to Wikipedia, January 2001 - June 2005 + +{table(h; 24, 12, 12, 12, 12, 12, 12)} + |Jan. 2001|Jan. 2002|Jan. 2003|Jan. 2004|July 2004|June 2006 +Contributors* | 10| 472| 2,188| 9,653| 25,011| 48,721 +Active contributors** | 9| 212| 846| 3,228| 8,442| 16,945 +Very active contributors*** | 0| 31| 190| 692| 1,639| 3,016 +No. of English language articles| 25| 16,000| 101,000| 190,000| 320,000| 630,000 +No. of articles, all languages | 25| 19,000| 138,000| 490,000| 862,000|1,600,000 + +* Contributed at least ten times; ** at least 5 times in last month; *** more than 100 times in last month. + +}code + +!_ resulting output: + + +!_ Table 3.1: Contributors to Wikipedia, January 2001 - June 2005 + +{table(h; 24, 12, 12, 12, 12, 12, 12)} + |Jan. 2001|Jan. 2002|Jan. 2003|Jan. 2004|July 2004|June 2006 +Contributors* | 10| 472| 2,188| 9,653| 25,011| 48,721 +Active contributors** | 9| 212| 846| 3,228| 8,442| 16,945 +Very active contributors*** | 0| 31| 190| 692| 1,639| 3,016 +No. of English language articles| 25| 16,000| 101,000| 190,000| 320,000| 630,000 +No. of articles, all languages | 25| 19,000| 138,000| 490,000| 862,000|1,600,000 + +* Contributed at least ten times; ** at least 5 times in last month; *** more than 100 times in last month. + +% code{ + +% test + +% ~# + +% % unnumbered paragraph (place marker at end of paragraph) + +% -# + +% % unnumbered paragraph, delete when not required (place marker at end of paragraph) [used in dummy headings, eg. for segmented html] + +% % add a comment to text, that will be removed prior to processing (place marker at beginning of line) + +% }code + +2~ Additional breaks - linebreaks within objects, column and page-breaks +={ SiSU markup:breaks (page and line);breaks } + +3~ line-breaks +={ SiSU markup:line break;line break } + +To break a line within a "paragraph object", two backslashes \\\\ \\ with a space before and a space or newline after them \\ may be used. + +code{ + +To break a line within a "paragraph object", +two backslashes \\ with a space before +and a space or newline after them \\ +may be used. + +}code + +The html break br enclosed in angle brackets (though undocumented) is available in versions prior to 3.0.13 and 2.9.7 (it remains available for the time being, but is depreciated). + +To draw a dividing line dividing paragraphs, see the section on page breaks. + +3~ page breaks +={ SiSU markup:page break;page break } + +Page breaks are only relevant and honored in some output formats. A page break or a new page may be inserted manually using the following markup on a line on its own: + +page new =\\= breaks the page, starts a new page. + +page break -\\- breaks a column, starts a new column, if using columns, else breaks the page, starts a new page. + +page break line across page -..- draws a dividing line, dividing paragraphs + +page break: + +code{ + +-\\- + +}code + +page (break) new: + +code{ + +=\\= + +}code + +page (break) line across page (dividing paragraphs): + +code{ + +-..- + +}code + + +2~ Excluding Object Numbers + +Object numbers can be switched off by adding a ~# to the end of a text object. + +Sometimes it is wished to switch off object numbers for a larger group of text. In this case it is possible before the group, body of text to be without object numbers on a new line with nothing else on it to open the un-numbered object block with --~# and to close the un-numbered block, and restart object numbering with on a similarly otherwise empty new-line with --+# + +code{ + +--~# + +un-numbered object block of text contained here + +still un-numbered + +--+# + +object numbering returns here and for subsequent text objects + +to switch of object numbering for a single objct, to the end of the object add ~# like so:~# + +}code + +2~ Bibliography / References +={ SiSU markup:references|bibliography|citations;references } + +There are three ways to prepare a bibliography using sisu (which are mutually exclusive): (i) manually preparing and marking up as regular text in sisu a list of references, this is treated as a regular document segment (and placed before endnotes if any); (ii) preparing a bibliography, marking a heading level 1~!biblio (note the exclamation mark) and preparing a bibliography using various metadata tags including for author: title: year: a list of which is provided below, or; (iii) as an assistance in preparing a bibliography, marking a heading level 1~!biblio and tagging citations within footnotes for inclusion, identifying citations and having a parser attempt to extract them and build a bibliography of the citations provided. + +For the heading/section sequence: endnotes, bibliography then book index to occur, the name biblio or bibliography must be given to the bibliography section, like so: + +code{ + +1~!biblio + +}code + +3~ a markup tagged metadata bibliography section + +Here instead of writing your full citations directly in footnotes, each time you have new material to cite, you add it to your bibliography section (if it has not been added yet) providing the information you need against an available list of tags (provided below). + +The required tags are au: ti: and year: ~{for which you may alternatively use the full form author: title: and year: }~ an short quick example might be as follows: + +code{ + +1~!biblio + +au: von Hippel, E. +ti: Perspective: User Toolkits for Innovation +lng: (language) +jo: Journal of Product Innovation Management +vo: 18 +ed: (editor) +yr: 2001 +note: +sn: Hippel, /{User Toolkits}/ (2001) +id: vHippel_2001 +% form: + +au: Benkler, Yochai +ti: The Wealth of Networks +st: How Social Production Transforms Markets and Freedom +lng: (language) +pb: Harvard University Press +edn: (edition) +yr: 2006 +pl: U.S. +url: http://cyber.law.harvard.edu/wealth_of_networks/Main_Page +note: +sn: Benkler, /{Wealth of Networks}/ (2006) +id: Benkler2006 + +au: Quixote, Don; Panza, Sancho +ti: Taming Windmills, Keeping True +jo: Imaginary Journal +yr: 1605 +url: https://en.wikipedia.org/wiki/Don_Quixote +note: made up to provide an example of author markup for an article with two authors +sn: Quixote & Panza, /{Taming Windmills}/ (1605) +id: quixote1605 + +}code + +Note that the section name !biblio (or !bibliography) is required for the bibliography to be treated specially as such, and placed after the auto-generated endnote section. + +Using this method, work goes into preparing the bibliography, the tags author or editor, year and title are required and will be used to sort the bibliography that is placed under the Bibliography section + +The metadata tags may include shortname (sn:) and id, if provided, which are used for substitution within text. Every time the given id is found within the text it will be replaced by the given short title of the work (it is for this reason the short title has sisu markup to italicize the title), it should work with any page numbers to be added, the short title should be one that can easily be used to look up the full description in the bibliography. + +code{ + +The following footnote~{ quixote1605, pp 1000 - 1001, also Benkler2006 p 1. }~ + +}code + +would be presented as: + +Quixote and Panza, /{Taming Windmills}/ (1605), pp 1000 - 1001 also, Benkler, /{Wealth of Networks}/, (2006) p 1 or rather~{ Quixote and Panza, /{Taming Windmills}/ (1605), pp 1000 - 1001 also, Benkler, /{Wealth of Networks}/ (2006), p 1 }~ + +code{ + +au: author Surname, FirstNames (if multiple semi-colon separator) + (required unless editor to be used instead) +ti: title (required) +st: subtitle +jo: journal +vo: volume +ed: editor (required if author not provided) +tr: translator +src: source (generic field where others are not appropriate) +in: in (like src) +pl: place/location (state, country) +pb: publisher +edn: edition +yr: year (yyyy or yyyy-mm or yyyy-mm-dd) (required) +pg: pages +url: http://url +note: note +id: create_short_identifier e.g. authorSurnameYear + (used in substitutions: when found within text will be + replaced by the short name provided) +sn: short name e.g. Author, /{short title}/, Year + (used in substitutions: when an id is found within text + the short name will be used to replace it) + +}code + +3~ Tagging citations for inclusion in the Bibliography + +Here whenever you make a citation that you wish be included in the bibliography, you tag the citation as such using special delimiters (which are subsequently removed from the final text produced by sisu) + +Here you would write something like the following, either in regular text or a footnote + +code{ + +See .: Quixote, Don; Panza, Sancho /{Taming Windmills, Keeping True}/ (1605) :. + +}code + +SiSU will parse for a number of patterns within the delimiters to try make out the authors, title, date etc. and from that create a Bibliography. This is more limited than the previously described method of preparing a tagged bibliography, and using an id within text to identify the work, which also lends itself to greater consistency. + +2~ Glossary +={ SiSU markup:glossary|Glossary } + +Using the section name 1~!glossary results in the Glossary being treated specially as such, and placed after the auto-generated endnote section (before the bibliography/list of references if there is one). + +The Glossary is ordinary text marked up in a manner deemed suitable for that purpose. e.g. with the term in bold, possibly with a hanging indent. + +code{ + +1~!glossary + +_0_1 *{GPL}* An abbreviation that stands for "General Purpose License." ... + +_0_1 [provide your list of terms and definitions] + +}code + +In the given example the first line is not indented subsequent lines are by one level, and the term to be defined is in bold text. + +2~ Book index +={ SiSU markup:book index;book index } + +To make an index append to paragraph the book index term relates to it, using an equal sign and curly braces. + +Currently two levels are provided, a main term and if needed a sub-term. Sub-terms are separated from the main term by a colon. + +code{ + + Paragraph containing main term and sub-term. + ={Main term:sub-term} + +}code + +The index syntax starts on a new line, but there should not be an empty line between paragraph and index markup. + +The structure of the resulting index would be: + +code{ + + Main term, 1 + sub-term, 1 + +}code + +Several terms may relate to a paragraph, they are separated by a semicolon. If the term refers to more than one paragraph, indicate the number of paragraphs. + +code{ + + Paragraph containing main term, second term and sub-term. + ={first term; second term: sub-term} + +}code + +The structure of the resulting index would be: + +code{ + + First term, 1, + Second term, 1, + sub-term, 1 + +}code + +If multiple sub-terms appear under one paragraph, they are separated under the main term heading from each other by a pipe symbol. + +code{ + + Paragraph containing main term, second term and sub-term. + ={Main term: + sub-term+2|second sub-term; + Another term + } + + A paragraph that continues discussion of the first sub-term + +}code + +The plus one in the example provided indicates the first sub-term spans one additional paragraph. The logical structure of the resulting index would be: + +code{ + + Main term, 1, + sub-term, 1-3, + second sub-term, 1, + Another term, 1 + +}code + +1~ Composite documents markup +={ SiSU markup:composite documents;composite documents } + +It is possible to build a document by creating a master document that requires other documents. The documents required may be complete documents that could be generated independently, or they could be markup snippets, prepared so as to be easily available to be placed within another text. If the calling document is a master document (built from other documents), it should be named with the suffix *{.ssm}* Within this document you would provide information on the other documents that should be included within the text. These may be other documents that would be processed in a regular way, or markup bits prepared only for inclusion within a master document *{.sst}* regular markup file, or *{.ssi}* (insert/information) A secondary file of the composite document is built prior to processing with the same prefix and the suffix *{._sst}* + +basic markup for importing a document into a master document + +code{ + +<< filename1.sst + +<< filename2.ssi + +}code + +The form described above should be relied on. Within the Vim editor it results in the text thus linked becoming hyperlinked to the document it is calling in which is convenient for editing. + +1~ Substitutions +={ SiSU markup:substitutions;substitutions } + +!_ markup example: + +code{ + +The current Debian is ${debian_stable} the next debian will be ${debian_testing} + +Configure substitution in _sisu/sisu_document_make + +@make: + :substitute: /${debian_stable}/,'*{Wheezy}*' /${debian_testing}/,'*{Jessie}*' + +}code + +!_ resulting output: + +The current Debian is ${debian_stable} the next debian will be ${debian_testing} + +Another test ${sisudoc} ok? + +Configure substitution in _sisu/sisu_document_make + +1~ Footnote, endnote stress test + +Globalisation is to be observed as a trend intrinsic to the world economy.~{ As Maria Cattaui Livanos suggests in /{The global economy - an opportunity to be seized}/ in /{Business World}/ the Electronic magazine of the International Chamber of Commerce (Paris, July 1997) at http://www.iccwbo.org/html/globalec.htm \\ "Globalization is unstoppable. Even though it may be only in its early stages, it is already intrinsic to the world economy. We have to live with it, recognize its advantages and learn to manage it. \\ That imperative applies to governments, who would be unwise to attempt to stem the tide for reasons of political expediency. It also goes for companies of all sizes, who must now compete on global markets and learn to adjust their strategies accordingly, seizing the opportunities that globalization offers."}~ Rudimentary economics explains this runaway process, as being driven by competition within the business community to achieve efficient production, and to reach and extend available markets.~{To remain successful, being in competition, the business community is compelled to take advantage of the opportunities provided by globalisation.}~ Technological advancement particularly in transport and communications has historically played a fundamental role in the furtherance of international commerce, with the Net, technology's latest spatio-temporally transforming offering, linchpin of the "new-economy", extending exponentially the global reach of the business community. The Net covers much of the essence of international commerce providing an instantaneous, low cost, convergent, global and borderless: information centre, marketplace and channel for communications, payments and the delivery of services and intellectual property. The sale of goods, however, involves the separate element of their physical delivery. The Net has raised a plethora of questions and has frequently offered solutions. The increased transparency of borders arising from the Net's ubiquitous nature results in an increased demand for the transparency of operation. As economic activities become increasingly global, to reduce transaction costs, there is a strong incentive for the "law" that provides for them, to do so in a similar dimension. The appeal of transnational legal solutions lies in the potential reduction in complexity, more widely dispersed expertise, and resulting increased transaction efficiency. The Net reflexively offers possibilities for the development of transnational legal solutions, having in a similar vein transformed the possibilities for the promulgation of texts, the sharing of ideas and collaborative ventures. There are however, likely to be tensions within the legal community protecting entrenched practices against that which is new, (both in law and technology) and the business community's goal to reduce transaction costs. This here http://sisudoc.org/now is a test and repeat { does this work? }http://www.sisudoc.com/ok.html + +Within commercial law an analysis of law and economics may assist in +developing a better understanding of the relationship between commercial +law and the commercial sector it serves.~{ Realists would contend that law +is contextual and best understood by exploring the interrelationships +between law and the other social sciences, such as sociology, psychology, +political science, and economics.}~ "...[T]he importance of the +interrelations between law and economics can be seen in the twin facts +that legal change is often a function of economic ideas and conditions, +which necessitate and/or generate demands for legal change, and that +economic change is often governed by legal change."~{ Part of a section +cited in Mercuro and Steven G. Medema, /{Economics and the Law: from +Posner to Post-Modernism}/ (Princeton, 1997) p. 11, with reference to Karl +N. Llewellyn The Effect of Legal Institutions upon Economics, American +Economic Review 15 (December 1925) pp 655-683, Mark M. Litchman Economics, +the Basis of Law, American Law Review 61 (May-June 1927) pp 357-387, and +W. S. Holdsworth A Neglected Aspect of the Relations between Economic and +Legal History, Economic History Review 1 (January 1927-1928) pp 114-123.}~ +In doing so, however, it is important to be aware that there are several +competing schools of law and economics, with different perspectives, +levels of abstraction, and analytical consequences of and for the world +that they model.~{ For a good introduction see Nicholas Mercuro and Steven +G. Medema, /{Economics and the Law: from Posner to Post-Modernism}/ +(Princeton, 1997). These include: Chicago law and economics (New law and +economics); New Haven School of law and economics; Public Choice Theory; +Institutional law and economics; Neoinstitutional law and economics; +Critical Legal Studies.}~ This sentence trails test endnote. + +Difference?~{ puzzle away }~ + +* !glossary + +head + +header +document header, containing document specific (i) metadata information or (ii) make instructions + +(document) structure +relationship between headings and sub-headings, and the objects they contain. Document structure is extracted from heading levels, which are either: explicitly marked up, or; determined from a make regex provided in the document header. Use of document structure allow for the meaningful representation of documents in alternative ways and the use of ocn permits easy reference across different output formats. + +heading +document heading, each heading is marked indicating its level (in relation to other headings), and this is used as basis for determininge document structure. There are 8 levels, which are can be distinguesed as being one of three types: (i) 1 title level (marked up A or numeric 0); (ii) 3 optional document division levels, above text separating headings (marked up B - D, or numeric 1 to 3); (iii) 4 text headings (marked up 1 - 4, or numeric 4 to 7) + +levels == heading levels +document heading level, see heading and structure + +marked up headings / mark up level + +collapsed headings / collapsed levels + +numeric levels + +dummy heading +a markup level 1 / dummy level 4 that does not exist in the original text that is manually inserted to maintain the documents structure rule that text follows a heading of markup level 1 (rather than A to D) (numeric level 4 rather than 0 to 3) + +relatives? see ancestors and descendants + +document ... + +ancestors +heading levels above the current heading level which it logically falls under and to which it belongs (headings preceding current level under which it occurs) + +decendants +decendant headings are sub-headings beneath the current heading level, heading levels below the current heading level which are derived from it and belong to it (sub-headings contained beneath current level); decendant objects are the range of objects contained by a heading (ocn ranges for each heading in document body) + +(document) sections +a document can be divided into 3 parts: front; body and; back. Front matter includes the table of contents (which is generated from headings) and any parts of the document that are presented before the document body (this might include a copyright notice for example). The document body, the substantive part of the document, all its substantive objects, including: headings, paragraphs, tables, verse etc. This is followed by optional backmatter: endnotes, generated from inline markup; glossary, from section using a subset of regular markup, with an indication that section is to be treated as glossary. Note two things glossary might do that it does not, there is: no automatic (sorting) alphabetisation of listing; no creation of term anchor tags (perhaps it should); bibliography, created from a specially marked up section, with indication that section is to be treated as bibliography; bookindex generated from dedicated markup appended to objects providing index terms and the relevant range; blurb made up of ordinary markup, with indication that section is to be treated as blurb + +segment, segmented text +certain forms of output are conveniently segmented, e.g. epub and segmented html. The document is broken into chunks indicated by markup level 1 heading (numeric level 4 headings) as the significant level at which the document should be segmented, and including all decendant objects of that level. For a longer text/book this will usually the chapter level. (this is significant in e.g. for epub and segmented html, which are broken by segment, usually chosen to be chapter) + +scroll +the document as a "scroll", e.g. as a single text file, or continuous html document + +object +a unit of text. Objects include: headings; paragraphs; code blocks; grouped text; verse of poems; tables. Each substantive object is given an object number, that should make it citable. + +ocn (object citation number / citation number) +numbers assigned sequentially to each substantive object of a document. An ocn has the characteristic of remaining identical across output formats. Translations should be prepared so number remains identical across objects in different languages + +citation number (see ocn / object citation number) + +document abstraction (== internal representation) intermediate step, +preprocessing of document, into abstraction / representation that is used by all downstream processing, i.e. for all output formats. This allows normalisation, reducing alternative markup options to common representations, e.g. code blocks (open and close), tables, ways of instructing that text be bold, shortuct way of providing and endnote reference to a link + +(document) internal representation (== document abstraction) +see document abstraction + +node representation + +attribute (object attributes) +when the document is abstracted attributes associated with an object, for example for a: paragraph, indent (hang ... check & add), bulleted, for a: code block, the language syntax, whether the block is numbered + +inline markup +when the document is abstracted, markup that remains embedded in the text, such as its font face (bold, italic, emphasis, underscore, strike, superscript, subscript), links, endnotes + +sequential all objects backkeeping number? + +1~commands Sample Commands + +2~ general + +~sdp/bin/sdp-ldc -v --epub --html --sqlite-update --output-dir=tmp/program-output data/sisupod/sisu-manual + +time ( ~sdp/bin/sdp-ldc -v --epub --html --sqlite-update --output-dir=tmp/program-output data/sisupod/* ) + +2~ source & sisupod + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisudir/media/text/sisu-manual.sst + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-ldc -v --source --sisupod --output-dir=tmp/program-output data/sisupod/* + +2~ sqlite + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-create --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --output-dir=tmp/program-output + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --sqlite-insert --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-recreate --sqlite-update --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/* + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-ldc -v --sqlite-db-drop --sqlite-db-create --sqlite-update --epub --html --output-dir=tmp/program-output data/sisupod/sisu-manual + +~sdp/bin/sdp-dmd -v --epub --html --output-dir=tmp/program-output data/sisudir/media/text/sisu_markup.sst + +1~!blurb On SiSU + +SiSU was started in 1997, open-sourced in 2005. diff --git a/docs/ao_abstract_doc_source.html b/docs/ao_abstract_doc_source.html new file mode 100644 index 0000000..bbd7f34 --- /dev/null +++ b/docs/ao_abstract_doc_source.html @@ -0,0 +1,17 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_abstract_doc_source.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_abstract_doc_source</title>
+ </head><body>
+ <h1>ao_abstract_doc_source</h1>
+<br><br>
+<dl><dt><big><a name="SiSUdocAbstraction"></a>template <u>SiSUdocAbstraction</u>()</big></dt>
+<dd>document abstraction:
+ abstraction of sisu markup for downstream processing
+ ao_abstract_doc_source.d<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_ansi_colors.html b/docs/ao_ansi_colors.html new file mode 100644 index 0000000..dac2751 --- /dev/null +++ b/docs/ao_ansi_colors.html @@ -0,0 +1,15 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_ansi_colors.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_ansi_colors</title>
+ </head><body>
+ <h1>ao_ansi_colors</h1>
+<br><br>
+<dl><dt><big><a name="ScreenTxtColors"></a>template <u>ScreenTxtColors</u>()</big></dt>
+<dd>ansi colors, depreciate use<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_conf_make_meta.html b/docs/ao_conf_make_meta.html new file mode 100644 index 0000000..a807764 --- /dev/null +++ b/docs/ao_conf_make_meta.html @@ -0,0 +1,22 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_conf_make_meta.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_conf_make_meta</title>
+ </head><body>
+ <h1>ao_conf_make_meta</h1>
+<br><br>
+<dl><dt><big><a name="SiSUheaderExtractHub"></a>template <u>SiSUheaderExtractHub</u>()</big></dt>
+<dd>extract native/orig header return associative array<BR>
+<br><br>
+the header is passed as text (lopped off top of a sisu markup file until the
+ required first heading ^A~), determine whether is a native header or sdlang one
+ with a regex check if whether it contains the "native header" required tag/field
+ @title: then process accordingly as a "native header" or "sdlang header"
+ converting the metadata and make instructions to a common json format used by
+ program internally. Moved to associative array.<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_conf_make_meta_native.html b/docs/ao_conf_make_meta_native.html new file mode 100644 index 0000000..387e379 --- /dev/null +++ b/docs/ao_conf_make_meta_native.html @@ -0,0 +1,16 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_conf_make_meta_native.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_conf_make_meta_native</title>
+ </head><body>
+ <h1>ao_conf_make_meta_native</h1>
+<br><br>
+<dl><dt><big><a name="SiSUheaderExtractNative"></a>template <u>SiSUheaderExtractNative</u>()</big></dt>
+<dd>native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR>
+ extract native/orig header return associative array<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_conf_make_meta_sdlang.html b/docs/ao_conf_make_meta_sdlang.html new file mode 100644 index 0000000..cfb6f24 --- /dev/null +++ b/docs/ao_conf_make_meta_sdlang.html @@ -0,0 +1,16 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_conf_make_meta_sdlang.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_conf_make_meta_sdlang</title>
+ </head><body>
+ <h1>ao_conf_make_meta_sdlang</h1>
+<br><br>
+<dl><dt><big><a name="SiSUheaderExtractSDLang"></a>template <u>SiSUheaderExtractSDLang</u>()</big></dt>
+<dd>sdlang headers<BR>
+ extract sdlang header return sdlang<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_defaults.html b/docs/ao_defaults.html new file mode 100644 index 0000000..280220e --- /dev/null +++ b/docs/ao_defaults.html @@ -0,0 +1,15 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_defaults.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_defaults</title>
+ </head><body>
+ <h1>ao_defaults</h1>
+<br><br>
+<dl><dt><big><a name="SiSUregisters"></a>template <u>SiSUregisters</u>()</big></dt>
+<dd>default settings<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_object_setter.html b/docs/ao_object_setter.html new file mode 100644 index 0000000..05e4e67 --- /dev/null +++ b/docs/ao_object_setter.html @@ -0,0 +1,17 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_object_setter.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_object_setter</title>
+ </head><body>
+ <h1>ao_object_setter</h1>
+<br><br>
+<dl><dt><big><a name="ObjectSetter"></a>template <u>ObjectSetter</u>()</big></dt>
+<dd>object setter:
+ setting of sisu objects for downstream processing
+ ao_object_setter.d<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_output_debugs.html b/docs/ao_output_debugs.html new file mode 100644 index 0000000..d57e800 --- /dev/null +++ b/docs/ao_output_debugs.html @@ -0,0 +1,15 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_output_debugs.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_output_debugs</title>
+ </head><body>
+ <h1>ao_output_debugs</h1>
+<br><br>
+<dl><dt><big><a name="SiSUoutputDebugs"></a>template <u>SiSUoutputDebugs</u>()</big></dt>
+<dd>output debugs<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_read_config_files.html b/docs/ao_read_config_files.html new file mode 100644 index 0000000..d5db7c1 --- /dev/null +++ b/docs/ao_read_config_files.html @@ -0,0 +1,17 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_read_config_files.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_read_config_files</title>
+ </head><body>
+ <h1>ao_read_config_files</h1>
+<br><br>
+<dl><dt><big><a name="SiSUconfigIn"></a>template <u>SiSUconfigIn</u>()</big></dt>
+<dd> read configuration files<BR>
+ - read config files<BR>
+ ao_config_files.d<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_read_source_files.html b/docs/ao_read_source_files.html new file mode 100644 index 0000000..a77a5b2 --- /dev/null +++ b/docs/ao_read_source_files.html @@ -0,0 +1,17 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_read_source_files.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_read_source_files</title>
+ </head><body>
+ <h1>ao_read_source_files</h1>
+<br><br>
+<dl><dt><big><a name="SiSUmarkupRaw"></a>template <u>SiSUmarkupRaw</u>()</big></dt>
+<dd> module ao_read_source_files;<BR>
+ - open markup files<BR>
+ - if master file scan for addional files to import/insert<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/ao_rgx.html b/docs/ao_rgx.html new file mode 100644 index 0000000..eedd524 --- /dev/null +++ b/docs/ao_rgx.html @@ -0,0 +1,16 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/ao_rgx.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>ao_rgx</title>
+ </head><body>
+ <h1>ao_rgx</h1>
+<br><br>
+<dl><dt><big><a name="RgxInit"></a>template <u>RgxInit</u>()</big></dt>
+<dd><b>regex:</b><br>
+regular expressions used in sisu document parser<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/compile_time_info.html b/docs/compile_time_info.html new file mode 100644 index 0000000..5e992f8 --- /dev/null +++ b/docs/compile_time_info.html @@ -0,0 +1,15 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/compile_time_info.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>compile_time_info</title>
+ </head><body>
+ <h1>compile_time_info</h1>
+<br><br>
+<dl><dt><big><a name="CompileTimeInfo"></a>template <u>CompileTimeInfo</u>()</big></dt>
+<dd>compile_time_info<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/output_html.html b/docs/output_html.html new file mode 100644 index 0000000..041521b --- /dev/null +++ b/docs/output_html.html @@ -0,0 +1,10 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/output_html.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>output_html</title>
+ </head><body>
+ <h1>output_html</h1>
+<br><br>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/output_hub.html b/docs/output_hub.html new file mode 100644 index 0000000..fd1c3b3 --- /dev/null +++ b/docs/output_hub.html @@ -0,0 +1,16 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp/output_hub.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>output_hub</title>
+ </head><body>
+ <h1>output_hub</h1>
+<br><br>
+<dl><dt><big><a name="SiSUoutputHub"></a>template <u>SiSUoutputHub</u>()</big></dt>
+<dd>output hub<BR>
+ check & generate output types requested<br><br>
+
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
diff --git a/docs/sdp.html b/docs/sdp.html new file mode 100644 index 0000000..3559b90 --- /dev/null +++ b/docs/sdp.html @@ -0,0 +1,25 @@ +<html><head>
+ <!-- Generated by Ddoc from src/sdp.d -->
+ <META http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>sdp</title>
+ </head><body>
+ <h1>sdp</h1>
+<br><br>
+<dl><dt><big><a name="main"></a>void <u>main</u>(string[] <i>args</i>);
+</big></dt>
+<dd>A SiSU document parser writen in D.<br><br>
+<b>Examples:</b><br>
+sdp sisu document parser
+<pre class="d_code">
+<font color=green>/++
+name "sdp"
+description "A SiSU document parser writen in D."
+homepage "http://sisudoc.org"
++/</font>
+</pre>
+<br><br>
+</dd>
+</dl>
+
+ <hr><small>Page generated by <a href="http://dlang.org/ddoc.html">Ddoc</a>. </small>
+ </body></html>
@@ -5,7 +5,6 @@ authors "Ralph Amissah" copyright "Copyright © 2016 Ralph Amissah" license "AGPL-3+" targetPath "./bin" -sourcePath "./src" stringImportPaths "./views" buildRequirements "allowWarnings" dependency "sdlang-d" version="~>0.10.0" diff --git a/dub.selections.json b/dub.selections.json index baf4c54..469497e 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -2,8 +2,8 @@ "fileVersion": 1, "versions": { "libinputvisitor": "1.2.2", - "sdlang-d": "0.10.0", + "sdlang-d": "0.10.1", "taggedalgebraic": "0.10.2", - "unit-threaded": "0.6.28" + "unit-threaded": "0.6.29" } } @@ -159,20 +159,22 @@ skel: init: skel +clean: + rm -rf $(PRG_SRCDIR); \ + rm -rf $(PRG_DOCDIR); + clean_bin: rm $(PRG_BINDIR)/* clean_src: - rm -f $(PRG_SRCDIR)/*; \ - rm -rf $(PRG_SRCDIR)/$(PRG_NAME); \ + rm -rf $(PRG_SRCDIR); clean_docs: rm -rf $(PRG_DOCDIR) expunge: rm -f dub.selections.json; \ - rm -f $(PRG_SRCDIR)/*; \ - rm -rf $(PRG_SRCDIR)/$(PRG_NAME); \ + rm -rf $(PRG_SRCDIR); \ rm -rf $(PRG_BINDIR); \ rm -rf $(PRG_DOCDIR); @@ -383,20 +383,22 @@ skel: init: skel +clean: + rm -rf $(PRG_SRCDIR); \ + rm -rf $(PRG_DOCDIR); + clean_bin: rm $(PRG_BINDIR)/* clean_src: - rm -f $(PRG_SRCDIR)/*; \ - rm -rf $(PRG_SRCDIR)/$(PRG_NAME); \ + rm -rf $(PRG_SRCDIR); clean_docs: rm -rf $(PRG_DOCDIR) expunge: rm -f dub.selections.json; \ - rm -f $(PRG_SRCDIR)/*; \ - rm -rf $(PRG_SRCDIR)/$(PRG_NAME); \ + rm -rf $(PRG_SRCDIR); \ rm -rf $(PRG_BINDIR); \ rm -rf $(PRG_DOCDIR); @@ -474,7 +476,6 @@ Every DUB package should contain a [[http://code.dlang.org/package-format?lang=j *** TODO dub.sdl [#A] :sdl: **** header :header: - #+BEGIN_SRC sh :tangle dub.sdl name "sdp" description "sisu document parser" @@ -483,7 +484,6 @@ authors "Ralph Amissah" copyright "Copyright © 2016 Ralph Amissah" license "AGPL-3+" targetPath "./bin" -sourcePath "./src" stringImportPaths "./views" buildRequirements "allowWarnings" dependency "sdlang-d" version="~>0.10.0" @@ -542,7 +542,7 @@ configuration "sdp-debug-clean" { } #+END_SRC -**** configuration dmd :dmd: +**** configuration dmd [#A] :dmd: ***** sdp-dmd #+BEGIN_SRC sh :tangle dub.sdl configuration "sdp-dmd" { @@ -610,7 +610,7 @@ configuration "sdp-debug-docs-dmd" { } #+END_SRC -**** configuration ldc :ldc: +**** configuration ldc [#A] :ldc: ***** sdp-ldc #+BEGIN_SRC sh :tangle dub.sdl configuration "sdp-ldc" { @@ -676,103 +676,6 @@ configuration "sdp-debug-docs-ldc" { } #+END_SRC -**** call dub directly - -#+BEGIN_SRC sh :tangle no -dub --config=sdp-debug-ldc -dub --config=sdp-debug-unittest-ldc -#+END_SRC - - -#+BEGIN_SRC sh :tangle no -dub --config=sdp-debug-ldc -dub --config=sdp-debug-unittest-ldc -#+END_SRC - -**** TODO configuration debug list :debug: - -#+BEGIN_SRC sh :tangle no -debugVersions "biblio" -debugVersions "biblio0" -debugVersions "bibliobuild" -debugVersions "biblioblock" -debugVersions "biblioblockinclude" -debugVersions "bibliosorted" -debugVersions "biblio_sorted" -debugVersions "block" -debugVersions "bookindex" -debugVersions "bookindexmatch" -debugVersions "bookindexraw" -debugVersions "check" -debugVersions "checkdoc" -debugVersions "code" -debugVersions "comment" -debugVersions "configfile" -debugVersions "dumpdoc" -debugVersions "endnotes" -debugVersions "endnotes_build" -debugVersions "footnotes" -debugVersions "footnotesdone" -debugVersions "group" -debugVersions "header" -debugVersions "header_and_content" -debugVersions "header1" -debugVersions "headerjson" -debugVersions "headermakejson" -debugVersions "headermetadatajson" -debugVersions "headersdlang" -debugVersions "heading" -debugVersions "headings" -debugVersions "headingsfound" -debugVersions "insert" -debugVersions "munge" -debugVersions "node" -debugVersions "objectrelated1" -debugVersions "objectrelated2" -debugVersions "objects" -debugVersions "ocnoff" -debugVersions "para" -debugVersions "parabullet" -debugVersions "parabulletindent" -debugVersions "paraindent" -debugVersions "paraindenthang" -debugVersions "parent" -debugVersions "poem" -debugVersions "quote" -debugVersions "raw" -debugVersions "sdlang" -debugVersions "source" -debugVersions "srclines" -debugVersions "structattrib" -debugVersions "summary" -debugVersions "table" -#+END_SRC - -dflags platform="dmd" "-v --force -de -w -J./views -I./src/sdp -of./bin/sdp" -dflags platform="dmd" "-O -release" -#buildOptions "-O -release" - -**** dub dependencies - -https://code.dlang.org/docs/commandline -https://code.dlang.org/package-format?lang=sdl -https://code.dlang.org/getting_started -https://code.dlang.org/packages/sdlang-d -https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md - -dub list -dub upgrade -dub fetch sdlang-d -dub fetch taggedalgebraic -dub fetch libinputvisitor -dub fetch unit-threaded ? - -sdp ~master: /home/ralph/grotto/repo/git.repo/utils/sdp-x/ -sdlang-d 0.10.0: /home/ralph/.dub/packages/sdlang-d-0.10.0/sdlang-d/ -taggedalgebraic 0.10.2: /home/ralph/.dub/packages/taggedalgebraic-0.10.2/taggedalgebraic/ -libinputvisitor 1.2.2: /home/ralph/.dub/packages/libinputvisitor-1.2.2/libinputvisitor/ -unit-threaded 0.6.28: /home/ralph/.dub/packages/unit-threaded-0.6.28/unit-threaded/ - *** +dub.json+ :json: **** +sdp+ @@ -793,62 +696,6 @@ unit-threaded 0.6.28: /home/ralph/.dub/packages/unit-threaded-0.6.28/unit-thread } #+END_SRC -**** +sdlang+ :sdlang: -#+BEGIN_SRC json :tangle ./src/sdlang/dub.json -{ - "name": "sdlang-d", - "description": "An SDL (Simple Declarative Language) library for D.", - "homepage": "http://github.com/Abscissa/SDLang-D", - "authors": ["Nick Sabalausky"], - "license": "zlib/libpng", - "copyright": "©2012-2015 Nick Sabalausky", - "sourcePaths": ["."], - "importPaths": ["."], - "buildRequirements": ["allowWarnings"], - "dependencies": { - "libinputvisitor": "~>1.2.0" - }, - "subPackages": [ - "./libinputvisitor" - ], - "configurations": [ - { - "name": "test", - "targetType": "executable", - "versions": ["SDLang_TestApp"], - "targetPath": "../../bin/", - "targetName": "sdlang" - }, - { - "name": "library", - "targetType": "library" - }, - { - "name": "unittest", - "targetType": "executable", - "targetPath": "../../bin/", - "targetName": "sdlang-unittest", - - "versions": ["sdlangUnittest", "sdlangTrace"] - } - ] -} -#+END_SRC - -**** +libinputvisitor+ :libinputvisitor: -#+BEGIN_SRC json :tangle ./src/sdlang/libinputvisitor/dub.json -{ - "name": "libinputvisitor", - "description": "Write D input range generators in a straightforward coroutine style", - "authors": ["Nick Sabalausky"], - "homepage": "https://github.com/abscissa/libInputVisitor", - "license": "WTFPL", - "sourcePaths": ["."], - "importPaths": ["."], - "excludedSourceFiles": ["libInputVisitorExample.d"] -} -#+END_SRC - ** .gitignore :gitignore: #+BEGIN_SRC sh :tangle .gitignore @@ -943,7 +790,7 @@ ldc2 -de -w -J./views -I./src/sdp -of=./bin/sdp ./src/sdp.d ldc2 -de -w -J./views -I./src/sdp -O4 -release -of=./bin/sdp ./src/sdp.d #+END_SRC -*** TODO gdc :gdc: +*** gdc (not done) [#F] :gdc: not done @@ -957,7 +804,7 @@ not done re: dmd "one can easily get 2x (and even more) speedup by simply switching to gdc -O2", ketmar ** build tools :build:tool: -*** rdmd [#B] :rdmd: +*** rdmd [#C] :rdmd: flags similar to dmd @@ -977,57 +824,131 @@ rdmd -de -w -J./views -I./src/sdp -unittest -d-debug=checkdoc -d-debug=summary - https://github.com/dlang/dub/ http://code.dlang.org/getting_started http://code.dlang.org/docs/commandline +**** dub json or sdlang Every DUB package should contain a [[http://code.dlang.org/package-format?lang=json][dub.json]] or [[http://code.dlang.org/package-format?lang=sdl][dub.sdl]] +https://code.dlang.org/package-format?lang=sdl +https://code.dlang.org/packages/sdlang-d +https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md -#+BEGIN_SRC sh ---compiler=dmd ---compiler=ldc2 +**** dub dependencies +dub list +dub upgrade +dub fetch sdlang-d +dub fetch taggedalgebraic +dub fetch libinputvisitor +dub fetch unit-threaded ? ---compiler=dmd -v --force ---compiler=ldc2 -v --force -#+END_SRC +sdp ~master: /home/ralph/grotto/repo/git.repo/utils/sdp-x/ +sdlang-d 0.10.0: /home/ralph/.dub/packages/sdlang-d-0.10.0/sdlang-d/ +taggedalgebraic 0.10.2: /home/ralph/.dub/packages/taggedalgebraic-0.10.2/taggedalgebraic/ +libinputvisitor 1.2.2: /home/ralph/.dub/packages/libinputvisitor-1.2.2/libinputvisitor/ +unit-threaded 0.6.28: /home/ralph/.dub/packages/unit-threaded-0.6.28/unit-threaded/ -#+BEGIN_SRC sh -dub -v --force --debug=checkdoc --debug=summary --debug=dumpdoc -dub -v --force --build=release +**** TODO call dub directly [#A] -dub -v --combined --build-mode allAtOnce --build release # --annotate -dub build sdp -v --combined --build-mode allAtOnce -dub describe -dub build sdp -v --force --debug=checkdoc --debug=dumpdoc +#+BEGIN_SRC sh :tangle no +dub --config=sdp-debug-ldc +dub --config=sdp-debug-unittest-ldc -dub -v --force --build=release +make tangle && dub --config=sdp-debug-unittest-ldc #--debug=node --debug=anchor --debug=endnotes -dub -v --force --config=sdp-debug -dub -v --force --config=sdp-release -dub -v --force --config=sdp-release --build=release +make expunge && make skel && make tangle && dub --config=sdp-ldc +make expunge && make skel && make tangle && dub --config=sdp-debug-unittest-ldc #--debug=node --debug=anchor --debug=endnotes -dub --compiler=dmd --force -v --config=sdp-debug -dub --compiler=dmd --force -v --config=sdp-release +make expunge && +make skel && make tangle && +dub --config=sdp-debug-unittest-ldc \ +--debug=node --debug=anchor --debug=endnotes -dub --config=sdp-debug-ldc -dub --config=sdp-debug-unittest-ldc +make tangle && +dub --config=sdp-debug-ldc --debug=node --debug=anchor +dub --config=sdp-debug-unittest-ldc --debug=node --debug=anchor +#+END_SRC + +*** sisu markup +#+BEGIN_SRC sh # dmd -,dubdmd --config=sdp-debug-clean +dub --compiler=dmd -v --force --config=sdp-debug-clean # time ~sdp3/bin/sdp-debug-clean -v --html --no-assert en/autonomy_markup0.sst # ldc # flags -,dubldc --config=sdp-release # sdp + +dub --compiler=ldc2 -v --force --config=sdp-release # sdp # time ~sdp3/bin/sdp -v --html --no-assert en/autonomy_markup0.sst -,dubldc --config=sdp-debug # sdp-debug +dub --compiler=ldc2 -v --force --config=sdp-debug # sdp-debug # time ~sdp3/bin/sdp-debug -v --html --no-assert en/autonomy_markup0.sst -,dubldc --config=sdp-tmp # sdp-tmp +dub --compiler=ldc2 -v --force --config=sdp-tmp # sdp-tmp # time ~sdp3/bin/sdp-tmp -v --html --no-assert en/autonomy_markup0.sst -,dubldc --config=sdp-debug-clean # sdp-debug-clean +dub --compiler=ldc2 -v --force --config=sdp-debug-clean # sdp-debug-clean # time ~sdp3/bin/sdp-debug-clean -v --html --no-assert en/autonomy_markup0.sst +#+END_SRC +** TODO debug options list [#A] :debug: + +#+BEGIN_SRC sh :tangle no +debugVersions "biblio" +debugVersions "biblio0" +debugVersions "bibliobuild" +debugVersions "biblioblock" +debugVersions "biblioblockinclude" +debugVersions "bibliosorted" +debugVersions "biblio_sorted" +debugVersions "block" +debugVersions "bookindex" +debugVersions "bookindexmatch" +debugVersions "bookindexraw" +debugVersions "check" +debugVersions "checkdoc" +debugVersions "code" +debugVersions "comment" +debugVersions "configfile" +debugVersions "dumpdoc" +debugVersions "endnotes" +debugVersions "endnotes_build" +debugVersions "footnotes" +debugVersions "footnotesdone" +debugVersions "group" +debugVersions "header" +debugVersions "header_and_content" +debugVersions "header1" +debugVersions "headerjson" +debugVersions "headermakejson" +debugVersions "headermetadatajson" +debugVersions "headersdlang" +debugVersions "heading" +debugVersions "headings" +debugVersions "headingsfound" +debugVersions "insert" +debugVersions "munge" +debugVersions "node" +debugVersions "objectrelated1" +debugVersions "objectrelated2" +debugVersions "objects" +debugVersions "ocnoff" +debugVersions "para" +debugVersions "parabullet" +debugVersions "parabulletindent" +debugVersions "paraindent" +debugVersions "paraindenthang" +debugVersions "parent" +debugVersions "poem" +debugVersions "quote" +debugVersions "raw" +debugVersions "sdlang" +debugVersions "source" +debugVersions "srclines" +debugVersions "structattrib" +debugVersions "summary" +debugVersions "table" #+END_SRC ** make (emacs) #+BEGIN_SRC sh +make -k tangle + make -k dub_dmd_release make -k dub_dmd_debug make -k dub_dmd_debug_clean diff --git a/org/ao_abstract_doc_source.org b/org/ao_abstract_doc_source.org index 958d2d1..5c78771 100644 --- a/org/ao_abstract_doc_source.org +++ b/org/ao_abstract_doc_source.org @@ -14,10 +14,10 @@ [[./sdp.org][sdp]] [[./][org/]] -* document abstraction :abstract:process: +* Document Abstraction :abstract:process: Process markup document, create document abstraction. -** 1. pre loop processing :pre: +** _1. pre loop processing_ :pre: *** imports :imports: [[./ao_defaults.org][ao_defaults]] @@ -36,10 +36,6 @@ import #+BEGIN_SRC d mixin ObjectSetter; mixin InternalMarkup; -// // mixin SiSUrgxInitFlags; -// // mixin AssertionsOnBlocks; -// mixin SiSUbiblio; // issue -// mixin SiSUheader; #+END_SRC *** initialize :initialize: @@ -133,9 +129,10 @@ string node_jstr_heading( is_ ); } -// mixin SiSUdocAbstractionFunctions; #+END_SRC +*** scope + #+name: abs_init_rest #+BEGIN_SRC d scope(success) { @@ -148,12 +145,17 @@ scope(exit) { destroy(processing); destroy(biblio_arr_json); } +#+END_SRC + +*** init rest + +#+name: abs_init_rest +#+BEGIN_SRC d line_occur = [ "heading" : 0, "para" : 0, ]; auto type = flags_type_init; -mixin ScreenTxtColors; void tell_lo(int obj_cite_number, in char[] line) { writefln( "* %s %s", @@ -209,7 +211,7 @@ auto heading_match_rgx = [ ]; #+END_SRC -** 2. loop: process document body [+6] :loop: +** _2. loop: process document body_ [+6] :loop: *** loop scope :scope: #+name: abs_in_loop_body_00 @@ -227,10 +229,9 @@ scope(failure) { } line = replaceAll(line, rgx.true_dollar, "$$$$"); // dollar represented as $$ needed to stop submatching on $ - // (substitutions using ${identifiers} must take into account (e.g. happen earlier)) + // (substitutions using ${identifiers} must take into account (i.e. happen earlier)) debug(source) { // source lines writeln(line); - // writeln(scr_txt_marker["green"], line); } debug(srclines) { if (!line.empty) { // source lines, not empty @@ -252,8 +253,8 @@ if (!line.empty) { #+END_SRC *** [#A] separate regular markup text from code blocks [+5] - **** code blocks :block:code: + #+name: abs_in_loop_body_00_code_block #+BEGIN_SRC d if (type["code"] == TriState.on) { @@ -686,8 +687,7 @@ if (contents_the_objects.length > 0) { } #+END_SRC -** 3. post loop processing :post: - +** _3. post loop processing_ :post: *** misc #+name: abs_post @@ -801,7 +801,7 @@ auto t = tuple( return t; #+END_SRC -* functions :abstract:function: +* Functions :abstract:function: functions used in document abstraction ** set & resets :reset: @@ -2315,16 +2315,13 @@ auto para_match( #+name: ao_emitters #+BEGIN_SRC d struct OCNemitter { -// class OCNemitter : AssertOCN { int obj_cite_number, obj_cite_number_; int obj_cite_number_emitter(int obj_cite_number_status_flag) in { assert(obj_cite_number_status_flag <= 2); } body { - if (obj_cite_number_status_flag == 0) { - obj_cite_number=++obj_cite_number_; - } else { - obj_cite_number=0; - } + obj_cite_number=(obj_cite_number_status_flag == 0) + ? ++obj_cite_number_ + : 0; assert(obj_cite_number >= 0); return obj_cite_number; } @@ -2338,7 +2335,6 @@ struct OCNemitter { #+name: ao_emitters #+BEGIN_SRC d struct ObjAttributes { -// class ObjAttributes : AssertObjAttributes { string[string] obj_txt; string para_and_blocks(string obj_txt_in) in { } @@ -2687,6 +2683,7 @@ struct ObjInlineMarkupMunge { #+END_SRC **** object inline markup :markup:inline: +***** open #+name: ao_emitters #+BEGIN_SRC d @@ -2694,6 +2691,12 @@ struct ObjInlineMarkup { // struct ObjInlineMarkup : AssertObjInlineMarkup { auto munge = ObjInlineMarkupMunge(); string[string] obj_txt; +#+END_SRC + +***** object inline markup and anchor tags :markup:inline: + +#+name: ao_emitters +#+BEGIN_SRC d auto obj_inline_markup_and_anchor_tags(string[string] obj_, string[string][string] dochead_make_aa) in { } body { @@ -2882,18 +2885,15 @@ struct ObjInlineMarkup { #+END_SRC **** object attrib :attributes: - +***** attributes structure open, public #+name: ao_emitters #+BEGIN_SRC d struct ObjAttrib { -// struct ObjAttrib : AssertObjAttrib { -// auto sink = appender!(char[])(); auto attrib = ObjAttributes(); string[string] obj_attrib; string obj_attributes(string obj_is_, string obj_raw, string node) in { } body { - // string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }"; scope(exit) { // destroy(obj_is_); destroy(obj_raw); @@ -2957,9 +2957,7 @@ struct ObjAttrib { obj_attrib["json"] = oa_j.toString(); debug(structattrib) { if (oa_j["is"].str() == "heading") { - // writeln(__LINE__); writeln(obj_attrib["json"]); - // writeln(node); writeln( "is: ", oa_j["is"].str(), "; obj_cite_number: ", oa_j["obj_cite_number"].integer() @@ -2971,6 +2969,11 @@ struct ObjAttrib { } invariant() { } +#+END_SRC + +***** close +#+name: ao_emitters +#+BEGIN_SRC d } #+END_SRC @@ -2980,7 +2983,6 @@ struct ObjAttrib { #+name: ao_emitters #+BEGIN_SRC d struct BookIndexNuggetHash { -// class BookIndexNuggetHash : AssertBookIndexNuggetHash { string main_term, sub_term, sub_term_bits; int obj_cite_number_offset, obj_cite_number_endpoint; string[] obj_cite_numbers; @@ -3088,11 +3090,17 @@ struct BookIndexReportIndent { **** book index (sort &) report section :report:section: +***** book index struct open #+name: ao_emitters #+BEGIN_SRC d struct BookIndexReportSection { int mkn, skn; auto rgx = Rgx(); +#+END_SRC + +***** bookindex write section +#+name: ao_emitters +#+BEGIN_SRC d auto bookindex_write_section( string[][string][string] bookindex_unordered_hashes ) { @@ -3121,8 +3129,7 @@ struct BookIndexReportSection { } #+END_SRC -**** book index (sort &) build section :report:section: - +***** book index (sort &) build section :report:section: #+name: ao_emitters #+BEGIN_SRC d auto bookindex_build_section( @@ -3223,8 +3230,7 @@ struct BookIndexReportSection { } #+END_SRC -**** book index (sort &) build section :report:section: - +***** book index struct close #+name: ao_emitters #+BEGIN_SRC d auto bookindex_build_section_( @@ -3273,6 +3279,11 @@ struct NotesSection { string object_notes; long previous_count; int mkn; +#+END_SRC + +**** gather notes for endnote section struct open +#+name: ao_emitters +#+BEGIN_SRC d auto rgx = Rgx(); private auto gather_notes_for_endnote_section( ObjComposite[] contents_am, @@ -3307,6 +3318,11 @@ struct NotesSection { } return object_notes; } +#+END_SRC + +**** gathered notes +#+name: ao_emitters +#+BEGIN_SRC d private auto gathered_notes() in { } @@ -3317,6 +3333,11 @@ struct NotesSection { } return endnotes_; } +#+END_SRC + +**** endnote objects +#+name: ao_emitters +#+BEGIN_SRC d private auto endnote_objects(int obj_cite_number) in { } @@ -3365,7 +3386,6 @@ struct NotesSection { ++mkn; foreach (endnote; endnotes_) { attrib=""; - attrib=""; // endnotes ~= // set_abstract_object.contents_para( // obj, @@ -3382,14 +3402,25 @@ struct NotesSection { auto t = tuple(endnotes_section, obj_cite_number); return t; } +#+END_SRC + +**** gather notes for endnote section struct close +#+name: ao_emitters +#+BEGIN_SRC d } #+END_SRC *** bibliography :bibliography: +**** biblio struct open #+name: ao_emitters #+BEGIN_SRC d struct Bibliography { +#+END_SRC + +**** biblio +#+name: ao_emitters +#+BEGIN_SRC d public JSONValue[] bibliography(ref string[] biblio_unsorted_incomplete, ref JSONValue[] bib_arr_json) in { } body { @@ -3413,6 +3444,11 @@ struct Bibliography { } return biblio_sorted__; } +#+END_SRC + +**** biblio unsorted complete +#+name: ao_emitters +#+BEGIN_SRC d final private JSONValue[] biblio_unsorted_complete( string[] biblio_unordered, ref JSONValue[] bib_arr_json @@ -3442,6 +3478,11 @@ struct Bibliography { bib_arr_json.dup; return biblio_unsorted_array_of_json_objects; } +#+END_SRC + +**** biblio sort +#+name: ao_emitters +#+BEGIN_SRC d final private JSONValue[] biblio_sort(JSONValue[] biblio_unordered) { JSONValue[] biblio_sorted_; biblio_sorted_ = @@ -3458,6 +3499,11 @@ struct Bibliography { } return biblio_sorted_; } +#+END_SRC + +**** biblio debug +#+name: ao_emitters +#+BEGIN_SRC d void biblio_debug(JSONValue[] biblio_sorted) { debug(biblio0) { foreach (j; biblio_sorted) { @@ -3467,20 +3513,29 @@ struct Bibliography { } } } +#+END_SRC + +**** biblio struct close +#+name: ao_emitters +#+BEGIN_SRC d } #+END_SRC *** node structure metadata :structure:metadata:node: -**** metadata (check) +**** metadata node struct open #+name: ao_emitters #+BEGIN_SRC d struct NodeStructureMetadata { -// class NodeStructureMetadata : AssertNodeJSON { int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7; int obj_cite_number; int[string] p_; // p_ parent_ string node; +#+END_SRC + +**** metadata node emitter +#+name: ao_emitters +#+BEGIN_SRC d string node_emitter( string lev_markup_number, int obj_cite_number_, @@ -3542,7 +3597,7 @@ struct NodeStructureMetadata { } #+END_SRC -**** metadata (check) +**** metadata emitter heading #+name: ao_emitters #+BEGIN_SRC d @@ -3730,6 +3785,11 @@ struct NodeStructureMetadata { } invariant() { } +#+END_SRC + +**** metadata node struct close +#+name: ao_emitters +#+BEGIN_SRC d } #+END_SRC @@ -3940,35 +4000,61 @@ auto assertions_flag_types_block_status_none_or_closed(int[string] type) { } #+END_SRC -* object setter :abstract:object: +* Object Setter :abstract:object: set abstracted objects for downstream processing -** structs :struct: +** initialize structs :struct: -#+name: ao_structs +*** heading attribute +#+name: ao_structs_init #+BEGIN_SRC d struct HeadingAttrib { string lev = "9"; int lev_markup_number = 9; int lev_collapsed_number = 9; } +#+END_SRC + +*** paragraph attribute +#+name: ao_structs_init +#+BEGIN_SRC d struct ParaAttrib { int indent_start = 0; int indent_rest = 0; bool bullet = false; } +#+END_SRC + +*** block attribute +#+name: ao_structs_init +#+BEGIN_SRC d struct BlockAttrib { string syntax = ""; } +#+END_SRC + +*** comment attribute +#+name: ao_structs_init +#+BEGIN_SRC d struct Comment { // no .attrib and no .obj_cite_number } +#+END_SRC + +*** node +#+name: ao_structs_init +#+BEGIN_SRC d struct Node { int ocn = 0; int parent_lev = 0; int parent_ocn = 0; string node = ""; } +#+END_SRC + +*** composite object +#+name: ao_structs_init +#+BEGIN_SRC d struct ObjComposite { // size_t id; string use = ""; @@ -3982,6 +4068,11 @@ struct ObjComposite { BlockAttrib block_attrib; Node node_structure; } +#+END_SRC + +*** object composite array +#+name: ao_structs_init +#+BEGIN_SRC d struct ObjCompositeArr { ObjComposite[] oca; } @@ -4143,12 +4234,13 @@ auto contents_block_obj_cite_number_string( } #+END_SRC -* tangles (code structure) :tangle:io:file: -** abstract doc source: :ao_abstract_doc_source.d: +* Tangles (code structure) :tangle:io:file: +** ao abstract doc source: :ao_abstract_doc_source.d: #+BEGIN_SRC d :tangle ../src/sdp/ao_abstract_doc_source.d -/+ - document abstraction +/++ + document abstraction: + abstraction of sisu markup for downstream processing ao_abstract_doc_source.d +/ template SiSUdocAbstraction() { @@ -4200,16 +4292,17 @@ template SiSUdocAbstraction() { } /+ ← closed: template SiSUdocAbstraction +/ #+END_SRC -** ao_object_setter: :ao_object_setter.d: +** ao object setter: :ao_object_setter.d: #+BEGIN_SRC d :tangle ../src/sdp/ao_object_setter.d -/+ - object setter +/++ + object setter: + setting of sisu objects for downstream processing ao_object_setter.d +/ template ObjectSetter() { /+ structs +/ - <<ao_structs>> + <<ao_structs_init>> /+ structs setter +/ struct ObjectAbstractSet { import std.conv : to; diff --git a/org/ao_conf_make_meta.org b/org/ao_conf_make_meta.org index f8c1332..ae028f2 100644 --- a/org/ao_conf_make_meta.org +++ b/org/ao_conf_make_meta.org @@ -624,16 +624,16 @@ private auto headerSDLangToAA(char[] header_sdlang_src, string[string][string] c * tangles (code structure) :tangle: ** 1. Header Hub :ao_markup_header_extract: -the header is passed as text (lopped off top of a sisu markup file until the -required first heading ^A~), determine whether is a native header or sdlang one -with a regex check if whether it contains the "native header" required tag/field -@title: then process accordingly as a "native header" or "sdlang header" -converting the metadata and make instructions to a common json format used by -program internally. Moved to associative array. - #+BEGIN_SRC d :tangle ../src/sdp/ao_conf_make_meta.d -/+ - extract native/orig header return associative array +/++ + extract native/orig header return associative array<BR> + + the header is passed as text (lopped off top of a sisu markup file until the + required first heading ^A~), determine whether is a native header or sdlang one + with a regex check if whether it contains the "native header" required tag/field + @title: then process accordingly as a "native header" or "sdlang header" + converting the metadata and make instructions to a common json format used by + program internally. Moved to associative array. +/ template SiSUheaderExtractHub() { private import @@ -660,7 +660,8 @@ template SiSUheaderExtractHub() { ** 2a. Header Native :ao_markup_header_extract_native: #+BEGIN_SRC d :tangle ../src/sdp/ao_conf_make_meta_native.d -/+ +/++ + native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR> extract native/orig header return associative array +/ template SiSUheaderExtractNative() { @@ -686,8 +687,9 @@ template SiSUheaderExtractNative() { ** 2b. Header SDLang :ao_markup_header_extract_sdlang: #+BEGIN_SRC d :tangle ../src/sdp/ao_conf_make_meta_sdlang.d -/+ - extract sdl header return sdl +/++ + sdlang headers<BR> + extract sdlang header return sdlang +/ template SiSUheaderExtractSDLang() { private import diff --git a/org/ao_defaults.org b/org/ao_defaults.org index b4ea917..c01ea41 100644 --- a/org/ao_defaults.org +++ b/org/ao_defaults.org @@ -893,9 +893,8 @@ string[string] scr_txt_marker = [ #+name: tangle_ao_defaults #+BEGIN_SRC d :tangle ../src/sdp/ao_defaults.d -/+ - defaults - ao_defaults.d +/++ + default settings +/ <<ao_defaults_templates>> #+END_SRC @@ -904,9 +903,8 @@ string[string] scr_txt_marker = [ #+name: tangle_ao_rgx #+BEGIN_SRC d :tangle ../src/sdp/ao_rgx.d -/+ - regex - ao_rgx.d +/++ + regex: regular expressions used in sisu document parser +/ template RgxInit() { struct Rgx { @@ -918,9 +916,8 @@ template RgxInit() { ** ansi_colors: :ao_ansi_colors.d: #+BEGIN_SRC d :tangle ../src/sdp/ao_ansi_colors.d -/+ - utils - ao_util.d +/++ + ansi colors, depreciate use +/ template ScreenTxtColors() { <<ao_ansi_colors>> diff --git a/org/ao_output_debugs.org b/org/ao_output_debugs.org index 8eccb73..4f17969 100644 --- a/org/ao_output_debugs.org +++ b/org/ao_output_debugs.org @@ -616,9 +616,8 @@ debug(checkdoc) { #+name: tangle_ao_output_debugs #+BEGIN_SRC d :tangle ../src/sdp/ao_output_debugs.d -/+ +/++ output debugs - ao_output_debugs.d +/ template SiSUoutputDebugs() { <<book_index_sorted_report>> diff --git a/org/ao_read_source_files.org b/org/ao_read_source_files.org index 37929a7..11647fb 100644 --- a/org/ao_read_source_files.org +++ b/org/ao_read_source_files.org @@ -439,12 +439,11 @@ return contents; ** ao_markup_source_raw.d: :ao_markup_source_raw: #+BEGIN_SRC d :tangle ../src/sdp/ao_read_source_files.d -/+ - ao_read_source_files.d - - open markup files +/++ + module ao_read_source_files;<BR> + - open markup files<BR> - if master file scan for addional files to import/insert +/ -// module ao_read_source_files; template SiSUmarkupRaw() { private import std.exception, @@ -520,9 +519,10 @@ WORK AREA *** config files, read in #+BEGIN_SRC d :tangle ../src/sdp/ao_read_config_files.d -/+ +/++ + read configuration files<BR> + - read config files<BR> ao_config_files.d - - read config files +/ template SiSUconfigIn() { private import diff --git a/org/compile_time_info.org b/org/compile_time_info.org index 9fb5830..2dc5eba 100644 --- a/org/compile_time_info.org +++ b/org/compile_time_info.org @@ -95,9 +95,8 @@ version(D_LP64) { ** compile_time_info: :compile_time_info.d: #+begin_src d :tangle ../src/sdp/compile_time_info.d -/+ +/++ compile_time_info - compile_time_info.d +/ template CompileTimeInfo() { <<sdp_compile_time_info>> diff --git a/org/output.org b/org/output.org index 1e1a346..1dd19a8 100644 --- a/org/output.org +++ b/org/output.org @@ -1539,9 +1539,9 @@ auto css_write() { **** hub #+BEGIN_SRC d :tangle ../src/sdp/output_hub.d -/+ - output_hub.d - output_html.d +/++ + output hub<BR> + check & generate output types requested +/ template SiSUoutputHub() { struct SDPoutput { diff --git a/org/sdp.org b/org/sdp.org index 60b37c3..954a1ef 100644 --- a/org/sdp.org +++ b/org/sdp.org @@ -24,7 +24,7 @@ struct Version { int minor; int patch; } -enum ver = Version(0, 6, 8); +enum ver = Version(0, 7, 0); #+END_SRC * sdp.d sisu document parser :sdp.d: diff --git a/src/sdlang/ast.d b/src/sdlang/ast.d deleted file mode 100644 index 87dd0bd..0000000 --- a/src/sdlang/ast.d +++ /dev/null @@ -1,2945 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.ast; - -import std.algorithm; -import std.array; -import std.conv; -import std.range; -import std.string; - -import sdlang.exception; -import sdlang.token; -import sdlang.util; - -class Attribute -{ - Value value; - Location location; - - private Tag _parent; - /// Get parent tag. To set a parent, attach this Attribute to its intended - /// parent tag by calling `Tag.add(...)`, or by passing it to - /// the parent tag's constructor. - @property Tag parent() - { - return _parent; - } - - private string _namespace; - /++ - This tag's namespace. Empty string if no namespace. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string namespace() - { - return _namespace; - } - ///ditto - @property void namespace(string value) - { - if(_parent && _namespace != value) - { - // Remove - auto saveParent = _parent; - if(_parent) - this.remove(); - - // Change namespace - _namespace = value; - - // Re-add - if(saveParent) - saveParent.add(this); - } - else - _namespace = value; - } - - private string _name; - /++ - This attribute's name, not including namespace. - - Use `getFullName().toString` if you want the namespace included. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this attribute is ordered - among its parent's list of tags. - +/ - @property string name() - { - return _name; - } - ///ditto - @property void name(string value) - { - if(_parent && _name != value) - { - _parent.updateId++; - - void removeFromGroupedLookup(string ns) - { - // Remove from _parent._attributes[ns] - auto sameNameAttrs = _parent._attributes[ns][_name]; - auto targetIndex = sameNameAttrs.countUntil(this); - _parent._attributes[ns][_name].removeIndex(targetIndex); - } - - // Remove from _parent._tags - removeFromGroupedLookup(_namespace); - removeFromGroupedLookup("*"); - - // Change name - _name = value; - - // Add to new locations in _parent._attributes - _parent._attributes[_namespace][_name] ~= this; - _parent._attributes["*"][_name] ~= this; - } - else - _name = value; - } - - /// This tag's name, including namespace if one exists. - deprecated("Use 'getFullName().toString()'") - @property string fullName() - { - return getFullName().toString(); - } - - /// This tag's name, including namespace if one exists. - FullName getFullName() - { - return FullName(_namespace, _name); - } - - this(string namespace, string name, Value value, Location location = Location(0, 0, 0)) - { - this._namespace = namespace; - this._name = name; - this.location = location; - this.value = value; - } - - this(string name, Value value, Location location = Location(0, 0, 0)) - { - this._namespace = ""; - this._name = name; - this.location = location; - this.value = value; - } - - /// Copy this Attribute. - /// The clone does $(B $(I not)) have a parent, even if the original does. - Attribute clone() - { - return new Attribute(_namespace, _name, value, location); - } - - /// Removes `this` from its parent, if any. Returns `this` for chaining. - /// Inefficient ATM, but it works. - Attribute remove() - { - if(!_parent) - return this; - - void removeFromGroupedLookup(string ns) - { - // Remove from _parent._attributes[ns] - auto sameNameAttrs = _parent._attributes[ns][_name]; - auto targetIndex = sameNameAttrs.countUntil(this); - _parent._attributes[ns][_name].removeIndex(targetIndex); - } - - // Remove from _parent._attributes - removeFromGroupedLookup(_namespace); - removeFromGroupedLookup("*"); - - // Remove from _parent.allAttributes - auto allAttrsIndex = _parent.allAttributes.countUntil(this); - _parent.allAttributes.removeIndex(allAttrsIndex); - - // Remove from _parent.attributeIndicies - auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace]; - auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex); - _parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex); - - // Fixup other indicies - foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies) - foreach(k, ref v; nsAttrIndicies) - if(v > allAttrsIndex) - v--; - - _parent.removeNamespaceIfEmpty(_namespace); - _parent.updateId++; - _parent = null; - return this; - } - - override bool opEquals(Object o) - { - auto a = cast(Attribute)o; - if(!a) - return false; - - return - _namespace == a._namespace && - _name == a._name && - value == a.value; - } - - string toSDLString()() - { - Appender!string sink; - this.toSDLString(sink); - return sink.data; - } - - void toSDLString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) - { - if(_namespace != "") - { - sink.put(_namespace); - sink.put(':'); - } - - sink.put(_name); - sink.put('='); - value.toSDLString(sink); - } -} - -/// Deep-copy an array of Tag or Attribute. -/// The top-level clones are $(B $(I not)) attached to any parent, even if the originals are. -T[] clone(T)(T[] arr) if(is(T==Tag) || is(T==Attribute)) -{ - T[] newArr; - newArr.length = arr.length; - - foreach(i; 0..arr.length) - newArr[i] = arr[i].clone(); - - return newArr; -} - -class Tag -{ - /// File/Line/Column/Index information for where this tag was located in - /// its original SDLang file. - Location location; - - /// Access all this tag's values, as an array of type `sdlang.token.Value`. - Value[] values; - - private Tag _parent; - /// Get parent tag. To set a parent, attach this Tag to its intended - /// parent tag by calling `Tag.add(...)`, or by passing it to - /// the parent tag's constructor. - @property Tag parent() - { - return _parent; - } - - private string _namespace; - /++ - This tag's namespace. Empty string if no namespace. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string namespace() - { - return _namespace; - } - ///ditto - @property void namespace(string value) - { - //TODO: Can we do this in-place, without removing/adding and thus - // modyfying the internal order? - if(_parent && _namespace != value) - { - // Remove - auto saveParent = _parent; - if(_parent) - this.remove(); - - // Change namespace - _namespace = value; - - // Re-add - if(saveParent) - saveParent.add(this); - } - else - _namespace = value; - } - - private string _name; - /++ - This tag's name, not including namespace. - - Use `getFullName().toString` if you want the namespace included. - - Note that setting this value is O(n) because internal lookup structures - need to be updated. - - Note also, that setting this may change where this tag is ordered among - its parent's list of tags. - +/ - @property string name() - { - return _name; - } - ///ditto - @property void name(string value) - { - //TODO: Seriously? Can't we at least do the "*" modification *in-place*? - - if(_parent && _name != value) - { - _parent.updateId++; - - // Not the most efficient, but it works. - void removeFromGroupedLookup(string ns) - { - // Remove from _parent._tags[ns] - auto sameNameTags = _parent._tags[ns][_name]; - auto targetIndex = sameNameTags.countUntil(this); - _parent._tags[ns][_name].removeIndex(targetIndex); - } - - // Remove from _parent._tags - removeFromGroupedLookup(_namespace); - removeFromGroupedLookup("*"); - - // Change name - _name = value; - - // Add to new locations in _parent._tags - //TODO: Can we re-insert while preserving the original order? - _parent._tags[_namespace][_name] ~= this; - _parent._tags["*"][_name] ~= this; - } - else - _name = value; - } - - /// This tag's name, including namespace if one exists. - deprecated("Use 'getFullName().toString()'") - @property string fullName() - { - return getFullName().toString(); - } - - /// This tag's name, including namespace if one exists. - FullName getFullName() - { - return FullName(_namespace, _name); - } - - // Tracks dirtiness. This is incremented every time a change is made which - // could invalidate existing ranges. This way, the ranges can detect when - // they've been invalidated. - private size_t updateId=0; - - this(Tag parent = null) - { - if(parent) - parent.add(this); - } - - this( - string namespace, string name, - Value[] values=null, Attribute[] attributes=null, Tag[] children=null - ) - { - this(null, namespace, name, values, attributes, children); - } - - this( - Tag parent, string namespace, string name, - Value[] values=null, Attribute[] attributes=null, Tag[] children=null - ) - { - this._namespace = namespace; - this._name = name; - - if(parent) - parent.add(this); - - this.values = values; - this.add(attributes); - this.add(children); - } - - /// Deep-copy this Tag. - /// The clone does $(B $(I not)) have a parent, even if the original does. - Tag clone() - { - auto newTag = new Tag(_namespace, _name, values.dup, allAttributes.clone(), allTags.clone()); - newTag.location = location; - return newTag; - } - - private Attribute[] allAttributes; // In same order as specified in SDL file. - private Tag[] allTags; // In same order as specified in SDL file. - private string[] allNamespaces; // In same order as specified in SDL file. - - private size_t[][string] attributeIndicies; // allAttributes[ attributes[namespace][i] ] - private size_t[][string] tagIndicies; // allTags[ tags[namespace][i] ] - - private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i] - private Tag[][string][string] _tags; // tags[namespace or "*"][name][i] - - /// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag. - /// Returns `this` for chaining. - /// Throws `ValidationException` if trying to add an Attribute or Tag - /// that already has a parent. - Tag add(Value val) - { - values ~= val; - updateId++; - return this; - } - - ///ditto - Tag add(Value[] vals) - { - foreach(val; vals) - add(val); - - return this; - } - - ///ditto - Tag add(Attribute attr) - { - if(attr._parent) - { - throw new ValidationException( - "Attribute is already attached to a parent tag. "~ - "Use Attribute.remove() before adding it to another tag." - ); - } - - if(!allNamespaces.canFind(attr._namespace)) - allNamespaces ~= attr._namespace; - - attr._parent = this; - - allAttributes ~= attr; - attributeIndicies[attr._namespace] ~= allAttributes.length-1; - _attributes[attr._namespace][attr._name] ~= attr; - _attributes["*"] [attr._name] ~= attr; - - updateId++; - return this; - } - - ///ditto - Tag add(Attribute[] attrs) - { - foreach(attr; attrs) - add(attr); - - return this; - } - - ///ditto - Tag add(Tag tag) - { - if(tag._parent) - { - throw new ValidationException( - "Tag is already attached to a parent tag. "~ - "Use Tag.remove() before adding it to another tag." - ); - } - - if(!allNamespaces.canFind(tag._namespace)) - allNamespaces ~= tag._namespace; - - tag._parent = this; - - allTags ~= tag; - tagIndicies[tag._namespace] ~= allTags.length-1; - _tags[tag._namespace][tag._name] ~= tag; - _tags["*"] [tag._name] ~= tag; - - updateId++; - return this; - } - - ///ditto - Tag add(Tag[] tags) - { - foreach(tag; tags) - add(tag); - - return this; - } - - /// Removes `this` from its parent, if any. Returns `this` for chaining. - /// Inefficient ATM, but it works. - Tag remove() - { - if(!_parent) - return this; - - void removeFromGroupedLookup(string ns) - { - // Remove from _parent._tags[ns] - auto sameNameTags = _parent._tags[ns][_name]; - auto targetIndex = sameNameTags.countUntil(this); - _parent._tags[ns][_name].removeIndex(targetIndex); - } - - // Remove from _parent._tags - removeFromGroupedLookup(_namespace); - removeFromGroupedLookup("*"); - - // Remove from _parent.allTags - auto allTagsIndex = _parent.allTags.countUntil(this); - _parent.allTags.removeIndex(allTagsIndex); - - // Remove from _parent.tagIndicies - auto sameNamespaceTags = _parent.tagIndicies[_namespace]; - auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex); - _parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex); - - // Fixup other indicies - foreach(ns, ref nsTagIndicies; _parent.tagIndicies) - foreach(k, ref v; nsTagIndicies) - if(v > allTagsIndex) - v--; - - _parent.removeNamespaceIfEmpty(_namespace); - _parent.updateId++; - _parent = null; - return this; - } - - private void removeNamespaceIfEmpty(string namespace) - { - // If namespace has no attributes, remove it from attributeIndicies/_attributes - if(namespace in attributeIndicies && attributeIndicies[namespace].length == 0) - { - attributeIndicies.remove(namespace); - _attributes.remove(namespace); - } - - // If namespace has no tags, remove it from tagIndicies/_tags - if(namespace in tagIndicies && tagIndicies[namespace].length == 0) - { - tagIndicies.remove(namespace); - _tags.remove(namespace); - } - - // If namespace is now empty, remove it from allNamespaces - if( - namespace !in tagIndicies && - namespace !in attributeIndicies - ) - { - auto allNamespacesIndex = allNamespaces.length - allNamespaces.find(namespace).length; - allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$]; - } - } - - struct NamedMemberRange(T, string membersGrouped) - { - private Tag tag; - private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) - private string name; - private size_t updateId; // Tag's updateId when this range was created. - - this(Tag tag, string namespace, string name, size_t updateId) - { - this.tag = tag; - this.namespace = namespace; - this.name = name; - this.updateId = updateId; - frontIndex = 0; - - if( - tag !is null && - namespace in mixin("tag."~membersGrouped) && - name in mixin("tag."~membersGrouped~"[namespace]") - ) - endIndex = mixin("tag."~membersGrouped~"[namespace][name].length"); - else - endIndex = 0; - } - - invariant() - { - assert( - this.updateId == tag.updateId, - "This range has been invalidated by a change to the tag." - ); - } - - @property bool empty() - { - return tag is null || frontIndex == endIndex; - } - - private size_t frontIndex; - @property T front() - { - return this[0]; - } - void popFront() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - frontIndex++; - } - - private size_t endIndex; // One past the last element - @property T back() - { - return this[$-1]; - } - void popBack() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - endIndex--; - } - - alias length opDollar; - @property size_t length() - { - return endIndex - frontIndex; - } - - @property typeof(this) save() - { - auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId); - r.frontIndex = this.frontIndex; - r.endIndex = this.endIndex; - return r; - } - - typeof(this) opSlice() - { - return save(); - } - - typeof(this) opSlice(size_t start, size_t end) - { - auto r = save(); - r.frontIndex = this.frontIndex + start; - r.endIndex = this.frontIndex + end; - - if( - r.frontIndex > this.endIndex || - r.endIndex > this.endIndex || - r.frontIndex > r.endIndex - ) - throw new DOMRangeException(tag, "Slice out of range"); - - return r; - } - - T opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - return mixin("tag."~membersGrouped~"[namespace][name][frontIndex+index]"); - } - } - - struct MemberRange(T, string allMembers, string memberIndicies, string membersGrouped) - { - private Tag tag; - private string namespace; // "*" indicates "all namespaces" (ok since it's not a valid namespace name) - private bool isMaybe; - private size_t updateId; // Tag's updateId when this range was created. - private size_t initialEndIndex; - - this(Tag tag, string namespace, bool isMaybe) - { - this.tag = tag; - this.namespace = namespace; - this.updateId = tag.updateId; - this.isMaybe = isMaybe; - frontIndex = 0; - - if(tag is null) - endIndex = 0; - else - { - - if(namespace == "*") - initialEndIndex = mixin("tag."~allMembers~".length"); - else if(namespace in mixin("tag."~memberIndicies)) - initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length"); - else - initialEndIndex = 0; - - endIndex = initialEndIndex; - } - } - - invariant() - { - assert( - this.updateId == tag.updateId, - "This range has been invalidated by a change to the tag." - ); - } - - @property bool empty() - { - return tag is null || frontIndex == endIndex; - } - - private size_t frontIndex; - @property T front() - { - return this[0]; - } - void popFront() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - frontIndex++; - } - - private size_t endIndex; // One past the last element - @property T back() - { - return this[$-1]; - } - void popBack() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - endIndex--; - } - - alias length opDollar; - @property size_t length() - { - return endIndex - frontIndex; - } - - @property typeof(this) save() - { - auto r = typeof(this)(this.tag, this.namespace, this.isMaybe); - r.frontIndex = this.frontIndex; - r.endIndex = this.endIndex; - r.initialEndIndex = this.initialEndIndex; - r.updateId = this.updateId; - return r; - } - - typeof(this) opSlice() - { - return save(); - } - - typeof(this) opSlice(size_t start, size_t end) - { - auto r = save(); - r.frontIndex = this.frontIndex + start; - r.endIndex = this.frontIndex + end; - - if( - r.frontIndex > this.endIndex || - r.endIndex > this.endIndex || - r.frontIndex > r.endIndex - ) - throw new DOMRangeException(tag, "Slice out of range"); - - return r; - } - - T opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - if(namespace == "*") - return mixin("tag."~allMembers~"[ frontIndex+index ]"); - else - return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]"); - } - - alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange; - ThisNamedMemberRange opIndex(string name) - { - if(frontIndex != 0 || endIndex != initialEndIndex) - { - throw new DOMRangeException(tag, - "Cannot lookup tags/attributes by name on a subset of a range, "~ - "only across the entire tag. "~ - "Please make sure you haven't called popFront or popBack on this "~ - "range and that you aren't using a slice of the range." - ); - } - - if(!isMaybe && empty) - throw new DOMRangeException(tag, "Range is empty"); - - if(!isMaybe && name !in this) - throw new DOMRangeException(tag, `No such `~T.stringof~` named: "`~name~`"`); - - return ThisNamedMemberRange(tag, namespace, name, updateId); - } - - bool opBinaryRight(string op)(string name) if(op=="in") - { - if(frontIndex != 0 || endIndex != initialEndIndex) - { - throw new DOMRangeException(tag, - "Cannot lookup tags/attributes by name on a subset of a range, "~ - "only across the entire tag. "~ - "Please make sure you haven't called popFront or popBack on this "~ - "range and that you aren't using a slice of the range." - ); - } - - if(tag is null) - return false; - - return - namespace in mixin("tag."~membersGrouped) && - name in mixin("tag."~membersGrouped~"[namespace]") && - mixin("tag."~membersGrouped~"[namespace][name].length") > 0; - } - } - - struct NamespaceRange - { - private Tag tag; - private bool isMaybe; - private size_t updateId; // Tag's updateId when this range was created. - - this(Tag tag, bool isMaybe) - { - this.tag = tag; - this.isMaybe = isMaybe; - this.updateId = tag.updateId; - frontIndex = 0; - endIndex = tag.allNamespaces.length; - } - - invariant() - { - assert( - this.updateId == tag.updateId, - "This range has been invalidated by a change to the tag." - ); - } - - @property bool empty() - { - return frontIndex == endIndex; - } - - private size_t frontIndex; - @property NamespaceAccess front() - { - return this[0]; - } - void popFront() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - frontIndex++; - } - - private size_t endIndex; // One past the last element - @property NamespaceAccess back() - { - return this[$-1]; - } - void popBack() - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - endIndex--; - } - - alias length opDollar; - @property size_t length() - { - return endIndex - frontIndex; - } - - @property NamespaceRange save() - { - auto r = NamespaceRange(this.tag, this.isMaybe); - r.frontIndex = this.frontIndex; - r.endIndex = this.endIndex; - r.updateId = this.updateId; - return r; - } - - typeof(this) opSlice() - { - return save(); - } - - typeof(this) opSlice(size_t start, size_t end) - { - auto r = save(); - r.frontIndex = this.frontIndex + start; - r.endIndex = this.frontIndex + end; - - if( - r.frontIndex > this.endIndex || - r.endIndex > this.endIndex || - r.frontIndex > r.endIndex - ) - throw new DOMRangeException(tag, "Slice out of range"); - - return r; - } - - NamespaceAccess opIndex(size_t index) - { - if(empty) - throw new DOMRangeException(tag, "Range is empty"); - - auto namespace = tag.allNamespaces[frontIndex+index]; - return NamespaceAccess( - namespace, - AttributeRange(tag, namespace, isMaybe), - TagRange(tag, namespace, isMaybe) - ); - } - - NamespaceAccess opIndex(string namespace) - { - if(!isMaybe && empty) - throw new DOMRangeException(tag, "Range is empty"); - - if(!isMaybe && namespace !in this) - throw new DOMRangeException(tag, `No such namespace: "`~namespace~`"`); - - return NamespaceAccess( - namespace, - AttributeRange(tag, namespace, isMaybe), - TagRange(tag, namespace, isMaybe) - ); - } - - /// Inefficient when range is a slice or has used popFront/popBack, but it works. - bool opBinaryRight(string op)(string namespace) if(op=="in") - { - if(frontIndex == 0 && endIndex == tag.allNamespaces.length) - { - return - namespace in tag.attributeIndicies || - namespace in tag.tagIndicies; - } - else - // Slower fallback method - return tag.allNamespaces[frontIndex..endIndex].canFind(namespace); - } - } - - static struct NamespaceAccess - { - string name; - AttributeRange attributes; - TagRange tags; - } - - alias MemberRange!(Attribute, "allAttributes", "attributeIndicies", "_attributes") AttributeRange; - alias MemberRange!(Tag, "allTags", "tagIndicies", "_tags" ) TagRange; - static assert(isRandomAccessRange!AttributeRange); - static assert(isRandomAccessRange!TagRange); - static assert(isRandomAccessRange!NamespaceRange); - - /++ - Access all attributes that don't have a namespace - - Returns a random access range of `Attribute` objects that supports - numeric-indexing, string-indexing, slicing and length. - - Since SDLang allows multiple attributes with the same name, - string-indexing returns a random access range of all attributes - with the given name. - - The string-indexing does $(B $(I not)) support namespace prefixes. - Use `namespace[string]`.`attributes` or `all`.`attributes` for that. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property AttributeRange attributes() - { - return AttributeRange(this, "", false); - } - - /++ - Access all direct-child tags that don't have a namespace. - - Returns a random access range of `Tag` objects that supports - numeric-indexing, string-indexing, slicing and length. - - Since SDLang allows multiple tags with the same name, string-indexing - returns a random access range of all immediate child tags with the - given name. - - The string-indexing does $(B $(I not)) support namespace prefixes. - Use `namespace[string]`.`attributes` or `all`.`attributes` for that. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property TagRange tags() - { - return TagRange(this, "", false); - } - - /++ - Access all namespaces in this tag, and the attributes/tags within them. - - Returns a random access range of `NamespaceAccess` elements that supports - numeric-indexing, string-indexing, slicing and length. - - See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - for a high-level overview (and examples) of how to use this. - +/ - @property NamespaceRange namespaces() - { - return NamespaceRange(this, false); - } - - /// Access all attributes and tags regardless of namespace. - /// - /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - /// for a better understanding (and examples) of how to use this. - @property NamespaceAccess all() - { - // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" - return NamespaceAccess( - "*", - AttributeRange(this, "*", false), - TagRange(this, "*", false) - ); - } - - struct MaybeAccess - { - Tag tag; - - /// Access all attributes that don't have a namespace - @property AttributeRange attributes() - { - return AttributeRange(tag, "", true); - } - - /// Access all direct-child tags that don't have a namespace - @property TagRange tags() - { - return TagRange(tag, "", true); - } - - /// Access all namespaces in this tag, and the attributes/tags within them. - @property NamespaceRange namespaces() - { - return NamespaceRange(tag, true); - } - - /// Access all attributes and tags regardless of namespace. - @property NamespaceAccess all() - { - // "*" isn't a valid namespace name, so we can use it to indicate "all namespaces" - return NamespaceAccess( - "*", - AttributeRange(tag, "*", true), - TagRange(tag, "*", true) - ); - } - } - - /// Access `attributes`, `tags`, `namespaces` and `all` like normal, - /// except that looking up a non-existant name/namespace with - /// opIndex(string) results in an empty array instead of - /// a thrown `sdlang.exception.DOMRangeException`. - /// - /// See $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/HOWTO.md#tag-and-attribute-api-summary, API Overview) - /// for a more information (and examples) of how to use this. - @property MaybeAccess maybe() - { - return MaybeAccess(this); - } - - // Internal implementations for the get/expect functions further below: - - private Tag getTagImpl(FullName tagFullName, Tag defaultValue=null, bool useDefaultValue=true) - { - auto tagNS = tagFullName.namespace; - auto tagName = tagFullName.name; - - // Can find namespace? - if(tagNS !in _tags) - { - if(useDefaultValue) - return defaultValue; - else - throw new TagNotFoundException(this, tagFullName, "No tags found in namespace '"~namespace~"'"); - } - - // Can find tag in namespace? - if(tagName !in _tags[tagNS] || _tags[tagNS][tagName].length == 0) - { - if(useDefaultValue) - return defaultValue; - else - throw new TagNotFoundException(this, tagFullName, "Can't find tag '"~tagFullName.toString()~"'"); - } - - // Return last matching tag found - return _tags[tagNS][tagName][$-1]; - } - - private T getValueImpl(T)(T defaultValue, bool useDefaultValue=true) - if(isValueType!T) - { - // Find value - foreach(value; this.values) - { - if(value.type == typeid(T)) - return value.get!T(); - } - - // No value of type T found - if(useDefaultValue) - return defaultValue; - else - { - throw new ValueNotFoundException( - this, - FullName(this.namespace, this.name), - typeid(T), - "No value of type "~T.stringof~" found." - ); - } - } - - private T getAttributeImpl(T)(FullName attrFullName, T defaultValue, bool useDefaultValue=true) - if(isValueType!T) - { - auto attrNS = attrFullName.namespace; - auto attrName = attrFullName.name; - - // Can find namespace and attribute name? - if(attrNS !in this._attributes || attrName !in this._attributes[attrNS]) - { - if(useDefaultValue) - return defaultValue; - else - { - throw new AttributeNotFoundException( - this, this.getFullName(), attrFullName, typeid(T), - "Can't find attribute '"~FullName.combine(attrNS, attrName)~"'" - ); - } - } - - // Find value with chosen type - foreach(attr; this._attributes[attrNS][attrName]) - { - if(attr.value.type == typeid(T)) - return attr.value.get!T(); - } - - // Chosen type not found - if(useDefaultValue) - return defaultValue; - else - { - throw new AttributeNotFoundException( - this, this.getFullName(), attrFullName, typeid(T), - "Can't find attribute '"~FullName.combine(attrNS, attrName)~"' of type "~T.stringof - ); - } - } - - // High-level interfaces for get/expect funtions: - - /++ - Lookup a child tag by name. Returns null if not found. - - Useful if you only expect one, and only one, child tag of a given name. - Only looks for immediate child tags of `this`, doesn't search recursively. - - If you expect multiple tags by the same name and want to get them all, - use `maybe`.`tags[string]` instead. - - The name can optionally include a namespace, as in `"namespace:name"`. - Or, you can search all namespaces using `"*:name"`. Use an empty string - to search for anonymous tags, or `"namespace:"` for anonymous tags inside - a namespace. Wildcard searching is only supported for namespaces, not names. - Use `maybe`.`tags[0]` if you don't care about the name. - - If there are multiple tags by the chosen name, the $(B $(I last tag)) will - always be chosen. That is, this function considers later tags with the - same name to override previous ones. - - If the tag cannot be found, and you provides a default value, the default - value is returned. Otherwise null is returned. If you'd prefer an - exception thrown, use `expectTag` instead. - +/ - Tag getTag(string fullTagName, Tag defaultValue=null) - { - auto parsedName = FullName.parse(fullTagName); - parsedName.ensureNoWildcardName( - "Instead, use 'Tag.maybe.tags[0]', 'Tag.maybe.all.tags[0]' or 'Tag.maybe.namespace[ns].tags[0]'." - ); - return getTagImpl(parsedName, defaultValue); - } - - /// - @("Tag.getTag") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 - foo 2 // getTag considers this to override the first foo - - ns1:foo 3 - ns1:foo 4 // getTag considers this to override the first ns1:foo - ns2:foo 33 - ns2:foo 44 // getTag considers this to override the first ns2:foo - `); - assert( root.getTag("foo" ).values[0].get!int() == 2 ); - assert( root.getTag("ns1:foo").values[0].get!int() == 4 ); - assert( root.getTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces - - // Not found - // If you'd prefer an exception, use `expectTag` instead. - assert( root.getTag("doesnt-exist") is null ); - - // Default value - auto foo = root.getTag("foo"); - assert( root.getTag("doesnt-exist", foo) is foo ); - } - - /++ - Lookup a child tag by name. Throws if not found. - - Useful if you only expect one, and only one, child tag of a given name. - Only looks for immediate child tags of `this`, doesn't search recursively. - - If you expect multiple tags by the same name and want to get them all, - use `tags[string]` instead. - - The name can optionally include a namespace, as in `"namespace:name"`. - Or, you can search all namespaces using `"*:name"`. Use an empty string - to search for anonymous tags, or `"namespace:"` for anonymous tags inside - a namespace. Wildcard searching is only supported for namespaces, not names. - Use `tags[0]` if you don't care about the name. - - If there are multiple tags by the chosen name, the $(B $(I last tag)) will - always be chosen. That is, this function considers later tags with the - same name to override previous ones. - - If no such tag is found, an `sdlang.exception.TagNotFoundException` will - be thrown. If you'd rather receive a default value, use `getTag` instead. - +/ - Tag expectTag(string fullTagName) - { - auto parsedName = FullName.parse(fullTagName); - parsedName.ensureNoWildcardName( - "Instead, use 'Tag.tags[0]', 'Tag.all.tags[0]' or 'Tag.namespace[ns].tags[0]'." - ); - return getTagImpl(parsedName, null, false); - } - - /// - @("Tag.expectTag") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 - foo 2 // expectTag considers this to override the first foo - - ns1:foo 3 - ns1:foo 4 // expectTag considers this to override the first ns1:foo - ns2:foo 33 - ns2:foo 44 // expectTag considers this to override the first ns2:foo - `); - assert( root.expectTag("foo" ).values[0].get!int() == 2 ); - assert( root.expectTag("ns1:foo").values[0].get!int() == 4 ); - assert( root.expectTag("*:foo" ).values[0].get!int() == 44 ); // Search all namespaces - - // Not found - // If you'd rather receive a default value than an exception, use `getTag` instead. - assertThrown!TagNotFoundException( root.expectTag("doesnt-exist") ); - } - - /++ - Retrieve a value of type T from `this` tag. Returns a default value if not found. - - Useful if you only expect one value of type T from this tag. Only looks for - values of `this` tag, it does not search child tags. If you wish to search - for a value in a child tag (for example, if this current tag is a root tag), - try `getTagValue`. - - If you want to get more than one value from this tag, use `values` instead. - - If this tag has multiple values, the $(B $(I first)) value matching the - requested type will be returned. Ie, Extra values in the tag are ignored. - - You may provide a default value to be returned in case no value of - the requested type can be found. If you don't provide a default value, - `T.init` will be used. - - If you'd rather an exception be thrown when a value cannot be found, - use `expectValue` instead. - +/ - T getValue(T)(T defaultValue = T.init) if(isValueType!T) - { - return getValueImpl!T(defaultValue, true); - } - - /// - @("Tag.getValue") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 true 2 false - `); - auto foo = root.getTag("foo"); - assert( foo.getValue!int() == 1 ); - assert( foo.getValue!bool() == true ); - - // Value found, default value ignored. - assert( foo.getValue!int(999) == 1 ); - - // No strings found - // If you'd prefer an exception, use `expectValue` instead. - assert( foo.getValue!string("Default") == "Default" ); - assert( foo.getValue!string() is null ); - - // No floats found - assert( foo.getValue!float(99.9).approxEqual(99.9) ); - assert( foo.getValue!float().isNaN() ); - } - - /++ - Retrieve a value of type T from `this` tag. Throws if not found. - - Useful if you only expect one value of type T from this tag. Only looks - for values of `this` tag, it does not search child tags. If you wish to - search for a value in a child tag (for example, if this current tag is a - root tag), try `expectTagValue`. - - If you want to get more than one value from this tag, use `values` instead. - - If this tag has multiple values, the $(B $(I first)) value matching the - requested type will be returned. Ie, Extra values in the tag are ignored. - - An `sdlang.exception.ValueNotFoundException` will be thrown if no value of - the requested type can be found. If you'd rather receive a default value, - use `getValue` instead. - +/ - T expectValue(T)() if(isValueType!T) - { - return getValueImpl!T(T.init, false); - } - - /// - @("Tag.expectValue") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 true 2 false - `); - auto foo = root.getTag("foo"); - assert( foo.expectValue!int() == 1 ); - assert( foo.expectValue!bool() == true ); - - // No strings or floats found - // If you'd rather receive a default value than an exception, use `getValue` instead. - assertThrown!ValueNotFoundException( foo.expectValue!string() ); - assertThrown!ValueNotFoundException( foo.expectValue!float() ); - } - - /++ - Lookup a child tag by name, and retrieve a value of type T from it. - Returns a default value if not found. - - Useful if you only expect one value of type T from a given tag. Only looks - for immediate child tags of `this`, doesn't search recursively. - - This is a shortcut for `getTag().getValue()`, except if the tag isn't found, - then instead of a null reference error, it will return the requested - `defaultValue` (or T.init by default). - +/ - T getTagValue(T)(string fullTagName, T defaultValue = T.init) if(isValueType!T) - { - auto tag = getTag(fullTagName); - if(!tag) - return defaultValue; - - return tag.getValue!T(defaultValue); - } - - /// - @("Tag.getTagValue") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // getTagValue considers this to override the first foo - - bar "hi" - bar 379 // getTagValue considers this to override the first bar - `); - assert( root.getTagValue!int("foo") == 3 ); - assert( root.getTagValue!string("foo") == "c" ); - - // Value found, default value ignored. - assert( root.getTagValue!int("foo", 999) == 3 ); - - // Tag not found - // If you'd prefer an exception, use `expectTagValue` instead. - assert( root.getTagValue!int("doesnt-exist", 999) == 999 ); - assert( root.getTagValue!int("doesnt-exist") == 0 ); - - // The last "bar" tag doesn't have an int (only the first "bar" tag does) - assert( root.getTagValue!string("bar", "Default") == "Default" ); - assert( root.getTagValue!string("bar") is null ); - - // Using namespaces: - root = parseSource(` - ns1:foo 1 "a" 2 "b" - ns1:foo 3 "c" 4 "d" - ns2:foo 11 "aa" 22 "bb" - ns2:foo 33 "cc" 44 "dd" - - ns1:bar "hi" - ns1:bar 379 // getTagValue considers this to override the first bar - `); - assert( root.getTagValue!int("ns1:foo") == 3 ); - assert( root.getTagValue!int("*:foo" ) == 33 ); // Search all namespaces - - assert( root.getTagValue!string("ns1:foo") == "c" ); - assert( root.getTagValue!string("*:foo" ) == "cc" ); // Search all namespaces - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assert( root.getTagValue!string("*:bar", "Default") == "Default" ); - assert( root.getTagValue!string("*:bar") is null ); - } - - /++ - Lookup a child tag by name, and retrieve a value of type T from it. - Throws if not found, - - Useful if you only expect one value of type T from a given tag. Only - looks for immediate child tags of `this`, doesn't search recursively. - - This is a shortcut for `expectTag().expectValue()`. - +/ - T expectTagValue(T)(string fullTagName) if(isValueType!T) - { - return expectTag(fullTagName).expectValue!T(); - } - - /// - @("Tag.expectTagValue") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // expectTagValue considers this to override the first foo - - bar "hi" - bar 379 // expectTagValue considers this to override the first bar - `); - assert( root.expectTagValue!int("foo") == 3 ); - assert( root.expectTagValue!string("foo") == "c" ); - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - // If you'd rather receive a default value than an exception, use `getTagValue` instead. - assertThrown!ValueNotFoundException( root.expectTagValue!string("bar") ); - - // Tag not found - assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist") ); - - // Using namespaces: - root = parseSource(` - ns1:foo 1 "a" 2 "b" - ns1:foo 3 "c" 4 "d" - ns2:foo 11 "aa" 22 "bb" - ns2:foo 33 "cc" 44 "dd" - - ns1:bar "hi" - ns1:bar 379 // expectTagValue considers this to override the first bar - `); - assert( root.expectTagValue!int("ns1:foo") == 3 ); - assert( root.expectTagValue!int("*:foo" ) == 33 ); // Search all namespaces - - assert( root.expectTagValue!string("ns1:foo") == "c" ); - assert( root.expectTagValue!string("*:foo" ) == "cc" ); // Search all namespaces - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assertThrown!ValueNotFoundException( root.expectTagValue!string("*:bar") ); - - // Namespace not found - assertThrown!TagNotFoundException( root.expectTagValue!int("doesnt-exist:bar") ); - } - - /++ - Lookup an attribute of `this` tag by name, and retrieve a value of type T - from it. Returns a default value if not found. - - Useful if you only expect one attribute of the given name and type. - - Only looks for attributes of `this` tag, it does not search child tags. - If you wish to search for a value in a child tag (for example, if this - current tag is a root tag), try `getTagAttribute`. - - If you expect multiple attributes by the same name and want to get them all, - use `maybe`.`attributes[string]` instead. - - The attribute name can optionally include a namespace, as in - `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. - (Note that unlike tags. attributes can't be anonymous - that's what - values are.) Wildcard searching is only supported for namespaces, not names. - Use `maybe`.`attributes[0]` if you don't care about the name. - - If this tag has multiple attributes, the $(B $(I first)) attribute - matching the requested name and type will be returned. Ie, Extra - attributes in the tag are ignored. - - You may provide a default value to be returned in case no attribute of - the requested name and type can be found. If you don't provide a default - value, `T.init` will be used. - - If you'd rather an exception be thrown when an attribute cannot be found, - use `expectAttribute` instead. - +/ - T getAttribute(T)(string fullAttributeName, T defaultValue = T.init) if(isValueType!T) - { - auto parsedName = FullName.parse(fullAttributeName); - parsedName.ensureNoWildcardName( - "Instead, use 'Attribute.maybe.tags[0]', 'Attribute.maybe.all.tags[0]' or 'Attribute.maybe.namespace[ns].tags[0]'." - ); - return getAttributeImpl!T(parsedName, defaultValue); - } - - /// - @("Tag.getAttribute") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo z=0 X=1 X=true X=2 X=false - `); - auto foo = root.getTag("foo"); - assert( foo.getAttribute!int("X") == 1 ); - assert( foo.getAttribute!bool("X") == true ); - - // Value found, default value ignored. - assert( foo.getAttribute!int("X", 999) == 1 ); - - // Attribute name not found - // If you'd prefer an exception, use `expectValue` instead. - assert( foo.getAttribute!int("doesnt-exist", 999) == 999 ); - assert( foo.getAttribute!int("doesnt-exist") == 0 ); - - // No strings found - assert( foo.getAttribute!string("X", "Default") == "Default" ); - assert( foo.getAttribute!string("X") is null ); - - // No floats found - assert( foo.getAttribute!float("X", 99.9).approxEqual(99.9) ); - assert( foo.getAttribute!float("X").isNaN() ); - - - // Using namespaces: - root = parseSource(` - foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 - `); - foo = root.getTag("foo"); - assert( foo.getAttribute!int("ns2:X") == 3 ); - assert( foo.getAttribute!int("*:X") == 1 ); // Search all namespaces - - // Namespace not found - assert( foo.getAttribute!int("doesnt-exist:X", 999) == 999 ); - - // No attribute X is in the default namespace - assert( foo.getAttribute!int("X", 999) == 999 ); - - // Attribute name not found - assert( foo.getAttribute!int("ns1:doesnt-exist", 999) == 999 ); - } - - /++ - Lookup an attribute of `this` tag by name, and retrieve a value of type T - from it. Throws if not found. - - Useful if you only expect one attribute of the given name and type. - - Only looks for attributes of `this` tag, it does not search child tags. - If you wish to search for a value in a child tag (for example, if this - current tag is a root tag), try `expectTagAttribute`. - - If you expect multiple attributes by the same name and want to get them all, - use `attributes[string]` instead. - - The attribute name can optionally include a namespace, as in - `"namespace:name"`. Or, you can search all namespaces using `"*:name"`. - (Note that unlike tags. attributes can't be anonymous - that's what - values are.) Wildcard searching is only supported for namespaces, not names. - Use `attributes[0]` if you don't care about the name. - - If this tag has multiple attributes, the $(B $(I first)) attribute - matching the requested name and type will be returned. Ie, Extra - attributes in the tag are ignored. - - An `sdlang.exception.AttributeNotFoundException` will be thrown if no - value of the requested type can be found. If you'd rather receive a - default value, use `getAttribute` instead. - +/ - T expectAttribute(T)(string fullAttributeName) if(isValueType!T) - { - auto parsedName = FullName.parse(fullAttributeName); - parsedName.ensureNoWildcardName( - "Instead, use 'Attribute.tags[0]', 'Attribute.all.tags[0]' or 'Attribute.namespace[ns].tags[0]'." - ); - return getAttributeImpl!T(parsedName, T.init, false); - } - - /// - @("Tag.expectAttribute") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo z=0 X=1 X=true X=2 X=false - `); - auto foo = root.getTag("foo"); - assert( foo.expectAttribute!int("X") == 1 ); - assert( foo.expectAttribute!bool("X") == true ); - - // Attribute name not found - // If you'd rather receive a default value than an exception, use `getAttribute` instead. - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist") ); - - // No strings found - assertThrown!AttributeNotFoundException( foo.expectAttribute!string("X") ); - - // No floats found - assertThrown!AttributeNotFoundException( foo.expectAttribute!float("X") ); - - - // Using namespaces: - root = parseSource(` - foo ns1:z=0 ns1:X=1 ns1:X=2 ns2:X=3 ns2:X=4 - `); - foo = root.getTag("foo"); - assert( foo.expectAttribute!int("ns2:X") == 3 ); - assert( foo.expectAttribute!int("*:X") == 1 ); // Search all namespaces - - // Namespace not found - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("doesnt-exist:X") ); - - // No attribute X is in the default namespace - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("X") ); - - // Attribute name not found - assertThrown!AttributeNotFoundException( foo.expectAttribute!int("ns1:doesnt-exist") ); - } - - /++ - Lookup a child tag and attribute by name, and retrieve a value of type T - from it. Returns a default value if not found. - - Useful if you only expect one attribute of type T from given - the tag and attribute names. Only looks for immediate child tags of - `this`, doesn't search recursively. - - This is a shortcut for `getTag().getAttribute()`, except if the tag isn't - found, then instead of a null reference error, it will return the requested - `defaultValue` (or T.init by default). - +/ - T getTagAttribute(T)(string fullTagName, string fullAttributeName, T defaultValue = T.init) if(isValueType!T) - { - auto tag = getTag(fullTagName); - if(!tag) - return defaultValue; - - return tag.getAttribute!T(fullAttributeName, defaultValue); - } - - /// - @("Tag.getTagAttribute") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X="a" X=2 X="b" - foo X=3 X="c" X=4 X="d" // getTagAttribute considers this to override the first foo - - bar X="hi" - bar X=379 // getTagAttribute considers this to override the first bar - `); - assert( root.getTagAttribute!int("foo", "X") == 3 ); - assert( root.getTagAttribute!string("foo", "X") == "c" ); - - // Value found, default value ignored. - assert( root.getTagAttribute!int("foo", "X", 999) == 3 ); - - // Tag not found - // If you'd prefer an exception, use `expectTagAttribute` instead of `getTagAttribute` - assert( root.getTagAttribute!int("doesnt-exist", "X", 999) == 999 ); - assert( root.getTagAttribute!int("doesnt-exist", "X") == 0 ); - assert( root.getTagAttribute!int("foo", "doesnt-exist", 999) == 999 ); - assert( root.getTagAttribute!int("foo", "doesnt-exist") == 0 ); - - // The last "bar" tag doesn't have a string (only the first "bar" tag does) - assert( root.getTagAttribute!string("bar", "X", "Default") == "Default" ); - assert( root.getTagAttribute!string("bar", "X") is null ); - - - // Using namespaces: - root = parseSource(` - ns1:foo X=1 X="a" X=2 X="b" - ns1:foo X=3 X="c" X=4 X="d" - ns2:foo X=11 X="aa" X=22 X="bb" - ns2:foo X=33 X="cc" X=44 X="dd" - - ns1:bar attrNS:X="hi" - ns1:bar attrNS:X=379 // getTagAttribute considers this to override the first bar - `); - assert( root.getTagAttribute!int("ns1:foo", "X") == 3 ); - assert( root.getTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces - - assert( root.getTagAttribute!string("ns1:foo", "X") == "c" ); - assert( root.getTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces - - // bar's attribute X is't in the default namespace - assert( root.getTagAttribute!int("*:bar", "X", 999) == 999 ); - assert( root.getTagAttribute!int("*:bar", "X") == 0 ); - - // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) - assert( root.getTagAttribute!string("*:bar", "attrNS:X", "Default") == "Default" ); - assert( root.getTagAttribute!string("*:bar", "attrNS:X") is null); - } - - /++ - Lookup a child tag and attribute by name, and retrieve a value of type T - from it. Throws if not found. - - Useful if you only expect one attribute of type T from given - the tag and attribute names. Only looks for immediate child tags of - `this`, doesn't search recursively. - - This is a shortcut for `expectTag().expectAttribute()`. - +/ - T expectTagAttribute(T)(string fullTagName, string fullAttributeName) if(isValueType!T) - { - return expectTag(fullTagName).expectAttribute!T(fullAttributeName); - } - - /// - @("Tag.expectTagAttribute") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X="a" X=2 X="b" - foo X=3 X="c" X=4 X="d" // expectTagAttribute considers this to override the first foo - - bar X="hi" - bar X=379 // expectTagAttribute considers this to override the first bar - `); - assert( root.expectTagAttribute!int("foo", "X") == 3 ); - assert( root.expectTagAttribute!string("foo", "X") == "c" ); - - // The last "bar" tag doesn't have an int attribute named "X" (only the first "bar" tag does) - // If you'd rather receive a default value than an exception, use `getAttribute` instead. - assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("bar", "X") ); - - // Tag not found - assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist", "X") ); - - // Using namespaces: - root = parseSource(` - ns1:foo X=1 X="a" X=2 X="b" - ns1:foo X=3 X="c" X=4 X="d" - ns2:foo X=11 X="aa" X=22 X="bb" - ns2:foo X=33 X="cc" X=44 X="dd" - - ns1:bar attrNS:X="hi" - ns1:bar attrNS:X=379 // expectTagAttribute considers this to override the first bar - `); - assert( root.expectTagAttribute!int("ns1:foo", "X") == 3 ); - assert( root.expectTagAttribute!int("*:foo", "X") == 33 ); // Search all namespaces - - assert( root.expectTagAttribute!string("ns1:foo", "X") == "c" ); - assert( root.expectTagAttribute!string("*:foo", "X") == "cc" ); // Search all namespaces - - // bar's attribute X is't in the default namespace - assertThrown!AttributeNotFoundException( root.expectTagAttribute!int("*:bar", "X") ); - - // The last "bar" tag's "attrNS:X" attribute doesn't have a string (only the first "bar" tag does) - assertThrown!AttributeNotFoundException( root.expectTagAttribute!string("*:bar", "attrNS:X") ); - - // Tag's namespace not found - assertThrown!TagNotFoundException( root.expectTagAttribute!int("doesnt-exist:bar", "attrNS:X") ); - } - - /++ - Lookup a child tag by name, and retrieve all values from it. - - This just like using `getTag()`.`values`, except if the tag isn't found, - it safely returns null (or an optional array of default values) instead of - a dereferencing null error. - - Note that, unlike `getValue`, this doesn't discriminate by the value's - type. It simply returns all values of a single tag as a `Value[]`. - - If you'd prefer an exception thrown when the tag isn't found, use - `expectTag`.`values` instead. - +/ - Value[] getTagValues(string fullTagName, Value[] defaultValues = null) - { - auto tag = getTag(fullTagName); - if(tag) - return tag.values; - else - return defaultValues; - } - - /// - @("getTagValues") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo 1 "a" 2 "b" - foo 3 "c" 4 "d" // getTagValues considers this to override the first foo - `); - assert( root.getTagValues("foo") == [Value(3), Value("c"), Value(4), Value("d")] ); - - // Tag not found - // If you'd prefer an exception, use `expectTag.values` instead. - assert( root.getTagValues("doesnt-exist") is null ); - assert( root.getTagValues("doesnt-exist", [ Value(999), Value("Not found") ]) == - [ Value(999), Value("Not found") ] ); - } - - /++ - Lookup a child tag by name, and retrieve all attributes in a chosen - (or default) namespace from it. - - This just like using `getTag()`.`attributes` (or - `getTag()`.`namespace[...]`.`attributes`, or `getTag()`.`all`.`attributes`), - except if the tag isn't found, it safely returns an empty range instead - of a dereferencing null error. - - If provided, the `attributeNamespace` parameter can be either the name of - a namespace, or an empty string for the default namespace (the default), - or `"*"` to retreive attributes from all namespaces. - - Note that, unlike `getAttributes`, this doesn't discriminate by the - value's type. It simply returns the usual `attributes` range. - - If you'd prefer an exception thrown when the tag isn't found, use - `expectTag`.`attributes` instead. - +/ - auto getTagAttributes(string fullTagName, string attributeNamespace = null) - { - auto tag = getTag(fullTagName); - if(tag) - { - if(attributeNamespace && attributeNamespace in tag.namespaces) - return tag.namespaces[attributeNamespace].attributes; - else if(attributeNamespace == "*") - return tag.all.attributes; - else - return tag.attributes; - } - - return AttributeRange(null, null, false); - } - - /// - @("getTagAttributes") - unittest - { - import std.exception; - import sdlang.parser; - - auto root = parseSource(` - foo X=1 X=2 - - // getTagAttributes considers this to override the first foo - foo X1=3 X2="c" namespace:bar=7 X3=4 X4="d" - `); - - auto fooAttrs = root.getTagAttributes("foo"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 4 ); - assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); - assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); - assert( fooAttrs[2].name == "X3" && fooAttrs[2].value == Value(4) ); - assert( fooAttrs[3].name == "X4" && fooAttrs[3].value == Value("d") ); - - fooAttrs = root.getTagAttributes("foo", "namespace"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 1 ); - assert( fooAttrs[0].name == "bar" && fooAttrs[0].value == Value(7) ); - - fooAttrs = root.getTagAttributes("foo", "*"); - assert( !fooAttrs.empty ); - assert( fooAttrs.length == 5 ); - assert( fooAttrs[0].name == "X1" && fooAttrs[0].value == Value(3) ); - assert( fooAttrs[1].name == "X2" && fooAttrs[1].value == Value("c") ); - assert( fooAttrs[2].name == "bar" && fooAttrs[2].value == Value(7) ); - assert( fooAttrs[3].name == "X3" && fooAttrs[3].value == Value(4) ); - assert( fooAttrs[4].name == "X4" && fooAttrs[4].value == Value("d") ); - - // Tag not found - // If you'd prefer an exception, use `expectTag.attributes` instead. - assert( root.getTagValues("doesnt-exist").empty ); - } - - @("*: Disallow wildcards for names") - unittest - { - import std.exception; - import std.math; - import sdlang.parser; - - auto root = parseSource(` - foo 1 X=2 - ns:foo 3 ns:X=4 - `); - auto foo = root.getTag("foo"); - auto nsfoo = root.getTag("ns:foo"); - - // Sanity check - assert( foo !is null ); - assert( foo.name == "foo" ); - assert( foo.namespace == "" ); - - assert( nsfoo !is null ); - assert( nsfoo.name == "foo" ); - assert( nsfoo.namespace == "ns" ); - - assert( foo.getValue !int() == 1 ); - assert( foo.expectValue !int() == 1 ); - assert( nsfoo.getValue !int() == 3 ); - assert( nsfoo.expectValue!int() == 3 ); - - assert( root.getTagValue !int("foo") == 1 ); - assert( root.expectTagValue!int("foo") == 1 ); - assert( root.getTagValue !int("ns:foo") == 3 ); - assert( root.expectTagValue!int("ns:foo") == 3 ); - - assert( foo.getAttribute !int("X") == 2 ); - assert( foo.expectAttribute !int("X") == 2 ); - assert( nsfoo.getAttribute !int("ns:X") == 4 ); - assert( nsfoo.expectAttribute!int("ns:X") == 4 ); - - assert( root.getTagAttribute !int("foo", "X") == 2 ); - assert( root.expectTagAttribute!int("foo", "X") == 2 ); - assert( root.getTagAttribute !int("ns:foo", "ns:X") == 4 ); - assert( root.expectTagAttribute!int("ns:foo", "ns:X") == 4 ); - - // No namespace - assertThrown!ArgumentException( root.getTag ("*") ); - assertThrown!ArgumentException( root.expectTag("*") ); - - assertThrown!ArgumentException( root.getTagValue !int("*") ); - assertThrown!ArgumentException( root.expectTagValue!int("*") ); - - assertThrown!ArgumentException( foo.getAttribute !int("*") ); - assertThrown!ArgumentException( foo.expectAttribute !int("*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*", "X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*", "X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("foo", "*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("foo", "*") ); - - // With namespace - assertThrown!ArgumentException( root.getTag ("ns:*") ); - assertThrown!ArgumentException( root.expectTag("ns:*") ); - - assertThrown!ArgumentException( root.getTagValue !int("ns:*") ); - assertThrown!ArgumentException( root.expectTagValue!int("ns:*") ); - - assertThrown!ArgumentException( nsfoo.getAttribute !int("ns:*") ); - assertThrown!ArgumentException( nsfoo.expectAttribute !int("ns:*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("ns:*", "ns:X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("ns:*", "ns:X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("ns:foo", "ns:*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("ns:foo", "ns:*") ); - - // With wildcard namespace - assertThrown!ArgumentException( root.getTag ("*:*") ); - assertThrown!ArgumentException( root.expectTag("*:*") ); - - assertThrown!ArgumentException( root.getTagValue !int("*:*") ); - assertThrown!ArgumentException( root.expectTagValue!int("*:*") ); - - assertThrown!ArgumentException( nsfoo.getAttribute !int("*:*") ); - assertThrown!ArgumentException( nsfoo.expectAttribute !int("*:*") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*:*", "*:X") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*:*", "*:X") ); - assertThrown!ArgumentException( root.getTagAttribute !int("*:foo", "*:*") ); - assertThrown!ArgumentException( root.expectTagAttribute!int("*:foo", "*:*") ); - } - - override bool opEquals(Object o) - { - auto t = cast(Tag)o; - if(!t) - return false; - - if(_namespace != t._namespace || _name != t._name) - return false; - - if( - values .length != t.values .length || - allAttributes .length != t.allAttributes.length || - allNamespaces .length != t.allNamespaces.length || - allTags .length != t.allTags .length - ) - return false; - - if(values != t.values) - return false; - - if(allNamespaces != t.allNamespaces) - return false; - - if(allAttributes != t.allAttributes) - return false; - - // Ok because cycles are not allowed - //TODO: Actually check for or prevent cycles. - return allTags == t.allTags; - } - - /// Treats `this` as the root tag. Note that root tags cannot have - /// values or attributes, and cannot be part of a namespace. - /// If this isn't a valid root tag, `sdlang.exception.ValidationException` - /// will be thrown. - string toSDLDocument()(string indent="\t", int indentLevel=0) - { - Appender!string sink; - toSDLDocument(sink, indent, indentLevel); - return sink.data; - } - - ///ditto - void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) - if(isOutputRange!(Sink,char)) - { - if(values.length > 0) - throw new ValidationException("Root tags cannot have any values, only child tags."); - - if(allAttributes.length > 0) - throw new ValidationException("Root tags cannot have any attributes, only child tags."); - - if(_namespace != "") - throw new ValidationException("Root tags cannot have a namespace."); - - foreach(tag; allTags) - tag.toSDLString(sink, indent, indentLevel); - } - - /// Output this entire tag in SDL format. Does $(B $(I not)) treat `this` as - /// a root tag. If you intend this to be the root of a standard SDL - /// document, use `toSDLDocument` instead. - string toSDLString()(string indent="\t", int indentLevel=0) - { - Appender!string sink; - toSDLString(sink, indent, indentLevel); - return sink.data; - } - - ///ditto - void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) - if(isOutputRange!(Sink,char)) - { - if(_name == "" && values.length == 0) - throw new ValidationException("Anonymous tags must have at least one value."); - - if(_name == "" && _namespace != "") - throw new ValidationException("Anonymous tags cannot have a namespace."); - - // Indent - foreach(i; 0..indentLevel) - sink.put(indent); - - // Name - if(_namespace != "") - { - sink.put(_namespace); - sink.put(':'); - } - sink.put(_name); - - // Values - foreach(i, v; values) - { - // Omit the first space for anonymous tags - if(_name != "" || i > 0) - sink.put(' '); - - v.toSDLString(sink); - } - - // Attributes - foreach(attr; allAttributes) - { - sink.put(' '); - attr.toSDLString(sink); - } - - // Child tags - bool foundChild=false; - foreach(tag; allTags) - { - if(!foundChild) - { - sink.put(" {\n"); - foundChild = true; - } - - tag.toSDLString(sink, indent, indentLevel+1); - } - if(foundChild) - { - foreach(i; 0..indentLevel) - sink.put(indent); - - sink.put("}\n"); - } - else - sink.put("\n"); - } - - /// Outputs full information on the tag. - string toDebugString() - { - import std.algorithm : sort; - - Appender!string buf; - - buf.put("\n"); - buf.put("Tag "); - if(_namespace != "") - { - buf.put("["); - buf.put(_namespace); - buf.put("]"); - } - buf.put("'%s':\n".format(_name)); - - // Values - foreach(val; values) - buf.put(" (%s): %s\n".format(.toString(val.type), val)); - - // Attributes - foreach(attrNamespace; _attributes.keys.sort()) - if(attrNamespace != "*") - foreach(attrName; _attributes[attrNamespace].keys.sort()) - foreach(attr; _attributes[attrNamespace][attrName]) - { - string namespaceStr; - if(attr._namespace != "") - namespaceStr = "["~attr._namespace~"]"; - - buf.put( - " %s%s(%s): %s\n".format( - namespaceStr, attr._name, .toString(attr.value.type), attr.value - ) - ); - } - - // Children - foreach(tagNamespace; _tags.keys.sort()) - if(tagNamespace != "*") - foreach(tagName; _tags[tagNamespace].keys.sort()) - foreach(tag; _tags[tagNamespace][tagName]) - buf.put( tag.toDebugString().replace("\n", "\n ") ); - - return buf.data; - } -} - -version(unittest) -{ - private void testRandomAccessRange(R, E)(R range, E[] expected, bool function(E, E) equals=null) - { - static assert(isRandomAccessRange!R); - static assert(is(ElementType!R == E)); - static assert(hasLength!R); - static assert(!isInfinite!R); - - assert(range.length == expected.length); - if(range.length == 0) - { - assert(range.empty); - return; - } - - static bool defaultEquals(E e1, E e2) - { - return e1 == e2; - } - if(equals is null) - equals = &defaultEquals; - - assert(equals(range.front, expected[0])); - assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' - assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' - - assert(equals(range.back, expected[$-1])); - assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' - assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' - - // Forward iteration - auto original = range.save; - auto r2 = range.save; - foreach(i; 0..expected.length) - { - //trace("Forward iteration: ", i); - - // Test length/empty - assert(range.length == expected.length - i); - assert(range.length == r2.length); - assert(!range.empty); - assert(!r2.empty); - - // Test front - assert(equals(range.front, expected[i])); - assert(equals(range.front, r2.front)); - - // Test back - assert(equals(range.back, expected[$-1])); - assert(equals(range.back, r2.back)); - - // Test opIndex(0) - assert(equals(range[0], expected[i])); - assert(equals(range[0], r2[0])); - - // Test opIndex($-1) - assert(equals(range[$-1], expected[$-1])); - assert(equals(range[$-1], r2[$-1])); - - // Test popFront - range.popFront(); - assert(range.length == r2.length - 1); - r2.popFront(); - assert(range.length == r2.length); - } - assert(range.empty); - assert(r2.empty); - assert(original.length == expected.length); - - // Backwards iteration - range = original.save; - r2 = original.save; - foreach(i; iota(0, expected.length).retro()) - { - //trace("Backwards iteration: ", i); - - // Test length/empty - assert(range.length == i+1); - assert(range.length == r2.length); - assert(!range.empty); - assert(!r2.empty); - - // Test front - assert(equals(range.front, expected[0])); - assert(equals(range.front, r2.front)); - - // Test back - assert(equals(range.back, expected[i])); - assert(equals(range.back, r2.back)); - - // Test opIndex(0) - assert(equals(range[0], expected[0])); - assert(equals(range[0], r2[0])); - - // Test opIndex($-1) - assert(equals(range[$-1], expected[i])); - assert(equals(range[$-1], r2[$-1])); - - // Test popBack - range.popBack(); - assert(range.length == r2.length - 1); - r2.popBack(); - assert(range.length == r2.length); - } - assert(range.empty); - assert(r2.empty); - assert(original.length == expected.length); - - // Random access - range = original.save; - r2 = original.save; - foreach(i; 0..expected.length) - { - //trace("Random access: ", i); - - // Test length/empty - assert(range.length == expected.length); - assert(range.length == r2.length); - assert(!range.empty); - assert(!r2.empty); - - // Test front - assert(equals(range.front, expected[0])); - assert(equals(range.front, r2.front)); - - // Test back - assert(equals(range.back, expected[$-1])); - assert(equals(range.back, r2.back)); - - // Test opIndex(i) - assert(equals(range[i], expected[i])); - assert(equals(range[i], r2[i])); - } - assert(!range.empty); - assert(!r2.empty); - assert(original.length == expected.length); - } -} - -@("*: Test sdlang ast") -unittest -{ - import std.exception; - import sdlang.parser; - - Tag root; - root = parseSource(""); - testRandomAccessRange(root.attributes, cast( Attribute[])[]); - testRandomAccessRange(root.tags, cast( Tag[])[]); - testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]); - - root = parseSource(` - blue 3 "Lee" isThree=true - blue 5 "Chan" 12345 isThree=false - stuff:orange 1 2 3 2 1 - stuff:square points=4 dimensions=2 points="Still four" - stuff:triangle data:points=3 data:dimensions=2 - nothing - namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30 - - people visitor:a=1 b=2 { - chiyo "Small" "Flies?" nemesis="Car" score=100 - yukari - visitor:sana - tomo - visitor:hayama - } - `); - - auto blue3 = new Tag( - null, "", "blue", - [ Value(3), Value("Lee") ], - [ new Attribute("isThree", Value(true)) ], - null - ); - auto blue5 = new Tag( - null, "", "blue", - [ Value(5), Value("Chan"), Value(12345) ], - [ new Attribute("isThree", Value(false)) ], - null - ); - auto orange = new Tag( - null, "stuff", "orange", - [ Value(1), Value(2), Value(3), Value(2), Value(1) ], - null, - null - ); - auto square = new Tag( - null, "stuff", "square", - null, - [ - new Attribute("points", Value(4)), - new Attribute("dimensions", Value(2)), - new Attribute("points", Value("Still four")), - ], - null - ); - auto triangle = new Tag( - null, "stuff", "triangle", - null, - [ - new Attribute("data", "points", Value(3)), - new Attribute("data", "dimensions", Value(2)), - ], - null - ); - auto nothing = new Tag( - null, "", "nothing", - null, null, null - ); - auto namespaces = new Tag( - null, "", "namespaces", - null, - [ - new Attribute("small", "A", Value(1)), - new Attribute("med", "A", Value(2)), - new Attribute("big", "A", Value(3)), - new Attribute("small", "B", Value(10)), - new Attribute("big", "B", Value(30)), - ], - null - ); - auto chiyo = new Tag( - null, "", "chiyo", - [ Value("Small"), Value("Flies?") ], - [ - new Attribute("nemesis", Value("Car")), - new Attribute("score", Value(100)), - ], - null - ); - auto chiyo_ = new Tag( - null, "", "chiyo_", - [ Value("Small"), Value("Flies?") ], - [ - new Attribute("nemesis", Value("Car")), - new Attribute("score", Value(100)), - ], - null - ); - auto yukari = new Tag( - null, "", "yukari", - null, null, null - ); - auto sana = new Tag( - null, "visitor", "sana", - null, null, null - ); - auto sana_ = new Tag( - null, "visitor", "sana_", - null, null, null - ); - auto sanaVisitor_ = new Tag( - null, "visitor_", "sana_", - null, null, null - ); - auto tomo = new Tag( - null, "", "tomo", - null, null, null - ); - auto hayama = new Tag( - null, "visitor", "hayama", - null, null, null - ); - auto people = new Tag( - null, "", "people", - null, - [ - new Attribute("visitor", "a", Value(1)), - new Attribute("b", Value(2)), - ], - [chiyo, yukari, sana, tomo, hayama] - ); - - assert(blue3 .opEquals( blue3 )); - assert(blue5 .opEquals( blue5 )); - assert(orange .opEquals( orange )); - assert(square .opEquals( square )); - assert(triangle .opEquals( triangle )); - assert(nothing .opEquals( nothing )); - assert(namespaces .opEquals( namespaces )); - assert(people .opEquals( people )); - assert(chiyo .opEquals( chiyo )); - assert(yukari .opEquals( yukari )); - assert(sana .opEquals( sana )); - assert(tomo .opEquals( tomo )); - assert(hayama .opEquals( hayama )); - - assert(!blue3.opEquals(orange)); - assert(!blue3.opEquals(people)); - assert(!blue3.opEquals(sana)); - assert(!blue3.opEquals(blue5)); - assert(!blue5.opEquals(blue3)); - - alias Tag.NamespaceAccess NSA; - static bool namespaceEquals(NSA n1, NSA n2) - { - return n1.name == n2.name; - } - - testRandomAccessRange(root.attributes, cast(Attribute[])[]); - testRandomAccessRange(root.tags, [blue3, blue5, nothing, namespaces, people]); - testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals); - testRandomAccessRange(root.namespaces[0].tags, [blue3, blue5, nothing, namespaces, people]); - testRandomAccessRange(root.namespaces[1].tags, [orange, square, triangle]); - assert("" in root.namespaces); - assert("stuff" in root.namespaces); - assert("foobar" !in root.namespaces); - testRandomAccessRange(root.namespaces[ ""].tags, [blue3, blue5, nothing, namespaces, people]); - testRandomAccessRange(root.namespaces["stuff"].tags, [orange, square, triangle]); - testRandomAccessRange(root.all.attributes, cast(Attribute[])[]); - testRandomAccessRange(root.all.tags, [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); - testRandomAccessRange(root.all.tags[], [blue3, blue5, orange, square, triangle, nothing, namespaces, people]); - testRandomAccessRange(root.all.tags[3..6], [square, triangle, nothing]); - assert("blue" in root.tags); - assert("nothing" in root.tags); - assert("people" in root.tags); - assert("orange" !in root.tags); - assert("square" !in root.tags); - assert("foobar" !in root.tags); - assert("blue" in root.all.tags); - assert("nothing" in root.all.tags); - assert("people" in root.all.tags); - assert("orange" in root.all.tags); - assert("square" in root.all.tags); - assert("foobar" !in root.all.tags); - assert("orange" in root.namespaces["stuff"].tags); - assert("square" in root.namespaces["stuff"].tags); - assert("square" in root.namespaces["stuff"].tags); - assert("foobar" !in root.attributes); - assert("foobar" !in root.all.attributes); - assert("foobar" !in root.namespaces["stuff"].attributes); - assert("blue" !in root.attributes); - assert("blue" !in root.all.attributes); - assert("blue" !in root.namespaces["stuff"].attributes); - testRandomAccessRange(root.tags["nothing"], [nothing]); - testRandomAccessRange(root.tags["blue"], [blue3, blue5]); - testRandomAccessRange(root.namespaces["stuff"].tags["orange"], [orange]); - testRandomAccessRange(root.all.tags["nothing"], [nothing]); - testRandomAccessRange(root.all.tags["blue"], [blue3, blue5]); - testRandomAccessRange(root.all.tags["orange"], [orange]); - - assertThrown!DOMRangeException(root.tags["foobar"]); - assertThrown!DOMRangeException(root.all.tags["foobar"]); - assertThrown!DOMRangeException(root.attributes["foobar"]); - assertThrown!DOMRangeException(root.all.attributes["foobar"]); - - // DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065, - // so work around it. - //assertThrown!DOMRangeException(root.namespaces["foobar"].tags["foobar"]); - //assertThrown!DOMRangeException(root.namespaces["foobar"].attributes["foobar"]); - bool didCatch = false; - try - auto x = root.namespaces["foobar"].tags["foobar"]; - catch(DOMRangeException e) - didCatch = true; - assert(didCatch); - - didCatch = false; - try - auto x = root.namespaces["foobar"].attributes["foobar"]; - catch(DOMRangeException e) - didCatch = true; - assert(didCatch); - - testRandomAccessRange(root.maybe.tags["nothing"], [nothing]); - testRandomAccessRange(root.maybe.tags["blue"], [blue3, blue5]); - testRandomAccessRange(root.maybe.namespaces["stuff"].tags["orange"], [orange]); - testRandomAccessRange(root.maybe.all.tags["nothing"], [nothing]); - testRandomAccessRange(root.maybe.all.tags["blue"], [blue3, blue5]); - testRandomAccessRange(root.maybe.all.tags["blue"][], [blue3, blue5]); - testRandomAccessRange(root.maybe.all.tags["blue"][0..1], [blue3]); - testRandomAccessRange(root.maybe.all.tags["blue"][1..2], [blue5]); - testRandomAccessRange(root.maybe.all.tags["orange"], [orange]); - testRandomAccessRange(root.maybe.tags["foobar"], cast(Tag[])[]); - testRandomAccessRange(root.maybe.all.tags["foobar"], cast(Tag[])[]); - testRandomAccessRange(root.maybe.namespaces["foobar"].tags["foobar"], cast(Tag[])[]); - testRandomAccessRange(root.maybe.attributes["foobar"], cast(Attribute[])[]); - testRandomAccessRange(root.maybe.all.attributes["foobar"], cast(Attribute[])[]); - testRandomAccessRange(root.maybe.namespaces["foobar"].attributes["foobar"], cast(Attribute[])[]); - - testRandomAccessRange(blue3.attributes, [ new Attribute("isThree", Value(true)) ]); - testRandomAccessRange(blue3.tags, cast(Tag[])[]); - testRandomAccessRange(blue3.namespaces, [NSA("")], &namespaceEquals); - testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]); - testRandomAccessRange(blue3.all.tags, cast(Tag[])[]); - - testRandomAccessRange(blue5.attributes, [ new Attribute("isThree", Value(false)) ]); - testRandomAccessRange(blue5.tags, cast(Tag[])[]); - testRandomAccessRange(blue5.namespaces, [NSA("")], &namespaceEquals); - testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]); - testRandomAccessRange(blue5.all.tags, cast(Tag[])[]); - - testRandomAccessRange(orange.attributes, cast(Attribute[])[]); - testRandomAccessRange(orange.tags, cast(Tag[])[]); - testRandomAccessRange(orange.namespaces, cast(NSA[])[], &namespaceEquals); - testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]); - testRandomAccessRange(orange.all.tags, cast(Tag[])[]); - - testRandomAccessRange(square.attributes, [ - new Attribute("points", Value(4)), - new Attribute("dimensions", Value(2)), - new Attribute("points", Value("Still four")), - ]); - testRandomAccessRange(square.tags, cast(Tag[])[]); - testRandomAccessRange(square.namespaces, [NSA("")], &namespaceEquals); - testRandomAccessRange(square.all.attributes, [ - new Attribute("points", Value(4)), - new Attribute("dimensions", Value(2)), - new Attribute("points", Value("Still four")), - ]); - testRandomAccessRange(square.all.tags, cast(Tag[])[]); - - testRandomAccessRange(triangle.attributes, cast(Attribute[])[]); - testRandomAccessRange(triangle.tags, cast(Tag[])[]); - testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals); - testRandomAccessRange(triangle.namespaces[0].attributes, [ - new Attribute("data", "points", Value(3)), - new Attribute("data", "dimensions", Value(2)), - ]); - assert("data" in triangle.namespaces); - assert("foobar" !in triangle.namespaces); - testRandomAccessRange(triangle.namespaces["data"].attributes, [ - new Attribute("data", "points", Value(3)), - new Attribute("data", "dimensions", Value(2)), - ]); - testRandomAccessRange(triangle.all.attributes, [ - new Attribute("data", "points", Value(3)), - new Attribute("data", "dimensions", Value(2)), - ]); - testRandomAccessRange(triangle.all.tags, cast(Tag[])[]); - - testRandomAccessRange(nothing.attributes, cast(Attribute[])[]); - testRandomAccessRange(nothing.tags, cast(Tag[])[]); - testRandomAccessRange(nothing.namespaces, cast(NSA[])[], &namespaceEquals); - testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]); - testRandomAccessRange(nothing.all.tags, cast(Tag[])[]); - - testRandomAccessRange(namespaces.attributes, cast(Attribute[])[]); - testRandomAccessRange(namespaces.tags, cast(Tag[])[]); - testRandomAccessRange(namespaces.namespaces, [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); - testRandomAccessRange(namespaces.namespaces[], [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); - testRandomAccessRange(namespaces.namespaces[1..2], [NSA("med")], &namespaceEquals); - testRandomAccessRange(namespaces.namespaces[0].attributes, [ - new Attribute("small", "A", Value(1)), - new Attribute("small", "B", Value(10)), - ]); - testRandomAccessRange(namespaces.namespaces[1].attributes, [ - new Attribute("med", "A", Value(2)), - ]); - testRandomAccessRange(namespaces.namespaces[2].attributes, [ - new Attribute("big", "A", Value(3)), - new Attribute("big", "B", Value(30)), - ]); - testRandomAccessRange(namespaces.namespaces[1..2][0].attributes, [ - new Attribute("med", "A", Value(2)), - ]); - assert("small" in namespaces.namespaces); - assert("med" in namespaces.namespaces); - assert("big" in namespaces.namespaces); - assert("foobar" !in namespaces.namespaces); - assert("small" !in namespaces.namespaces[1..2]); - assert("med" in namespaces.namespaces[1..2]); - assert("big" !in namespaces.namespaces[1..2]); - assert("foobar" !in namespaces.namespaces[1..2]); - testRandomAccessRange(namespaces.namespaces["small"].attributes, [ - new Attribute("small", "A", Value(1)), - new Attribute("small", "B", Value(10)), - ]); - testRandomAccessRange(namespaces.namespaces["med"].attributes, [ - new Attribute("med", "A", Value(2)), - ]); - testRandomAccessRange(namespaces.namespaces["big"].attributes, [ - new Attribute("big", "A", Value(3)), - new Attribute("big", "B", Value(30)), - ]); - testRandomAccessRange(namespaces.all.attributes, [ - new Attribute("small", "A", Value(1)), - new Attribute("med", "A", Value(2)), - new Attribute("big", "A", Value(3)), - new Attribute("small", "B", Value(10)), - new Attribute("big", "B", Value(30)), - ]); - testRandomAccessRange(namespaces.all.attributes[], [ - new Attribute("small", "A", Value(1)), - new Attribute("med", "A", Value(2)), - new Attribute("big", "A", Value(3)), - new Attribute("small", "B", Value(10)), - new Attribute("big", "B", Value(30)), - ]); - testRandomAccessRange(namespaces.all.attributes[2..4], [ - new Attribute("big", "A", Value(3)), - new Attribute("small", "B", Value(10)), - ]); - testRandomAccessRange(namespaces.all.tags, cast(Tag[])[]); - assert("A" !in namespaces.attributes); - assert("B" !in namespaces.attributes); - assert("foobar" !in namespaces.attributes); - assert("A" in namespaces.all.attributes); - assert("B" in namespaces.all.attributes); - assert("foobar" !in namespaces.all.attributes); - assert("A" in namespaces.namespaces["small"].attributes); - assert("B" in namespaces.namespaces["small"].attributes); - assert("foobar" !in namespaces.namespaces["small"].attributes); - assert("A" in namespaces.namespaces["med"].attributes); - assert("B" !in namespaces.namespaces["med"].attributes); - assert("foobar" !in namespaces.namespaces["med"].attributes); - assert("A" in namespaces.namespaces["big"].attributes); - assert("B" in namespaces.namespaces["big"].attributes); - assert("foobar" !in namespaces.namespaces["big"].attributes); - assert("foobar" !in namespaces.tags); - assert("foobar" !in namespaces.all.tags); - assert("foobar" !in namespaces.namespaces["small"].tags); - assert("A" !in namespaces.tags); - assert("A" !in namespaces.all.tags); - assert("A" !in namespaces.namespaces["small"].tags); - testRandomAccessRange(namespaces.namespaces["small"].attributes["A"], [ - new Attribute("small", "A", Value(1)), - ]); - testRandomAccessRange(namespaces.namespaces["med"].attributes["A"], [ - new Attribute("med", "A", Value(2)), - ]); - testRandomAccessRange(namespaces.namespaces["big"].attributes["A"], [ - new Attribute("big", "A", Value(3)), - ]); - testRandomAccessRange(namespaces.all.attributes["A"], [ - new Attribute("small", "A", Value(1)), - new Attribute("med", "A", Value(2)), - new Attribute("big", "A", Value(3)), - ]); - testRandomAccessRange(namespaces.all.attributes["B"], [ - new Attribute("small", "B", Value(10)), - new Attribute("big", "B", Value(30)), - ]); - - testRandomAccessRange(chiyo.attributes, [ - new Attribute("nemesis", Value("Car")), - new Attribute("score", Value(100)), - ]); - testRandomAccessRange(chiyo.tags, cast(Tag[])[]); - testRandomAccessRange(chiyo.namespaces, [NSA("")], &namespaceEquals); - testRandomAccessRange(chiyo.all.attributes, [ - new Attribute("nemesis", Value("Car")), - new Attribute("score", Value(100)), - ]); - testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]); - - testRandomAccessRange(yukari.attributes, cast(Attribute[])[]); - testRandomAccessRange(yukari.tags, cast(Tag[])[]); - testRandomAccessRange(yukari.namespaces, cast(NSA[])[], &namespaceEquals); - testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]); - testRandomAccessRange(yukari.all.tags, cast(Tag[])[]); - - testRandomAccessRange(sana.attributes, cast(Attribute[])[]); - testRandomAccessRange(sana.tags, cast(Tag[])[]); - testRandomAccessRange(sana.namespaces, cast(NSA[])[], &namespaceEquals); - testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]); - testRandomAccessRange(sana.all.tags, cast(Tag[])[]); - - testRandomAccessRange(people.attributes, [new Attribute("b", Value(2))]); - testRandomAccessRange(people.tags, [chiyo, yukari, tomo]); - testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); - testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("visitor", "a", Value(1))]); - testRandomAccessRange(people.namespaces[1].attributes, [new Attribute("b", Value(2))]); - testRandomAccessRange(people.namespaces[0].tags, [sana, hayama]); - testRandomAccessRange(people.namespaces[1].tags, [chiyo, yukari, tomo]); - assert("visitor" in people.namespaces); - assert("" in people.namespaces); - assert("foobar" !in people.namespaces); - testRandomAccessRange(people.namespaces["visitor"].attributes, [new Attribute("visitor", "a", Value(1))]); - testRandomAccessRange(people.namespaces[ ""].attributes, [new Attribute("b", Value(2))]); - testRandomAccessRange(people.namespaces["visitor"].tags, [sana, hayama]); - testRandomAccessRange(people.namespaces[ ""].tags, [chiyo, yukari, tomo]); - testRandomAccessRange(people.all.attributes, [ - new Attribute("visitor", "a", Value(1)), - new Attribute("b", Value(2)), - ]); - testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]); - - people.attributes["b"][0].name = "b_"; - people.namespaces["visitor"].attributes["a"][0].name = "a_"; - people.tags["chiyo"][0].name = "chiyo_"; - people.namespaces["visitor"].tags["sana"][0].name = "sana_"; - - assert("b_" in people.attributes); - assert("a_" in people.namespaces["visitor"].attributes); - assert("chiyo_" in people.tags); - assert("sana_" in people.namespaces["visitor"].tags); - - assert(people.attributes["b_"][0] == new Attribute("b_", Value(2))); - assert(people.namespaces["visitor"].attributes["a_"][0] == new Attribute("visitor", "a_", Value(1))); - assert(people.tags["chiyo_"][0] == chiyo_); - assert(people.namespaces["visitor"].tags["sana_"][0] == sana_); - - assert("b" !in people.attributes); - assert("a" !in people.namespaces["visitor"].attributes); - assert("chiyo" !in people.tags); - assert("sana" !in people.namespaces["visitor"].tags); - - assert(people.maybe.attributes["b"].length == 0); - assert(people.maybe.namespaces["visitor"].attributes["a"].length == 0); - assert(people.maybe.tags["chiyo"].length == 0); - assert(people.maybe.namespaces["visitor"].tags["sana"].length == 0); - - people.tags["tomo"][0].remove(); - people.namespaces["visitor"].tags["hayama"][0].remove(); - people.tags["chiyo_"][0].remove(); - testRandomAccessRange(people.tags, [yukari]); - testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); - testRandomAccessRange(people.namespaces[0].tags, [sana_]); - testRandomAccessRange(people.namespaces[1].tags, [yukari]); - assert("visitor" in people.namespaces); - assert("" in people.namespaces); - assert("foobar" !in people.namespaces); - testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]); - testRandomAccessRange(people.namespaces[ ""].tags, [yukari]); - testRandomAccessRange(people.all.tags, [yukari, sana_]); - - people.attributes["b_"][0].namespace = "_"; - people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_"; - assert("_" in people.namespaces); - assert("visitor_" in people.namespaces); - assert("" in people.namespaces); - assert("visitor" in people.namespaces); - people.namespaces["visitor"].tags["sana_"][0].namespace = "visitor_"; - assert("_" in people.namespaces); - assert("visitor_" in people.namespaces); - assert("" in people.namespaces); - assert("visitor" !in people.namespaces); - - assert(people.namespaces["_" ].attributes["b_"][0] == new Attribute("_", "b_", Value(2))); - assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1))); - assert(people.namespaces["visitor_"].tags["sana_"][0] == sanaVisitor_); - - people.tags["yukari"][0].remove(); - people.namespaces["visitor_"].tags["sana_"][0].remove(); - people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor"; - people.namespaces["_"].attributes["b_"][0].namespace = ""; - testRandomAccessRange(people.tags, cast(Tag[])[]); - testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); - testRandomAccessRange(people.namespaces[0].tags, cast(Tag[])[]); - testRandomAccessRange(people.namespaces[1].tags, cast(Tag[])[]); - assert("visitor" in people.namespaces); - assert("" in people.namespaces); - assert("foobar" !in people.namespaces); - testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]); - testRandomAccessRange(people.namespaces[ ""].tags, cast(Tag[])[]); - testRandomAccessRange(people.all.tags, cast(Tag[])[]); - - people.namespaces["visitor"].attributes["a_"][0].remove(); - testRandomAccessRange(people.attributes, [new Attribute("b_", Value(2))]); - testRandomAccessRange(people.namespaces, [NSA("")], &namespaceEquals); - testRandomAccessRange(people.namespaces[0].attributes, [new Attribute("b_", Value(2))]); - assert("visitor" !in people.namespaces); - assert("" in people.namespaces); - assert("foobar" !in people.namespaces); - testRandomAccessRange(people.namespaces[""].attributes, [new Attribute("b_", Value(2))]); - testRandomAccessRange(people.all.attributes, [ - new Attribute("b_", Value(2)), - ]); - - people.attributes["b_"][0].remove(); - testRandomAccessRange(people.attributes, cast(Attribute[])[]); - testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals); - assert("visitor" !in people.namespaces); - assert("" !in people.namespaces); - assert("foobar" !in people.namespaces); - testRandomAccessRange(people.all.attributes, cast(Attribute[])[]); - - // Test clone() - auto rootClone = root.clone(); - assert(rootClone !is root); - assert(rootClone.parent is null); - assert(rootClone.name == root.name); - assert(rootClone.namespace == root.namespace); - assert(rootClone.location == root.location); - assert(rootClone.values == root.values); - assert(rootClone.toSDLDocument() == root.toSDLDocument()); - - auto peopleClone = people.clone(); - assert(peopleClone !is people); - assert(peopleClone.parent is null); - assert(peopleClone.name == people.name); - assert(peopleClone.namespace == people.namespace); - assert(peopleClone.location == people.location); - assert(peopleClone.values == people.values); - assert(peopleClone.toSDLString() == people.toSDLString()); -} - -// Regression test, issue #11: https://github.com/Abscissa/SDLang-D/issues/11 -@("*: Regression test issue #11") -unittest -{ - import sdlang.parser; - - auto root = parseSource( -`// -a`); - - assert("a" in root.tags); - - root = parseSource( -`// -parent { - child -} -`); - - auto child = new Tag( - null, "", "child", - null, null, null - ); - - assert("parent" in root.tags); - assert("child" !in root.tags); - testRandomAccessRange(root.tags["parent"][0].tags, [child]); - assert("child" in root.tags["parent"][0].tags); -} diff --git a/src/sdlang/dub.json b/src/sdlang/dub.json deleted file mode 100644 index d5a0493..0000000 --- a/src/sdlang/dub.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "sdlang-d", - "description": "An SDL (Simple Declarative Language) library for D.", - "homepage": "http://github.com/Abscissa/SDLang-D", - "authors": ["Nick Sabalausky"], - "license": "zlib/libpng", - "copyright": "©2012-2015 Nick Sabalausky", - "sourcePaths": ["."], - "importPaths": ["."], - "buildRequirements": ["allowWarnings"], - "dependencies": { - "libinputvisitor": "~>1.2.0" - }, - "subPackages": [ - "./libinputvisitor" - ], - "configurations": [ - { - "name": "test", - "targetType": "executable", - "versions": ["SDLang_TestApp"], - "targetPath": "../../bin/", - "targetName": "sdlang" - }, - { - "name": "library", - "targetType": "library" - }, - { - "name": "unittest", - "targetType": "executable", - "targetPath": "../../bin/", - "targetName": "sdlang-unittest", - - "versions": ["sdlangUnittest", "sdlangTrace"] - } - ] -} diff --git a/src/sdlang/exception.d b/src/sdlang/exception.d deleted file mode 100644 index 188991e..0000000 --- a/src/sdlang/exception.d +++ /dev/null @@ -1,190 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.exception; - -import std.array; -import std.exception; -import std.range; -import std.stdio; -import std.string; - -import sdlang.ast; -import sdlang.util; - -/// Abstract parent class of all SDLang-D defined exceptions. -abstract class SDLangException : Exception -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Thrown when a syntax error is encounterd while parsing. -class ParseException : SDLangException -{ - Location location; - bool hasLocation; - - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - hasLocation = false; - super(msg, file, line); - } - - this(Location location, string msg, string file = __FILE__, size_t line = __LINE__) - { - hasLocation = true; - super("%s: %s".format(location.toString(), msg), file, line); - } -} - -/// Compatibility alias -deprecated("The new name is ParseException") -alias SDLangParseException = ParseException; - -/++ -Thrown when attempting to do something in the DOM that's unsupported, such as: - -$(UL -$(LI Adding the same instance of a tag or attribute to more than one parent.) -$(LI Writing SDLang where: - $(UL - $(LI The root tag has values, attributes or a namespace. ) - $(LI An anonymous tag has a namespace. ) - $(LI An anonymous tag has no values. ) - $(LI A floating point value is infinity or NaN. ) - ) -)) -+/ -class ValidationException : SDLangException -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Compatibility alias -deprecated("The new name is ValidationException") -alias SDLangValidationException = ValidationException; - -/// Thrown when someting is wrong with the provided arguments to a function. -class ArgumentException : SDLangException -{ - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -abstract class DOMException : SDLangException -{ - Tag base; /// The tag searched from - - this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.base = base; - super(msg, file, line); - } - - /// Prefixes a message with file/line information from the tag (if tag exists). - /// Optionally takes output range as a sink. - string customMsg(string msg) - { - if(!base) - return msg; - - Appender!string sink; - this.customMsg(sink, msg); - return sink.data; - } - - ///ditto - void customMsg(Sink)(ref Sink sink, string msg) if(isOutputRange!(Sink,char)) - { - if(base) - { - sink.put(base.location.toString()); - sink.put(": "); - sink.put(msg); - } - else - sink.put(msg); - } - - /// Outputs a message to stderr, prefixed with file/line information - void writeCustomMsg(string msg) - { - stderr.writeln( customMsg(msg) ); - } -} - -/// Thrown by the DOM on empty range and out-of-range conditions. -class DOMRangeException : DOMException -{ - this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__) - { - super(base, msg, file, line); - } -} - -/// Compatibility alias -deprecated("The new name is DOMRangeException") -alias SDLangRangeException = DOMRangeException; - -/// Abstract parent class of `TagNotFoundException`, `ValueNotFoundException` -/// and `AttributeNotFoundException`. -/// -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a matching element isn't found. -abstract class DOMNotFoundException : DOMException -{ - FullName tagName; /// The tag searched for - - this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.tagName = tagName; - super(base, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a Tag isn't found. -class TagNotFoundException : DOMNotFoundException -{ - this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__) - { - super(base, tagName, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectValue`, etc. functions if a Value isn't found. -class ValueNotFoundException : DOMNotFoundException -{ - /// Expected type for the not-found value. - TypeInfo valueType; - - this(Tag base, FullName tagName, TypeInfo valueType, string msg, string file = __FILE__, size_t line = __LINE__) - { - this.valueType = valueType; - super(base, tagName, msg, file, line); - } -} - -/// Thrown by the DOM's `sdlang.ast.Tag.expectAttribute`, etc. functions if an Attribute isn't found. -class AttributeNotFoundException : DOMNotFoundException -{ - FullName attributeName; /// The attribute searched for - - /// Expected type for the not-found attribute's value. - TypeInfo valueType; - - this(Tag base, FullName tagName, FullName attributeName, TypeInfo valueType, string msg, - string file = __FILE__, size_t line = __LINE__) - { - this.valueType = valueType; - this.attributeName = attributeName; - super(base, tagName, msg, file, line); - } -} diff --git a/src/sdlang/lexer.d b/src/sdlang/lexer.d deleted file mode 100644 index 3788188..0000000 --- a/src/sdlang/lexer.d +++ /dev/null @@ -1,2068 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.lexer; - -import std.algorithm; -import std.array; -static import std.ascii; -import std.base64; -import std.bigint; -import std.conv; -import std.datetime; -import std.file; -import std.format; -import std.traits; -import std.typecons; -import std.uni; -import std.utf; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -alias sdlang.util.startsWith startsWith; - -Token[] lexFile(string filename) -{ - auto source = cast(string)read(filename); - return lexSource(source, filename); -} - -Token[] lexSource(string source, string filename=null) -{ - auto lexer = scoped!Lexer(source, filename); - - // Can't use 'std.array.array(Range)' because 'lexer' is scoped - // and therefore cannot have its reference copied. - Appender!(Token[]) tokens; - foreach(tok; lexer) - tokens.put(tok); - - return tokens.data; -} - -// Kind of a poor-man's yield, but fast. -// Only to be used inside Lexer.popFront (and Lexer.this). -private template accept(string symbolName) -{ - static assert(symbolName != "Value", "Value symbols must also take a value."); - enum accept = acceptImpl!(symbolName, "null"); -} -private template accept(string symbolName, string value) -{ - static assert(symbolName == "Value", "Only a Value symbol can take a value."); - enum accept = acceptImpl!(symbolName, value); -} -private template accept(string symbolName, string value, string startLocation, string endLocation) -{ - static assert(symbolName == "Value", "Only a Value symbol can take a value."); - enum accept = (" - { - _front = makeToken!"~symbolName.stringof~"; - _front.value = "~value~"; - _front.location = "~(startLocation==""? "tokenStart" : startLocation)~"; - _front.data = source[ - "~(startLocation==""? "tokenStart.index" : startLocation)~" - .. - "~(endLocation==""? "location.index" : endLocation)~" - ]; - return; - } - ").replace("\n", ""); -} -private template acceptImpl(string symbolName, string value) -{ - enum acceptImpl = (" - { - _front = makeToken!"~symbolName.stringof~"; - _front.value = "~value~"; - return; - } - ").replace("\n", ""); -} - -class Lexer -{ - string source; - string filename; - Location location; /// Location of current character in source - - private dchar ch; // Current character - private dchar nextCh; // Lookahead character - private size_t nextPos; // Position of lookahead character (an index into source) - private bool hasNextCh; // If false, then there's no more lookahead, just EOF - private size_t posAfterLookahead; // Position after lookahead character (an index into source) - - private Location tokenStart; // The starting location of the token being lexed - - // Length so far of the token being lexed, not including current char - private size_t tokenLength; // Length in UTF-8 code units - private size_t tokenLength32; // Length in UTF-32 code units - - // Slight kludge: - // If a numeric fragment is found after a Date (separated by arbitrary - // whitespace), it could be the "hours" part of a DateTime, or it could - // be a separate numeric literal that simply follows a plain Date. If the - // latter, then the Date must be emitted, but numeric fragment that was - // found after it needs to be saved for the the lexer's next iteration. - // - // It's a slight kludge, and could instead be implemented as a slightly - // kludgey parser hack, but it's the only situation where SDLang's lexing - // needs to lookahead more than one character, so this is good enough. - private struct LookaheadTokenInfo - { - bool exists = false; - string numericFragment = ""; - bool isNegative = false; - Location tokenStart; - } - private LookaheadTokenInfo lookaheadTokenInfo; - - this(string source=null, string filename=null) - { - this.filename = filename; - this.source = source; - - _front = Token(symbol!"Error", Location()); - lookaheadTokenInfo = LookaheadTokenInfo.init; - - if( source.startsWith( ByteOrderMarks[BOM.UTF8] ) ) - { - source = source[ ByteOrderMarks[BOM.UTF8].length .. $ ]; - this.source = source; - } - - foreach(bom; ByteOrderMarks) - if( source.startsWith(bom) ) - error(Location(filename,0,0,0), "SDL spec only supports UTF-8, not UTF-16 or UTF-32"); - - if(source == "") - mixin(accept!"EOF"); - - // Prime everything - hasNextCh = true; - nextCh = source.decode(posAfterLookahead); - advanceChar(ErrorOnEOF.Yes); - location = Location(filename, 0, 0, 0); - popFront(); - } - - @property bool empty() - { - return _front.symbol == symbol!"EOF"; - } - - Token _front; - @property Token front() - { - return _front; - } - - @property bool isEOF() - { - return location.index == source.length && !lookaheadTokenInfo.exists; - } - - private void error(string msg) - { - error(location, msg); - } - - //TODO: Take varargs and use output range sink. - private void error(Location loc, string msg) - { - throw new ParseException(loc, "Error: "~msg); - } - - private Token makeToken(string symbolName)() - { - auto tok = Token(symbol!symbolName, tokenStart); - tok.data = tokenData; - return tok; - } - - private @property string tokenData() - { - return source[ tokenStart.index .. location.index ]; - } - - /// Check the lookahead character - private bool lookahead(dchar ch) - { - return hasNextCh && nextCh == ch; - } - - private bool lookahead(bool function(dchar) condition) - { - return hasNextCh && condition(nextCh); - } - - private static bool isNewline(dchar ch) - { - return ch == '\n' || ch == '\r' || ch == lineSep || ch == paraSep; - } - - /// Returns the length of the newline sequence, or zero if the current - /// character is not a newline - /// - /// Note that there are only single character sequences and the two - /// character sequence `\r\n` as used on Windows. - private size_t isAtNewline() - { - if(ch == '\n' || ch == lineSep || ch == paraSep) return 1; - else if(ch == '\r') return lookahead('\n') ? 2 : 1; - else return 0; - } - - /// Is 'ch' a valid base 64 character? - private bool isBase64(dchar ch) - { - if(ch >= 'A' && ch <= 'Z') - return true; - - if(ch >= 'a' && ch <= 'z') - return true; - - if(ch >= '0' && ch <= '9') - return true; - - return ch == '+' || ch == '/' || ch == '='; - } - - /// Is the current character one that's allowed - /// immediately *after* an int/float literal? - private bool isEndOfNumber() - { - if(isEOF) - return true; - - return !isDigit(ch) && ch != ':' && ch != '_' && !isAlpha(ch); - } - - /// Is current character the last one in an ident? - private bool isEndOfIdentCached = false; - private bool _isEndOfIdent; - private bool isEndOfIdent() - { - if(!isEndOfIdentCached) - { - if(!hasNextCh) - _isEndOfIdent = true; - else - _isEndOfIdent = !isIdentChar(nextCh); - - isEndOfIdentCached = true; - } - - return _isEndOfIdent; - } - - /// Is 'ch' a character that's allowed *somewhere* in an identifier? - private bool isIdentChar(dchar ch) - { - if(isAlpha(ch)) - return true; - - else if(isNumber(ch)) - return true; - - else - return - ch == '-' || - ch == '_' || - ch == '.' || - ch == '$'; - } - - private bool isDigit(dchar ch) - { - return ch >= '0' && ch <= '9'; - } - - private enum KeywordResult - { - Accept, // Keyword is matched - Continue, // Keyword is not matched *yet* - Failed, // Keyword doesn't match - } - private KeywordResult checkKeyword(dstring keyword32) - { - // Still within length of keyword - if(tokenLength32 < keyword32.length) - { - if(ch == keyword32[tokenLength32]) - return KeywordResult.Continue; - else - return KeywordResult.Failed; - } - - // At position after keyword - else if(tokenLength32 == keyword32.length) - { - if(isEOF || !isIdentChar(ch)) - { - debug assert(tokenData == to!string(keyword32)); - return KeywordResult.Accept; - } - else - return KeywordResult.Failed; - } - - assert(0, "Fell off end of keyword to check"); - } - - enum ErrorOnEOF { No, Yes } - - /// Advance one code point. - private void advanceChar(ErrorOnEOF errorOnEOF) - { - if(auto cnt = isAtNewline()) - { - if (cnt == 1) - location.line++; - location.col = 0; - } - else - location.col++; - - location.index = nextPos; - - nextPos = posAfterLookahead; - ch = nextCh; - - if(!hasNextCh) - { - if(errorOnEOF == ErrorOnEOF.Yes) - error("Unexpected end of file"); - - return; - } - - tokenLength32++; - tokenLength = location.index - tokenStart.index; - - if(nextPos == source.length) - { - nextCh = dchar.init; - hasNextCh = false; - return; - } - - nextCh = source.decode(posAfterLookahead); - isEndOfIdentCached = false; - } - - /// Advances the specified amount of characters - private void advanceChar(size_t count, ErrorOnEOF errorOnEOF) - { - while(count-- > 0) - advanceChar(errorOnEOF); - } - - void popFront() - { - // -- Main Lexer ------------- - - eatWhite(); - - if(isEOF) - mixin(accept!"EOF"); - - tokenStart = location; - tokenLength = 0; - tokenLength32 = 0; - isEndOfIdentCached = false; - - if(lookaheadTokenInfo.exists) - { - tokenStart = lookaheadTokenInfo.tokenStart; - - auto prevLATokenInfo = lookaheadTokenInfo; - lookaheadTokenInfo = LookaheadTokenInfo.init; - lexNumeric(prevLATokenInfo); - return; - } - - if(ch == '=') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"="); - } - - else if(ch == '{') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"{"); - } - - else if(ch == '}') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"}"); - } - - else if(ch == ':') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!":"); - } - - else if(ch == ';') - { - advanceChar(ErrorOnEOF.No); - mixin(accept!"EOL"); - } - - else if(auto cnt = isAtNewline()) - { - advanceChar(cnt, ErrorOnEOF.No); - mixin(accept!"EOL"); - } - - else if(isAlpha(ch) || ch == '_') - lexIdentKeyword(); - - else if(ch == '"') - lexRegularString(); - - else if(ch == '`') - lexRawString(); - - else if(ch == '\'') - lexCharacter(); - - else if(ch == '[') - lexBinary(); - - else if(ch == '-' || ch == '.' || isDigit(ch)) - lexNumeric(); - - else - { - if(ch == ',') - error("Unexpected comma: SDLang is not a comma-separated format."); - else if(std.ascii.isPrintable(ch)) - error(text("Unexpected: ", ch)); - else - error("Unexpected character code 0x%02X".format(ch)); - - advanceChar(ErrorOnEOF.No); - } - } - - /// Lex Ident or Keyword - private void lexIdentKeyword() - { - assert(isAlpha(ch) || ch == '_'); - - // Keyword - struct Key - { - dstring name; - Value value; - bool failed = false; - } - static Key[5] keywords; - static keywordsInited = false; - if(!keywordsInited) - { - // Value (as a std.variant-based type) can't be statically inited - keywords[0] = Key("true", Value(true )); - keywords[1] = Key("false", Value(false)); - keywords[2] = Key("on", Value(true )); - keywords[3] = Key("off", Value(false)); - keywords[4] = Key("null", Value(null )); - keywordsInited = true; - } - - foreach(ref key; keywords) - key.failed = false; - - auto numKeys = keywords.length; - - do - { - foreach(ref key; keywords) - if(!key.failed) - { - final switch(checkKeyword(key.name)) - { - case KeywordResult.Accept: - mixin(accept!("Value", "key.value")); - - case KeywordResult.Continue: - break; - - case KeywordResult.Failed: - key.failed = true; - numKeys--; - break; - } - } - - if(numKeys == 0) - { - lexIdent(); - return; - } - - advanceChar(ErrorOnEOF.No); - - } while(!isEOF); - - foreach(ref key; keywords) - if(!key.failed) - if(key.name.length == tokenLength32+1) - mixin(accept!("Value", "key.value")); - - mixin(accept!"Ident"); - } - - /// Lex Ident - private void lexIdent() - { - if(tokenLength == 0) - assert(isAlpha(ch) || ch == '_'); - - while(!isEOF && isIdentChar(ch)) - advanceChar(ErrorOnEOF.No); - - mixin(accept!"Ident"); - } - - /// Lex regular string - private void lexRegularString() - { - assert(ch == '"'); - - Appender!string buf; - size_t spanStart = nextPos; - - // Doesn't include current character - void updateBuf() - { - if(location.index == spanStart) - return; - - buf.put( source[spanStart..location.index] ); - } - - advanceChar(ErrorOnEOF.Yes); - while(ch != '"') - { - if(ch == '\\') - { - updateBuf(); - - bool wasEscSequence = true; - if(hasNextCh) - { - switch(nextCh) - { - case 'n': buf.put('\n'); break; - case 'r': buf.put('\r'); break; - case 't': buf.put('\t'); break; - case '"': buf.put('\"'); break; - case '\\': buf.put('\\'); break; - default: wasEscSequence = false; break; - } - } - - if(wasEscSequence) - { - advanceChar(ErrorOnEOF.Yes); - spanStart = nextPos; - } - else - { - eatWhite(false); - spanStart = location.index; - } - } - - else if(isNewline(ch)) - error("Unescaped newlines are only allowed in raw strings, not regular strings."); - - advanceChar(ErrorOnEOF.Yes); - } - - updateBuf(); - advanceChar(ErrorOnEOF.No); // Skip closing double-quote - mixin(accept!("Value", "buf.data")); - } - - /// Lex raw string - private void lexRawString() - { - assert(ch == '`'); - - do - advanceChar(ErrorOnEOF.Yes); - while(ch != '`'); - - advanceChar(ErrorOnEOF.No); // Skip closing back-tick - mixin(accept!("Value", "tokenData[1..$-1]")); - } - - /// Lex character literal - private void lexCharacter() - { - assert(ch == '\''); - advanceChar(ErrorOnEOF.Yes); // Skip opening single-quote - - dchar value; - if(ch == '\\') - { - advanceChar(ErrorOnEOF.Yes); // Skip escape backslash - switch(ch) - { - case 'n': value = '\n'; break; - case 'r': value = '\r'; break; - case 't': value = '\t'; break; - case '\'': value = '\''; break; - case '\\': value = '\\'; break; - default: error("Invalid escape sequence."); - } - } - else if(isNewline(ch)) - error("Newline not alowed in character literal."); - else - value = ch; - advanceChar(ErrorOnEOF.Yes); // Skip the character itself - - if(ch == '\'') - advanceChar(ErrorOnEOF.No); // Skip closing single-quote - else - error("Expected closing single-quote."); - - mixin(accept!("Value", "value")); - } - - /// Lex base64 binary literal - private void lexBinary() - { - assert(ch == '['); - advanceChar(ErrorOnEOF.Yes); - - void eatBase64Whitespace() - { - while(!isEOF && isWhite(ch)) - { - if(isNewline(ch)) - advanceChar(ErrorOnEOF.Yes); - - if(!isEOF && isWhite(ch)) - eatWhite(); - } - } - - eatBase64Whitespace(); - - // Iterates all valid base64 characters, ending at ']'. - // Skips all whitespace. Throws on invalid chars. - struct Base64InputRange - { - Lexer lexer; - private bool isInited = false; - private int numInputCharsMod4 = 0; - - @property bool empty() - { - if(lexer.ch == ']') - { - if(numInputCharsMod4 != 0) - lexer.error("Length of Base64 encoding must be a multiple of 4. ("~to!string(numInputCharsMod4)~")"); - - return true; - } - - return false; - } - - @property dchar front() - { - return lexer.ch; - } - - void popFront() - { - auto lex = lexer; - - if(!isInited) - { - if(lexer.isBase64(lexer.ch)) - { - numInputCharsMod4++; - numInputCharsMod4 %= 4; - } - - isInited = true; - } - - lex.advanceChar(lex.ErrorOnEOF.Yes); - - eatBase64Whitespace(); - - if(lex.isEOF) - lex.error("Unexpected end of file."); - - if(lex.ch != ']') - { - if(!lex.isBase64(lex.ch)) - lex.error("Invalid character in base64 binary literal."); - - numInputCharsMod4++; - numInputCharsMod4 %= 4; - } - } - } - - // This is a slow ugly hack. It's necessary because Base64.decode - // currently requires the source to have known length. - //TODO: Remove this when DMD issue #9543 is fixed. - dchar[] tmpBuf = array(Base64InputRange(this)); - - Appender!(ubyte[]) outputBuf; - // Ugly workaround for DMD issue #9102 - //TODO: Remove this when DMD #9102 is fixed - struct OutputBuf - { - void put(ubyte ch) - { - outputBuf.put(ch); - } - } - - try - //Base64.decode(Base64InputRange(this), OutputBuf()); - Base64.decode(tmpBuf, OutputBuf()); - - catch(Base64Exception e) - error("Invalid character in base64 binary literal."); - - advanceChar(ErrorOnEOF.No); // Skip ']' - mixin(accept!("Value", "outputBuf.data")); - } - - private BigInt toBigInt(bool isNegative, string absValue) - { - auto num = BigInt(absValue); - assert(num >= 0); - - if(isNegative) - num = -num; - - return num; - } - - /// Lex [0-9]+, but without emitting a token. - /// This is used by the other numeric parsing functions. - private string lexNumericFragment() - { - if(!isDigit(ch)) - error("Expected a digit 0-9."); - - auto spanStart = location.index; - - do - { - advanceChar(ErrorOnEOF.No); - } while(!isEOF && isDigit(ch)); - - return source[spanStart..location.index]; - } - - /// Lex anything that starts with 0-9 or '-'. Ints, floats, dates, etc. - private void lexNumeric(LookaheadTokenInfo laTokenInfo = LookaheadTokenInfo.init) - { - bool isNegative; - string firstFragment; - if(laTokenInfo.exists) - { - firstFragment = laTokenInfo.numericFragment; - isNegative = laTokenInfo.isNegative; - } - else - { - assert(ch == '-' || ch == '.' || isDigit(ch)); - - // Check for negative - isNegative = ch == '-'; - if(isNegative) - advanceChar(ErrorOnEOF.Yes); - - // Some floating point with omitted leading zero? - if(ch == '.') - { - lexFloatingPoint(""); - return; - } - - firstFragment = lexNumericFragment(); - } - - // Long integer (64-bit signed)? - if(ch == 'L' || ch == 'l') - { - advanceChar(ErrorOnEOF.No); - - // BigInt(long.min) is a workaround for DMD issue #9548 - auto num = toBigInt(isNegative, firstFragment); - if(num < BigInt(long.min) || num > long.max) - error(tokenStart, "Value doesn't fit in 64-bit signed long integer: "~to!string(num)); - - mixin(accept!("Value", "num.toLong()")); - } - - // Float (32-bit signed)? - else if(ch == 'F' || ch == 'f') - { - auto value = to!float(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Double float (64-bit signed) with suffix? - else if((ch == 'D' || ch == 'd') && !lookahead(':') - ) - { - auto value = to!double(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Decimal (128+ bits signed)? - else if( - (ch == 'B' || ch == 'b') && - (lookahead('D') || lookahead('d')) - ) - { - auto value = to!real(tokenData); - advanceChar(ErrorOnEOF.No); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Some floating point? - else if(ch == '.') - lexFloatingPoint(firstFragment); - - // Some date? - else if(ch == '/' && hasNextCh && isDigit(nextCh)) - lexDate(isNegative, firstFragment); - - // Some time span? - else if(ch == ':' || ch == 'd') - lexTimeSpan(isNegative, firstFragment); - - // Integer (32-bit signed)? - else if(isEndOfNumber()) - { - auto num = toBigInt(isNegative, firstFragment); - if(num < int.min || num > int.max) - error(tokenStart, "Value doesn't fit in 32-bit signed integer: "~to!string(num)); - - mixin(accept!("Value", "num.toInt()")); - } - - // Invalid suffix - else - error("Invalid integer suffix."); - } - - /// Lex any floating-point literal (after the initial numeric fragment was lexed) - private void lexFloatingPoint(string firstPart) - { - assert(ch == '.'); - advanceChar(ErrorOnEOF.No); - - auto secondPart = lexNumericFragment(); - - try - { - // Double float (64-bit signed) with suffix? - if(ch == 'D' || ch == 'd') - { - auto value = to!double(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Float (32-bit signed)? - else if(ch == 'F' || ch == 'f') - { - auto value = to!float(tokenData); - advanceChar(ErrorOnEOF.No); - mixin(accept!("Value", "value")); - } - - // Decimal (128+ bits signed)? - else if(ch == 'B' || ch == 'b') - { - auto value = to!real(tokenData); - advanceChar(ErrorOnEOF.Yes); - - if(!isEOF && (ch == 'D' || ch == 'd')) - { - advanceChar(ErrorOnEOF.No); - if(isEndOfNumber()) - mixin(accept!("Value", "value")); - } - - error("Invalid floating point suffix."); - } - - // Double float (64-bit signed) without suffix? - else if(isEOF || !isIdentChar(ch)) - { - auto value = to!double(tokenData); - mixin(accept!("Value", "value")); - } - - // Invalid suffix - else - error("Invalid floating point suffix."); - } - catch(ConvException e) - error("Invalid floating point literal."); - } - - private Date makeDate(bool isNegative, string yearStr, string monthStr, string dayStr) - { - BigInt biTmp; - - biTmp = BigInt(yearStr); - if(isNegative) - biTmp = -biTmp; - if(biTmp < int.min || biTmp > int.max) - error(tokenStart, "Date's year is out of range. (Must fit within a 32-bit signed int.)"); - auto year = biTmp.toInt(); - - biTmp = BigInt(monthStr); - if(biTmp < 1 || biTmp > 12) - error(tokenStart, "Date's month is out of range."); - auto month = biTmp.toInt(); - - biTmp = BigInt(dayStr); - if(biTmp < 1 || biTmp > 31) - error(tokenStart, "Date's month is out of range."); - auto day = biTmp.toInt(); - - return Date(year, month, day); - } - - private DateTimeFrac makeDateTimeFrac( - bool isNegative, Date date, string hourStr, string minuteStr, - string secondStr, string millisecondStr - ) - { - BigInt biTmp; - - biTmp = BigInt(hourStr); - if(biTmp < int.min || biTmp > int.max) - error(tokenStart, "Datetime's hour is out of range."); - auto numHours = biTmp.toInt(); - - biTmp = BigInt(minuteStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's minute is out of range."); - auto numMinutes = biTmp.toInt(); - - int numSeconds = 0; - if(secondStr != "") - { - biTmp = BigInt(secondStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's second is out of range."); - numSeconds = biTmp.toInt(); - } - - int millisecond = 0; - if(millisecondStr != "") - { - biTmp = BigInt(millisecondStr); - if(biTmp < 0 || biTmp > int.max) - error(tokenStart, "Datetime's millisecond is out of range."); - millisecond = biTmp.toInt(); - - if(millisecondStr.length == 1) - millisecond *= 100; - else if(millisecondStr.length == 2) - millisecond *= 10; - } - - Duration fracSecs = millisecond.msecs; - - auto offset = hours(numHours) + minutes(numMinutes) + seconds(numSeconds); - - if(isNegative) - { - offset = -offset; - fracSecs = -fracSecs; - } - - return DateTimeFrac(DateTime(date) + offset, fracSecs); - } - - private Duration makeDuration( - bool isNegative, string dayStr, - string hourStr, string minuteStr, string secondStr, - string millisecondStr - ) - { - BigInt biTmp; - - long day = 0; - if(dayStr != "") - { - biTmp = BigInt(dayStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's day is out of range."); - day = biTmp.toLong(); - } - - biTmp = BigInt(hourStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's hour is out of range."); - auto hour = biTmp.toLong(); - - biTmp = BigInt(minuteStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's minute is out of range."); - auto minute = biTmp.toLong(); - - biTmp = BigInt(secondStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's second is out of range."); - auto second = biTmp.toLong(); - - long millisecond = 0; - if(millisecondStr != "") - { - biTmp = BigInt(millisecondStr); - if(biTmp < long.min || biTmp > long.max) - error(tokenStart, "Time span's millisecond is out of range."); - millisecond = biTmp.toLong(); - - if(millisecondStr.length == 1) - millisecond *= 100; - else if(millisecondStr.length == 2) - millisecond *= 10; - } - - auto duration = - dur!"days" (day) + - dur!"hours" (hour) + - dur!"minutes"(minute) + - dur!"seconds"(second) + - dur!"msecs" (millisecond); - - if(isNegative) - duration = -duration; - - return duration; - } - - // This has to reproduce some weird corner case behaviors from the - // original Java version of SDL. So some of this may seem weird. - private Nullable!Duration getTimeZoneOffset(string str) - { - if(str.length < 2) - return Nullable!Duration(); // Unknown timezone - - if(str[0] != '+' && str[0] != '-') - return Nullable!Duration(); // Unknown timezone - - auto isNegative = str[0] == '-'; - - string numHoursStr; - string numMinutesStr; - if(str[1] == ':') - { - numMinutesStr = str[1..$]; - numHoursStr = ""; - } - else - { - numMinutesStr = str.find(':'); - numHoursStr = str[1 .. $-numMinutesStr.length]; - } - - long numHours = 0; - long numMinutes = 0; - bool isUnknown = false; - try - { - switch(numHoursStr.length) - { - case 0: - if(numMinutesStr.length == 3) - { - numHours = 0; - numMinutes = to!long(numMinutesStr[1..$]); - } - else - isUnknown = true; - break; - - case 1: - case 2: - if(numMinutesStr.length == 0) - { - numHours = to!long(numHoursStr); - numMinutes = 0; - } - else if(numMinutesStr.length == 3) - { - numHours = to!long(numHoursStr); - numMinutes = to!long(numMinutesStr[1..$]); - } - else - isUnknown = true; - break; - - default: - if(numMinutesStr.length == 0) - { - // Yes, this is correct - numHours = 0; - numMinutes = to!long(numHoursStr[1..$]); - } - else - isUnknown = true; - break; - } - } - catch(ConvException e) - isUnknown = true; - - if(isUnknown) - return Nullable!Duration(); // Unknown timezone - - auto timeZoneOffset = hours(numHours) + minutes(numMinutes); - if(isNegative) - timeZoneOffset = -timeZoneOffset; - - // Timezone valid - return Nullable!Duration(timeZoneOffset); - } - - /// Lex date or datetime (after the initial numeric fragment was lexed) - private void lexDate(bool isDateNegative, string yearStr) - { - assert(ch == '/'); - - // Lex months - advanceChar(ErrorOnEOF.Yes); // Skip '/' - auto monthStr = lexNumericFragment(); - - // Lex days - if(ch != '/') - error("Invalid date format: Missing days."); - advanceChar(ErrorOnEOF.Yes); // Skip '/' - auto dayStr = lexNumericFragment(); - - auto date = makeDate(isDateNegative, yearStr, monthStr, dayStr); - - if(!isEndOfNumber() && ch != '/') - error("Dates cannot have suffixes."); - - // Date? - if(isEOF) - mixin(accept!("Value", "date")); - - auto endOfDate = location; - - while( - !isEOF && - ( ch == '\\' || ch == '/' || (isWhite(ch) && !isNewline(ch)) ) - ) - { - if(ch == '\\' && hasNextCh && isNewline(nextCh)) - { - advanceChar(ErrorOnEOF.Yes); - if(isAtNewline()) - advanceChar(ErrorOnEOF.Yes); - advanceChar(ErrorOnEOF.No); - } - - eatWhite(); - } - - // Date? - if(isEOF || (!isDigit(ch) && ch != '-')) - mixin(accept!("Value", "date", "", "endOfDate.index")); - - auto startOfTime = location; - - // Is time negative? - bool isTimeNegative = ch == '-'; - if(isTimeNegative) - advanceChar(ErrorOnEOF.Yes); - - // Lex hours - auto hourStr = ch == '.'? "" : lexNumericFragment(); - - // Lex minutes - if(ch != ':') - { - // No minutes found. Therefore we had a plain Date followed - // by a numeric literal, not a DateTime. - lookaheadTokenInfo.exists = true; - lookaheadTokenInfo.numericFragment = hourStr; - lookaheadTokenInfo.isNegative = isTimeNegative; - lookaheadTokenInfo.tokenStart = startOfTime; - mixin(accept!("Value", "date", "", "endOfDate.index")); - } - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto minuteStr = lexNumericFragment(); - - // Lex seconds, if exists - string secondStr; - if(ch == ':') - { - advanceChar(ErrorOnEOF.Yes); // Skip ':' - secondStr = lexNumericFragment(); - } - - // Lex milliseconds, if exists - string millisecondStr; - if(ch == '.') - { - advanceChar(ErrorOnEOF.Yes); // Skip '.' - millisecondStr = lexNumericFragment(); - } - - auto dateTimeFrac = makeDateTimeFrac(isTimeNegative, date, hourStr, minuteStr, secondStr, millisecondStr); - - // Lex zone, if exists - if(ch == '-') - { - advanceChar(ErrorOnEOF.Yes); // Skip '-' - auto timezoneStart = location; - - if(!isAlpha(ch)) - error("Invalid timezone format."); - - while(!isEOF && !isWhite(ch)) - advanceChar(ErrorOnEOF.No); - - auto timezoneStr = source[timezoneStart.index..location.index]; - if(timezoneStr.startsWith("GMT")) - { - auto isoPart = timezoneStr["GMT".length..$]; - auto offset = getTimeZoneOffset(isoPart); - - if(offset.isNull()) - { - // Unknown time zone - mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); - } - else - { - auto timezone = new immutable SimpleTimeZone(offset.get()); - mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); - } - } - - try - { - auto timezone = TimeZone.getTimeZone(timezoneStr); - if(timezone) - mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezone)")); - } - catch(TimeException e) - { - // Time zone not found. So just move along to "Unknown time zone" below. - } - - // Unknown time zone - mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSecs, timezoneStr)")); - } - - if(!isEndOfNumber()) - error("Date-Times cannot have suffixes."); - - mixin(accept!("Value", "dateTimeFrac")); - } - - /// Lex time span (after the initial numeric fragment was lexed) - private void lexTimeSpan(bool isNegative, string firstPart) - { - assert(ch == ':' || ch == 'd'); - - string dayStr = ""; - string hourStr; - - // Lexed days? - bool hasDays = ch == 'd'; - if(hasDays) - { - dayStr = firstPart; - advanceChar(ErrorOnEOF.Yes); // Skip 'd' - - // Lex hours - if(ch != ':') - error("Invalid time span format: Missing hours."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - hourStr = lexNumericFragment(); - } - else - hourStr = firstPart; - - // Lex minutes - if(ch != ':') - error("Invalid time span format: Missing minutes."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto minuteStr = lexNumericFragment(); - - // Lex seconds - if(ch != ':') - error("Invalid time span format: Missing seconds."); - advanceChar(ErrorOnEOF.Yes); // Skip ':' - auto secondStr = lexNumericFragment(); - - // Lex milliseconds, if exists - string millisecondStr = ""; - if(ch == '.') - { - advanceChar(ErrorOnEOF.Yes); // Skip '.' - millisecondStr = lexNumericFragment(); - } - - if(!isEndOfNumber()) - error("Time spans cannot have suffixes."); - - auto duration = makeDuration(isNegative, dayStr, hourStr, minuteStr, secondStr, millisecondStr); - mixin(accept!("Value", "duration")); - } - - /// Advances past whitespace and comments - private void eatWhite(bool allowComments=true) - { - // -- Comment/Whitepace Lexer ------------- - - enum State - { - normal, - lineComment, // Got "#" or "//" or "--", Eating everything until newline - blockComment, // Got "/*", Eating everything until "*/" - } - - if(isEOF) - return; - - Location commentStart; - State state = State.normal; - bool consumeNewlines = false; - bool hasConsumedNewline = false; - while(true) - { - final switch(state) - { - case State.normal: - - if(ch == '\\') - { - commentStart = location; - consumeNewlines = true; - hasConsumedNewline = false; - } - - else if(ch == '#') - { - if(!allowComments) - return; - - commentStart = location; - state = State.lineComment; - continue; - } - - else if(ch == '/' || ch == '-') - { - commentStart = location; - if(lookahead(ch)) - { - if(!allowComments) - return; - - advanceChar(ErrorOnEOF.No); - state = State.lineComment; - continue; - } - else if(ch == '/' && lookahead('*')) - { - if(!allowComments) - return; - - advanceChar(ErrorOnEOF.No); - state = State.blockComment; - continue; - } - else - return; // Done - } - else if(isAtNewline()) - { - if(consumeNewlines) - hasConsumedNewline = true; - else - return; // Done - } - else if(!isWhite(ch)) - { - if(consumeNewlines) - { - if(hasConsumedNewline) - return; // Done - else - error("Only whitespace can come between a line-continuation backslash and the following newline."); - } - else - return; // Done - } - - break; - - case State.lineComment: - if(lookahead(&isNewline)) - state = State.normal; - break; - - case State.blockComment: - if(ch == '*' && lookahead('/')) - { - advanceChar(ErrorOnEOF.No); - state = State.normal; - } - break; - } - - advanceChar(ErrorOnEOF.No); - if(isEOF) - { - // Reached EOF - - if(consumeNewlines && !hasConsumedNewline) - error("Missing newline after line-continuation backslash."); - - else if(state == State.blockComment) - error(commentStart, "Unterminated block comment."); - - else - return; // Done, reached EOF - } - } - } -} - -version(unittest) -{ - import std.stdio; - - version(Have_unit_threaded) import unit_threaded; - else { enum DontTest; } - - private auto loc = Location("filename", 0, 0, 0); - private auto loc2 = Location("a", 1, 1, 1); - - @("lexer: EOL") - unittest - { - assert([Token(symbol!"EOL",loc) ] == [Token(symbol!"EOL",loc) ] ); - assert([Token(symbol!"EOL",loc,Value(7),"A")] == [Token(symbol!"EOL",loc2,Value(7),"B")] ); - } - - private int numErrors = 0; - @DontTest - private void testLex(string source, Token[] expected, bool test_locations = false, string file=__FILE__, size_t line=__LINE__) - { - Token[] actual; - try - actual = lexSource(source, "filename"); - catch(ParseException e) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected); - stderr.writeln(" Actual: ParseException thrown:"); - stderr.writeln(" ", e.msg); - return; - } - - bool is_same = actual == expected; - if (is_same && test_locations) { - is_same = actual.map!(t => t.location).equal(expected.map!(t => t.location)); - } - - if(!is_same) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual); - - if(expected.length > 1 || actual.length > 1) - { - stderr.writeln(" expected.length: ", expected.length); - stderr.writeln(" actual.length: ", actual.length); - - if(actual.length == expected.length) - foreach(i; 0..actual.length) - if(actual[i] != expected[i]) - { - stderr.writeln(" Unequal at index #", i, ":"); - stderr.writeln(" Expected:"); - stderr.writeln(" ", expected[i]); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual[i]); - } - } - } - } - - private void testLexThrows(string file=__FILE__, size_t line=__LINE__)(string source) - { - bool hadException = false; - Token[] actual; - try - actual = lexSource(source, "filename"); - catch(ParseException e) - hadException = true; - - if(!hadException) - { - numErrors++; - stderr.writeln(file, "(", line, "): testLex failed on: ", source); - stderr.writeln(" Expected ParseException"); - stderr.writeln(" Actual:"); - stderr.writeln(" ", actual); - } - } -} - -@("sdlang lexer") -unittest -{ - testLex("", []); - testLex(" ", []); - testLex("\\\n", []); - testLex("/*foo*/", []); - testLex("/* multiline \n comment */", []); - testLex("/* * */", []); - testLexThrows("/* "); - - testLex(":", [ Token(symbol!":", loc) ]); - testLex("=", [ Token(symbol!"=", loc) ]); - testLex("{", [ Token(symbol!"{", loc) ]); - testLex("}", [ Token(symbol!"}", loc) ]); - testLex(";", [ Token(symbol!"EOL",loc) ]); - testLex("\n", [ Token(symbol!"EOL",loc) ]); - - testLex("foo", [ Token(symbol!"Ident",loc,Value(null),"foo") ]); - testLex("_foo", [ Token(symbol!"Ident",loc,Value(null),"_foo") ]); - testLex("foo.bar", [ Token(symbol!"Ident",loc,Value(null),"foo.bar") ]); - testLex("foo-bar", [ Token(symbol!"Ident",loc,Value(null),"foo-bar") ]); - testLex("foo.", [ Token(symbol!"Ident",loc,Value(null),"foo.") ]); - testLex("foo-", [ Token(symbol!"Ident",loc,Value(null),"foo-") ]); - testLexThrows(".foo"); - - testLex("foo bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLex("foo \\ \n \n bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLex("foo \\ \n \\ \n bar", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!"Ident",loc,Value(null),"bar"), - ]); - testLexThrows("foo \\ "); - testLexThrows("foo \\ bar"); - testLexThrows("foo \\ \n \\ "); - testLexThrows("foo \\ \n \\ bar"); - - testLex("foo : = { } ; \n bar \n", [ - Token(symbol!"Ident",loc,Value(null),"foo"), - Token(symbol!":",loc), - Token(symbol!"=",loc), - Token(symbol!"{",loc), - Token(symbol!"}",loc), - Token(symbol!"EOL",loc), - Token(symbol!"EOL",loc), - Token(symbol!"Ident",loc,Value(null),"bar"), - Token(symbol!"EOL",loc), - ]); - - testLexThrows("<"); - testLexThrows("*"); - testLexThrows(`\`); - - // Integers - testLex( "7", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - testLex( "-7", [ Token(symbol!"Value",loc,Value(cast( int)-7)) ]); - testLex( "7L", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); - testLex( "7l", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]); - testLex("-7L", [ Token(symbol!"Value",loc,Value(cast(long)-7)) ]); - testLex( "0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); - testLex( "-0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]); - - testLex("7/**/", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - testLex("7#", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); - - testLex("7 A", [ - Token(symbol!"Value",loc,Value(cast(int)7)), - Token(symbol!"Ident",loc,Value( null),"A"), - ]); - testLexThrows("7A"); - testLexThrows("-A"); - testLexThrows(`-""`); - - testLex("7;", [ - Token(symbol!"Value",loc,Value(cast(int)7)), - Token(symbol!"EOL",loc), - ]); - - // Floats - testLex("1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); - testLex("1.2f" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); - testLex("1.2" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2d" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]); - testLex("1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2Bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - testLex("1.2bD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]); - - testLex(".2F" , [ Token(symbol!"Value",loc,Value(cast( float)0.2)) ]); - testLex(".2" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); - testLex(".2D" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]); - testLex(".2BD", [ Token(symbol!"Value",loc,Value(cast( real)0.2)) ]); - - testLex("-1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-1.2)) ]); - testLex("-1.2" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); - testLex("-1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]); - testLex("-1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-1.2)) ]); - - testLex("-.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-0.2)) ]); - testLex("-.2" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); - testLex("-.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]); - testLex("-.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-0.2)) ]); - - testLex( "0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex( "0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex( "0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex("-0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex("-0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex("-0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex( "7F" , [ Token(symbol!"Value",loc,Value(cast( float)7.0)) ]); - testLex( "7D" , [ Token(symbol!"Value",loc,Value(cast(double)7.0)) ]); - testLex( "7BD" , [ Token(symbol!"Value",loc,Value(cast( real)7.0)) ]); - testLex( "0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex( "0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex( "0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - testLex("-0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]); - testLex("-0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]); - testLex("-0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]); - - testLex("1.2 F", [ - Token(symbol!"Value",loc,Value(cast(double)1.2)), - Token(symbol!"Ident",loc,Value( null),"F"), - ]); - testLexThrows("1.2A"); - testLexThrows("1.2B"); - testLexThrows("1.2BDF"); - - testLex("1.2;", [ - Token(symbol!"Value",loc,Value(cast(double)1.2)), - Token(symbol!"EOL",loc), - ]); - - testLex("1.2F;", [ - Token(symbol!"Value",loc,Value(cast(float)1.2)), - Token(symbol!"EOL",loc), - ]); - - testLex("1.2BD;", [ - Token(symbol!"Value",loc,Value(cast(real)1.2)), - Token(symbol!"EOL",loc), - ]); - - // Booleans and null - testLex("true", [ Token(symbol!"Value",loc,Value( true)) ]); - testLex("false", [ Token(symbol!"Value",loc,Value(false)) ]); - testLex("on", [ Token(symbol!"Value",loc,Value( true)) ]); - testLex("off", [ Token(symbol!"Value",loc,Value(false)) ]); - testLex("null", [ Token(symbol!"Value",loc,Value( null)) ]); - - testLex("TRUE", [ Token(symbol!"Ident",loc,Value(null),"TRUE") ]); - testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]); - testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]); - testLex("tru", [ Token(symbol!"Ident",loc,Value(null),"tru") ]); - testLex("truX", [ Token(symbol!"Ident",loc,Value(null),"truX") ]); - testLex("trueX", [ Token(symbol!"Ident",loc,Value(null),"trueX") ]); - - // Raw Backtick Strings - testLex("`hello world`", [ Token(symbol!"Value",loc,Value(`hello world` )) ]); - testLex("` hello world `", [ Token(symbol!"Value",loc,Value(` hello world ` )) ]); - testLex("`hello \\t world`", [ Token(symbol!"Value",loc,Value(`hello \t world`)) ]); - testLex("`hello \\n world`", [ Token(symbol!"Value",loc,Value(`hello \n world`)) ]); - testLex("`hello \n world`", [ Token(symbol!"Value",loc,Value("hello \n world")) ]); - testLex("`hello \r\n world`", [ Token(symbol!"Value",loc,Value("hello \r\n world")) ]); - testLex("`hello \"world\"`", [ Token(symbol!"Value",loc,Value(`hello "world"` )) ]); - - testLexThrows("`foo"); - testLexThrows("`"); - - // Double-Quote Strings - testLex(`"hello world"`, [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex(`" hello world "`, [ Token(symbol!"Value",loc,Value(" hello world " )) ]); - testLex(`"hello \t world"`, [ Token(symbol!"Value",loc,Value("hello \t world")) ]); - testLex(`"hello \n world"`, [ Token(symbol!"Value",loc,Value("hello \n world")) ]); - testLex("\"hello \\\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex("\"hello \\ \n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex("\"hello \\ \n\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]); - testLex(`"\"hello world\""`, [ Token(symbol!"Value",loc,Value(`"hello world"` )) ]); - testLex(`""`, [ Token(symbol!"Value",loc,Value("" )) ]); // issue #34 - - testLexThrows("\"hello \n world\""); - testLexThrows(`"foo`); - testLexThrows(`"`); - - // Characters - testLex("'a'", [ Token(symbol!"Value",loc,Value(cast(dchar) 'a')) ]); - testLex("'\\n'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\n')) ]); - testLex("'\\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); - testLex("'\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]); - testLex("'\\''", [ Token(symbol!"Value",loc,Value(cast(dchar)'\'')) ]); - testLex(`'\\'`, [ Token(symbol!"Value",loc,Value(cast(dchar)'\\')) ]); - - testLexThrows("'a"); - testLexThrows("'aa'"); - testLexThrows("''"); - testLexThrows("'\\\n'"); - testLexThrows("'\n'"); - testLexThrows(`'\`); - testLexThrows(`'\'`); - testLexThrows("'"); - - // Unicode - testLex("日本語", [ Token(symbol!"Ident",loc,Value(null), "日本語") ]); - testLex("`おはよう、日本。`", [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); - testLex(`"おはよう、日本。"`, [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); - testLex("'月'", [ Token(symbol!"Value",loc,Value("月"d.dup[0])) ]); - - // Base64 Binary - testLex("[aGVsbG8gd29ybGQ=]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - testLex("[ aGVsbG8gd29ybGQ= ]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - testLex("[\n aGVsbG8g \n \n d29ybGQ= \n]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]); - - testLexThrows("[aGVsbG8gd29ybGQ]"); // Ie: Not multiple of 4 - testLexThrows("[ aGVsbG8gd29ybGQ ]"); - - // Date - testLex( "1999/12/5", [ Token(symbol!"Value",loc,Value(Date( 1999, 12, 5))) ]); - testLex( "2013/2/22", [ Token(symbol!"Value",loc,Value(Date( 2013, 2, 22))) ]); - testLex("-2013/2/22", [ Token(symbol!"Value",loc,Value(Date(-2013, 2, 22))) ]); - - testLexThrows("7/"); - testLexThrows("2013/2/22a"); - testLexThrows("2013/2/22f"); - - testLex("1999/12/5\n", [ - Token(symbol!"Value",loc,Value(Date(1999, 12, 5))), - Token(symbol!"EOL",loc), - ]); - - // DateTime, no timezone - testLex( "2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 \t 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22/*foo*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n\n \n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 /*foo*/ \\\n\\\n \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22/*foo*/\\\n/*bar*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]); - testLex("-2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 7, 53, 0)))) ]); - testLex( "2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); - testLex("-2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); - testLex( "2013/2/22 07:53:34", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34)))) ]); - testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs))) ]); - testLex( "2013/2/22 07:53:34.12", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 120.msecs))) ]); - testLex( "2013/2/22 07:53:34.1", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 100.msecs))) ]); - testLex( "2013/2/22 07:53.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs))) ]); - - testLex( "2013/2/22 34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0)))) ]); - testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), 123.msecs))) ]); - testLex( "2013/2/22 34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), 123.msecs))) ]); - - testLex( "2013/2/22 -34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0)))) ]); - testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), -123.msecs))) ]); - testLex( "2013/2/22 -34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), -123.msecs))) ]); - - testLexThrows("2013/2/22 07:53a"); - testLexThrows("2013/2/22 07:53f"); - testLexThrows("2013/2/22 07:53:34.123a"); - testLexThrows("2013/2/22 07:53:34.123f"); - testLexThrows("2013/2/22a 07:53"); - - testLex(`2013/2/22 "foo"`, [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value("foo")), - ]); - - testLex("2013/2/22 07", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(int)7)), - ]); - - testLex("2013/2/22 1.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)1.2)), - ]); - - testLex("2013/2/22 .2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)0.2)), - ]); - - testLex("2013/2/22 -1.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)-1.2)), - ]); - - testLex("2013/2/22 -.2F", [ - Token(symbol!"Value",loc,Value(Date(2013, 2, 22))), - Token(symbol!"Value",loc,Value(cast(float)-0.2)), - ]); - - // DateTime, with known timezone - testLex( "2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex("-2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex("-2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53:34-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53:34-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53:34-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - - testLex( "2013/2/22 -34:65-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - - // DateTime, with Java SDLang's occasionally weird interpretation of some - // "not quite ISO" variations of the "GMT with offset" timezone strings. - Token testTokenSimpleTimeZone(Duration d) - { - auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); - auto tz = new immutable SimpleTimeZone(d); - return Token( symbol!"Value", loc, Value(SysTime(dateTime,tz)) ); - } - Token testTokenUnknownTimeZone(string tzName) - { - auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); - auto frac = 0.msecs; - return Token( symbol!"Value", loc, Value(DateTimeFracUnknownZone(dateTime,frac,tzName)) ); - } - testLex("2013/2/22 07:53-GMT+", [ testTokenUnknownTimeZone("GMT+") ]); - testLex("2013/2/22 07:53-GMT+:", [ testTokenUnknownTimeZone("GMT+:") ]); - testLex("2013/2/22 07:53-GMT+:3", [ testTokenUnknownTimeZone("GMT+:3") ]); - testLex("2013/2/22 07:53-GMT+:03", [ testTokenSimpleTimeZone(minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+:003", [ testTokenUnknownTimeZone("GMT+:003") ]); - - testLex("2013/2/22 07:53-GMT+4", [ testTokenSimpleTimeZone(hours(4)) ]); - testLex("2013/2/22 07:53-GMT+4:", [ testTokenUnknownTimeZone("GMT+4:") ]); - testLex("2013/2/22 07:53-GMT+4:3", [ testTokenUnknownTimeZone("GMT+4:3") ]); - testLex("2013/2/22 07:53-GMT+4:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+4:003", [ testTokenUnknownTimeZone("GMT+4:003") ]); - - testLex("2013/2/22 07:53-GMT+04", [ testTokenSimpleTimeZone(hours(4)) ]); - testLex("2013/2/22 07:53-GMT+04:", [ testTokenUnknownTimeZone("GMT+04:") ]); - testLex("2013/2/22 07:53-GMT+04:3", [ testTokenUnknownTimeZone("GMT+04:3") ]); - testLex("2013/2/22 07:53-GMT+04:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]); - testLex("2013/2/22 07:53-GMT+04:03abc", [ testTokenUnknownTimeZone("GMT+04:03abc") ]); - testLex("2013/2/22 07:53-GMT+04:003", [ testTokenUnknownTimeZone("GMT+04:003") ]); - - testLex("2013/2/22 07:53-GMT+004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+004:", [ testTokenUnknownTimeZone("GMT+004:") ]); - testLex("2013/2/22 07:53-GMT+004:3", [ testTokenUnknownTimeZone("GMT+004:3") ]); - testLex("2013/2/22 07:53-GMT+004:03", [ testTokenUnknownTimeZone("GMT+004:03") ]); - testLex("2013/2/22 07:53-GMT+004:003", [ testTokenUnknownTimeZone("GMT+004:003") ]); - - testLex("2013/2/22 07:53-GMT+0004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+0004:", [ testTokenUnknownTimeZone("GMT+0004:") ]); - testLex("2013/2/22 07:53-GMT+0004:3", [ testTokenUnknownTimeZone("GMT+0004:3") ]); - testLex("2013/2/22 07:53-GMT+0004:03", [ testTokenUnknownTimeZone("GMT+0004:03") ]); - testLex("2013/2/22 07:53-GMT+0004:003", [ testTokenUnknownTimeZone("GMT+0004:003") ]); - - testLex("2013/2/22 07:53-GMT+00004", [ testTokenSimpleTimeZone(minutes(4)) ]); - testLex("2013/2/22 07:53-GMT+00004:", [ testTokenUnknownTimeZone("GMT+00004:") ]); - testLex("2013/2/22 07:53-GMT+00004:3", [ testTokenUnknownTimeZone("GMT+00004:3") ]); - testLex("2013/2/22 07:53-GMT+00004:03", [ testTokenUnknownTimeZone("GMT+00004:03") ]); - testLex("2013/2/22 07:53-GMT+00004:003", [ testTokenUnknownTimeZone("GMT+00004:003") ]); - - // DateTime, with unknown timezone - testLex( "2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]); - testLex("-2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); - testLex("-2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 0.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, "Bogus/Foo"))) ]); - - // Time Span - testLex( "12:14:42", [ Token(symbol!"Value",loc,Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0))) ]); - testLex("-12:14:42", [ Token(symbol!"Value",loc,Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0))) ]); - testLex( "00:09:12", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0))) ]); - testLex( "00:00:01.023", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23))) ]); - testLex( "23d:05:21:23.532", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532))) ]); - testLex( "23d:05:21:23.53", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530))) ]); - testLex( "23d:05:21:23.5", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500))) ]); - testLex("-23d:05:21:23.532", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532))) ]); - testLex("-23d:05:21:23.5", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500))) ]); - testLex( "23d:05:21:23", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0))) ]); - - testLexThrows("12:14:42a"); - testLexThrows("23d:05:21:23.532a"); - testLexThrows("23d:05:21:23.532f"); - - // Combination - testLex("foo. 7", [ - Token(symbol!"Ident",loc,Value( null),"foo."), - Token(symbol!"Value",loc,Value(cast(int)7)) - ]); - - testLex(` - namespace:person "foo" "bar" 1 23L name.first="ひとみ" name.last="Smith" { - namespace:age 37; namespace:favorite_color "blue" // comment - somedate 2013/2/22 07:53 -- comment - - inventory /* comment */ { - socks - } - } - `, - [ - Token(symbol!"EOL",loc,Value(null),"\n"), - - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "person"), - Token(symbol!"Value", loc, Value( "foo" ), `"foo"`), - Token(symbol!"Value", loc, Value( "bar" ), `"bar"`), - Token(symbol!"Value", loc, Value( cast( int) 1 ), "1"), - Token(symbol!"Value", loc, Value( cast(long)23 ), "23L"), - Token(symbol!"Ident", loc, Value( null ), "name.first"), - Token(symbol!"=", loc, Value( null ), "="), - Token(symbol!"Value", loc, Value( "ひとみ" ), `"ひとみ"`), - Token(symbol!"Ident", loc, Value( null ), "name.last"), - Token(symbol!"=", loc, Value( null ), "="), - Token(symbol!"Value", loc, Value( "Smith" ), `"Smith"`), - Token(symbol!"{", loc, Value( null ), "{"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "age"), - Token(symbol!"Value", loc, Value( cast(int)37 ), "37"), - Token(symbol!"EOL", loc, Value( null ), ";"), - Token(symbol!"Ident", loc, Value( null ), "namespace"), - Token(symbol!":", loc, Value( null ), ":"), - Token(symbol!"Ident", loc, Value( null ), "favorite_color"), - Token(symbol!"Value", loc, Value( "blue" ), `"blue"`), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value( null ), "somedate"), - Token(symbol!"Value", loc, Value( DateTimeFrac(DateTime(2013, 2, 22, 7, 53, 0)) ), "2013/2/22 07:53"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - Token(symbol!"EOL", loc, Value( null ), "\n"), - - Token(symbol!"Ident", loc, Value(null), "inventory"), - Token(symbol!"{", loc, Value(null), "{"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"Ident", loc, Value(null), "socks"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"}", loc, Value(null), "}"), - Token(symbol!"EOL", loc, Value(null), "\n"), - - Token(symbol!"}", loc, Value(null), "}"), - Token(symbol!"EOL", loc, Value(null), "\n"), - ]); - - if(numErrors > 0) - stderr.writeln(numErrors, " failed test(s)"); -} - -@("lexer: Regression test issue #8") -unittest -{ - testLex(`"\n \n"`, [ Token(symbol!"Value",loc,Value("\n \n"),`"\n \n"`) ]); - testLex(`"\t\t"`, [ Token(symbol!"Value",loc,Value("\t\t"),`"\t\t"`) ]); - testLex(`"\n\n"`, [ Token(symbol!"Value",loc,Value("\n\n"),`"\n\n"`) ]); -} - -@("lexer: Regression test issue #11") -unittest -{ - void test(string input) - { - testLex( - input, - [ - Token(symbol!"EOL", loc, Value(null), "\n"), - Token(symbol!"Ident",loc,Value(null), "a") - ] - ); - } - - test("//X\na"); - test("//\na"); - test("--\na"); - test("#\na"); -} - -@("ast: Regression test issue #28") -unittest -{ - enum offset = 1; // workaround for an of-by-one error for line numbers - testLex("test", [ - Token(symbol!"Ident", Location("filename", 0, 0, 0), Value(null), "test") - ], true); - testLex("\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\n"), - Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") - ], true); - testLex("\rtest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), - Token(symbol!"Ident", Location("filename", 1, 0, 1), Value(null), "test") - ], true); - testLex("\r\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), - Token(symbol!"Ident", Location("filename", 1, 0, 2), Value(null), "test") - ], true); - testLex("\r\n\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r\n"), - Token(symbol!"EOL", Location("filename", 1, 0, 2), Value(null), "\n"), - Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") - ], true); - testLex("\r\r\ntest", [ - Token(symbol!"EOL", Location("filename", 0, 0, 0), Value(null), "\r"), - Token(symbol!"EOL", Location("filename", 1, 0, 1), Value(null), "\r\n"), - Token(symbol!"Ident", Location("filename", 2, 0, 3), Value(null), "test") - ], true); -} diff --git a/src/sdlang/libinputvisitor/dub.json b/src/sdlang/libinputvisitor/dub.json deleted file mode 100644 index 6e273c8..0000000 --- a/src/sdlang/libinputvisitor/dub.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "libinputvisitor", - "description": "Write D input range generators in a straightforward coroutine style", - "authors": ["Nick Sabalausky"], - "homepage": "https://github.com/abscissa/libInputVisitor", - "license": "WTFPL", - "sourcePaths": ["."], - "importPaths": ["."], - "excludedSourceFiles": ["libInputVisitorExample.d"] -} diff --git a/src/sdlang/libinputvisitor/libInputVisitor.d b/src/sdlang/libinputvisitor/libInputVisitor.d deleted file mode 100644 index f29dc4f..0000000 --- a/src/sdlang/libinputvisitor/libInputVisitor.d +++ /dev/null @@ -1,113 +0,0 @@ -/++ -Copyright (C) 2012 Nick Sabalausky <http://semitwist.com/contact> - -This program is free software. It comes without any warranty, to -the extent permitted by applicable law. You can redistribute it -and/or modify it under the terms of the Do What The Fuck You Want -To Public License, Version 2, as published by Sam Hocevar. See -http://www.wtfpl.net/ for more details. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> - -Everyone is permitted to copy and distribute verbatim or modified -copies of this license document, and changing it is allowed as long -as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. You just DO WHAT THE FUCK YOU WANT TO. -+/ - -/++ -Should work with DMD 2.059 and up - -For more info on this, see: -http://semitwist.com/articles/article/view/combine-coroutines-and-input-ranges-for-dead-simple-d-iteration -+/ - -import core.thread; - -class InputVisitor(Obj, Elem) : Fiber -{ - bool started = false; - Obj obj; - this(Obj obj) - { - this.obj = obj; - - version(Windows) // Issue #1 - { - import core.sys.windows.windows : SYSTEM_INFO, GetSystemInfo; - SYSTEM_INFO info; - GetSystemInfo(&info); - auto PAGESIZE = info.dwPageSize; - - super(&run, PAGESIZE * 16); - } - else - super(&run); - } - - this(Obj obj, size_t stackSize) - { - this.obj = obj; - super(&run, stackSize); - } - - private void run() - { - obj.visit(this); - } - - private void ensureStarted() - { - if(!started) - { - call(); - started = true; - } - } - - // Member 'front' must be a function due to DMD Issue #5403 - private Elem _front = Elem.init; // Default initing here avoids "Error: field _front must be initialized in constructor" - @property Elem front() - { - ensureStarted(); - return _front; - } - - void popFront() - { - ensureStarted(); - call(); - } - - @property bool empty() - { - ensureStarted(); - return state == Fiber.State.TERM; - } - - void yield(Elem elem) - { - _front = elem; - Fiber.yield(); - } -} - -template inputVisitor(Elem) -{ - @property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj) - { - return new InputVisitor!(Obj, Elem)(obj); - } - - @property InputVisitor!(Obj, Elem) inputVisitor(Obj)(Obj obj, size_t stackSize) - { - return new InputVisitor!(Obj, Elem)(obj, stackSize); - } -} diff --git a/src/sdlang/package.d b/src/sdlang/package.d deleted file mode 100644 index dd8df1a..0000000 --- a/src/sdlang/package.d +++ /dev/null @@ -1,133 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -/++ -$(H2 SDLang-D v0.10.0) - -Library for parsing and generating SDL (Simple Declarative Language). - -Import this module to use SDLang-D as a library. - -For the list of officially supported compiler versions, see the -$(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/.travis.yml, .travis.yml) -file included with your version of SDLang-D. - -Links: -$(UL - $(LI $(LINK2 http://sdlang.org/, SDLang Language Homepage) ) - $(LI $(LINK2 https://github.com/Abscissa/SDLang-D, SDLang-D Homepage) ) - $(LI $(LINK2 http://semitwist.com/sdlang-d, SDLang-D API Reference (latest version) ) ) - $(LI $(LINK2 http://semitwist.com/sdlang-d-docs, SDLang-D API Reference (earlier versions) ) ) - $(LI $(LINK2 http://sdl.ikayzo.org/display/SDL/Language+Guide, Old Official SDL Site) [$(LINK2 http://semitwist.com/sdl-mirror/Language+Guide.html, mirror)] ) -) - -Authors: Nick Sabalausky ("Abscissa") http://semitwist.com/contact -Copyright: -Copyright (C) 2012-2016 Nick Sabalausky. - -License: $(LINK2 https://github.com/Abscissa/SDLang-D/blob/master/LICENSE.txt, zlib/libpng) -+/ - -module sdlang; - -import std.array; -import std.datetime; -import std.file; -import std.stdio; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.parser; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -// Expose main public API -public import sdlang.ast : Attribute, Tag; -public import sdlang.exception; -public import sdlang.parser : parseFile, parseSource; -public import sdlang.token : Value, Token, DateTimeFrac, DateTimeFracUnknownZone; -public import sdlang.util : sdlangVersion, Location; - -version(sdlangUsingBuiltinTestRunner) - void main() {} - -version(sdlangCliApp) -{ - int main(string[] args) - { - if( - args.length != 3 || - (args[1] != "lex" && args[1] != "parse" && args[1] != "to-sdl") - ) - { - stderr.writeln("SDLang-D v", sdlangVersion); - stderr.writeln("Usage: sdlang [lex|parse|to-sdl] filename.sdl"); - return 1; - } - - auto filename = args[2]; - - try - { - if(args[1] == "lex") - doLex(filename); - else if(args[1] == "parse") - doParse(filename); - else - doToSDL(filename); - } - catch(ParseException e) - { - stderr.writeln(e.msg); - return 1; - } - - return 0; - } - - void doLex(string filename) - { - auto source = cast(string)read(filename); - auto lexer = new Lexer(source, filename); - - foreach(tok; lexer) - { - // Value - string value; - if(tok.symbol == symbol!"Value") - value = tok.value.hasValue? toString(tok.value.type) : "{null}"; - - value = value==""? "\t" : "("~value~":"~tok.value.toString()~") "; - - // Data - auto data = tok.data.replace("\n", "").replace("\r", ""); - if(data != "") - data = "\t|"~tok.data~"|"; - - // Display - writeln( - tok.location.toString, ":\t", - tok.symbol.name, value, - data - ); - - if(tok.symbol.name == "Error") - break; - } - } - - void doParse(string filename) - { - auto root = parseFile(filename); - stdout.rawWrite(root.toDebugString()); - writeln(); - } - - void doToSDL(string filename) - { - auto root = parseFile(filename); - stdout.rawWrite(root.toSDLDocument()); - } -} diff --git a/src/sdlang/parser.d b/src/sdlang/parser.d deleted file mode 100644 index c9b8d4f..0000000 --- a/src/sdlang/parser.d +++ /dev/null @@ -1,628 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.parser; - -import std.file; - -import libInputVisitor; -import taggedalgebraic; - -import sdlang.ast; -import sdlang.exception; -import sdlang.lexer; -import sdlang.symbol; -import sdlang.token; -import sdlang.util; - -/// Returns root tag. -Tag parseFile(string filename) -{ - auto source = cast(string)read(filename); - return parseSource(source, filename); -} - -/// Returns root tag. The optional `filename` parameter can be included -/// so that the SDLang document's filename (if any) can be displayed with -/// any syntax error messages. -Tag parseSource(string source, string filename=null) -{ - auto lexer = new Lexer(source, filename); - auto parser = DOMParser(lexer); - return parser.parseRoot(); -} - -/++ -Parses an SDL document using StAX/Pull-style. Returns an InputRange with -element type ParserEvent. - -The pullParseFile version reads a file and parses it, while pullParseSource -parses a string passed in. The optional `filename` parameter in pullParseSource -can be included so that the SDLang document's filename (if any) can be displayed -with any syntax error messages. - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. - -Example: ------------------- -parent 12 attr="q" { - childA 34 - childB 56 -} -lastTag ------------------- - -The ParserEvent sequence emitted for that SDL document would be as -follows (indented for readability): ------------------- -TagStartEvent (parent) - ValueEvent (12) - AttributeEvent (attr, "q") - TagStartEvent (childA) - ValueEvent (34) - TagEndEvent - TagStartEvent (childB) - ValueEvent (56) - TagEndEvent -TagEndEvent -TagStartEvent (lastTag) -TagEndEvent ------------------- -+/ -auto pullParseFile(string filename) -{ - auto source = cast(string)read(filename); - return parseSource(source, filename); -} - -///ditto -auto pullParseSource(string source, string filename=null) -{ - auto lexer = new Lexer(source, filename); - auto parser = PullParser(lexer); - return inputVisitor!ParserEvent( parser ); -} - -/// -@("pullParseFile/pullParseSource example") -unittest -{ - // stuff.sdl - immutable stuffSdl = ` - name "sdlang-d" - description "An SDL (Simple Declarative Language) library for D." - homepage "http://github.com/Abscissa/SDLang-D" - - configuration "library" { - targetType "library" - } - `; - - import std.stdio; - - foreach(event; pullParseSource(stuffSdl)) - final switch(event.kind) - { - case ParserEvent.Kind.tagStart: - auto e = cast(TagStartEvent) event; - writeln("TagStartEvent: ", e.namespace, ":", e.name, " @ ", e.location); - break; - - case ParserEvent.Kind.tagEnd: - auto e = cast(TagEndEvent) event; - writeln("TagEndEvent"); - break; - - case ParserEvent.Kind.value: - auto e = cast(ValueEvent) event; - writeln("ValueEvent: ", e.value); - break; - - case ParserEvent.Kind.attribute: - auto e = cast(AttributeEvent) event; - writeln("AttributeEvent: ", e.namespace, ":", e.name, "=", e.value); - break; - } -} - -private union ParserEventUnion -{ - TagStartEvent tagStart; - TagEndEvent tagEnd; - ValueEvent value; - AttributeEvent attribute; -} - -/++ -The element of the InputRange returned by pullParseFile and pullParseSource. - -This is a tagged union, built from the following: -------- -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; -private union ParserEventUnion -{ - TagStartEvent tagStart; - TagEndEvent tagEnd; - ValueEvent value; - AttributeEvent attribute; -} -------- - -Note: The old FileStartEvent and FileEndEvent events -$(LINK2 https://github.com/Abscissa/SDLang-D/issues/17, were deemed unnessecary) -and removed as of SDLang-D v0.10.0. - -Note: Previously, in SDLang-D v0.9.x, ParserEvent was a -$(LINK2 http://dlang.org/phobos/std_variant.html#.Algebraic, std.variant.Algebraic). -As of SDLang-D v0.10.0, it is now a -$(LINK2 https://github.com/s-ludwig/taggedalgebraic, TaggedAlgebraic), -so usage has changed somewhat. -+/ -alias ParserEvent = TaggedAlgebraic!ParserEventUnion; - -/// -@("ParserEvent example") -unittest -{ - // Create - ParserEvent event1 = TagStartEvent(); - ParserEvent event2 = TagEndEvent(); - ParserEvent event3 = ValueEvent(); - ParserEvent event4 = AttributeEvent(); - - // Check type - assert(event1.kind == ParserEvent.Kind.tagStart); - assert(event2.kind == ParserEvent.Kind.tagEnd); - assert(event3.kind == ParserEvent.Kind.value); - assert(event4.kind == ParserEvent.Kind.attribute); - - // Cast to base type - auto e1 = cast(TagStartEvent) event1; - auto e2 = cast(TagEndEvent) event2; - auto e3 = cast(ValueEvent) event3; - auto e4 = cast(AttributeEvent) event4; - //auto noGood = cast(AttributeEvent) event1; // AssertError: event1 is a TagStartEvent, not AttributeEvent. - - // Use as base type. - // In many cases, no casting is even needed. - event1.name = "foo"; - //auto noGood = event3.name; // AssertError: ValueEvent doesn't have a member 'name'. - - // Final switch is supported: - final switch(event1.kind) - { - case ParserEvent.Kind.tagStart: break; - case ParserEvent.Kind.tagEnd: break; - case ParserEvent.Kind.value: break; - case ParserEvent.Kind.attribute: break; - } -} - -/// Event: Start of tag -struct TagStartEvent -{ - Location location; - string namespace; - string name; -} - -/// Event: End of tag -struct TagEndEvent -{ - //Location location; -} - -/// Event: Found a Value in the current tag -struct ValueEvent -{ - Location location; - Value value; -} - -/// Event: Found an Attribute in the current tag -struct AttributeEvent -{ - Location location; - string namespace; - string name; - Value value; -} - -// The actual pull parser -private struct PullParser -{ - private Lexer lexer; - - private struct IDFull - { - string namespace; - string name; - } - - private void error(string msg) - { - error(lexer.front.location, msg); - } - - private void error(Location loc, string msg) - { - throw new ParseException(loc, "Error: "~msg); - } - - private InputVisitor!(PullParser, ParserEvent) v; - - void visit(InputVisitor!(PullParser, ParserEvent) v) - { - this.v = v; - parseRoot(); - } - - private void emit(Event)(Event event) - { - v.yield( ParserEvent(event) ); - } - - /// <Root> ::= <Tags> EOF (Lookaheads: Anything) - private void parseRoot() - { - //trace("Starting parse of file: ", lexer.filename); - //trace(__FUNCTION__, ": <Root> ::= <Tags> EOF (Lookaheads: Anything)"); - - auto startLocation = Location(lexer.filename, 0, 0, 0); - - parseTags(); - - auto token = lexer.front; - if(token.matches!":"()) - { - lexer.popFront(); - token = lexer.front; - if(token.matches!"Ident"()) - { - error("Missing namespace. If you don't wish to use a namespace, then say '"~token.data~"', not ':"~token.data~"'"); - assert(0); - } - else - { - error("Missing namespace. If you don't wish to use a namespace, then omit the ':'"); - assert(0); - } - } - else if(!token.matches!"EOF"()) - error("Expected a tag or end-of-file, not " ~ token.symbol.name); - } - - /// <Tags> ::= <Tag> <Tags> (Lookaheads: Ident Value) - /// | EOL <Tags> (Lookaheads: EOL) - /// | {empty} (Lookaheads: Anything else, except '{') - void parseTags() - { - //trace("Enter ", __FUNCTION__); - while(true) - { - auto token = lexer.front; - if(token.matches!"Ident"() || token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Tags> ::= <Tag> <Tags> (Lookaheads: Ident Value)"); - parseTag(); - continue; - } - else if(token.matches!"EOL"()) - { - //trace(__FUNCTION__, ": <Tags> ::= EOL <Tags> (Lookaheads: EOL)"); - lexer.popFront(); - continue; - } - else if(token.matches!"{"()) - { - error("Found start of child block, but no tag name. If you intended an anonymous "~ - "tag, you must have at least one value before any attributes or child tags."); - } - else - { - //trace(__FUNCTION__, ": <Tags> ::= {empty} (Lookaheads: Anything else, except '{')"); - break; - } - } - } - - /// <Tag> - /// ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Ident) - /// | <Value> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Value) - void parseTag() - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <Tag> ::= <IDFull> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Ident)"); - //trace("Found tag named: ", tag.fullName); - auto id = parseIDFull(); - emit( TagStartEvent(token.location, id.namespace, id.name) ); - } - else if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Tag> ::= <Value> <Values> <Attributes> <OptChild> <TagTerminator> (Lookaheads: Value)"); - //trace("Found anonymous tag."); - emit( TagStartEvent(token.location, null, null) ); - } - else - error("Expected tag name or value, not " ~ token.symbol.name); - - if(lexer.front.matches!"="()) - error("Found attribute, but no tag name. If you intended an anonymous "~ - "tag, you must have at least one value before any attributes."); - - parseValues(); - parseAttributes(); - parseOptChild(); - parseTagTerminator(); - - emit( TagEndEvent() ); - } - - /// <IDFull> ::= Ident <IDSuffix> (Lookaheads: Ident) - IDFull parseIDFull() - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <IDFull> ::= Ident <IDSuffix> (Lookaheads: Ident)"); - lexer.popFront(); - return parseIDSuffix(token.data); - } - else - { - error("Expected namespace or identifier, not " ~ token.symbol.name); - assert(0); - } - } - - /// <IDSuffix> - /// ::= ':' Ident (Lookaheads: ':') - /// ::= {empty} (Lookaheads: Anything else) - IDFull parseIDSuffix(string firstIdent) - { - auto token = lexer.front; - if(token.matches!":"()) - { - //trace(__FUNCTION__, ": <IDSuffix> ::= ':' Ident (Lookaheads: ':')"); - lexer.popFront(); - token = lexer.front; - if(token.matches!"Ident"()) - { - lexer.popFront(); - return IDFull(firstIdent, token.data); - } - else - { - error("Expected name, not " ~ token.symbol.name); - assert(0); - } - } - else - { - //trace(__FUNCTION__, ": <IDSuffix> ::= {empty} (Lookaheads: Anything else)"); - return IDFull("", firstIdent); - } - } - - /// <Values> - /// ::= Value <Values> (Lookaheads: Value) - /// | {empty} (Lookaheads: Anything else) - void parseValues() - { - while(true) - { - auto token = lexer.front; - if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": <Values> ::= Value <Values> (Lookaheads: Value)"); - parseValue(); - continue; - } - else - { - //trace(__FUNCTION__, ": <Values> ::= {empty} (Lookaheads: Anything else)"); - break; - } - } - } - - /// Handle Value terminals that aren't part of an attribute - void parseValue() - { - auto token = lexer.front; - if(token.matches!"Value"()) - { - //trace(__FUNCTION__, ": (Handle Value terminals that aren't part of an attribute)"); - auto value = token.value; - //trace("In tag '", parent.fullName, "', found value: ", value); - emit( ValueEvent(token.location, value) ); - - lexer.popFront(); - } - else - error("Expected value, not "~token.symbol.name); - } - - /// <Attributes> - /// ::= <Attribute> <Attributes> (Lookaheads: Ident) - /// | {empty} (Lookaheads: Anything else) - void parseAttributes() - { - while(true) - { - auto token = lexer.front; - if(token.matches!"Ident"()) - { - //trace(__FUNCTION__, ": <Attributes> ::= <Attribute> <Attributes> (Lookaheads: Ident)"); - parseAttribute(); - continue; - } - else - { - //trace(__FUNCTION__, ": <Attributes> ::= {empty} (Lookaheads: Anything else)"); - break; - } - } - } - - /// <Attribute> ::= <IDFull> '=' Value (Lookaheads: Ident) - void parseAttribute() - { - //trace(__FUNCTION__, ": <Attribute> ::= <IDFull> '=' Value (Lookaheads: Ident)"); - auto token = lexer.front; - if(!token.matches!"Ident"()) - error("Expected attribute name, not "~token.symbol.name); - - auto id = parseIDFull(); - - token = lexer.front; - if(!token.matches!"="()) - error("Expected '=' after attribute name, not "~token.symbol.name); - - lexer.popFront(); - token = lexer.front; - if(!token.matches!"Value"()) - error("Expected attribute value, not "~token.symbol.name); - - //trace("In tag '", parent.fullName, "', found attribute '", attr.fullName, "'"); - emit( AttributeEvent(token.location, id.namespace, id.name, token.value) ); - - lexer.popFront(); - } - - /// <OptChild> - /// ::= '{' EOL <Tags> '}' (Lookaheads: '{') - /// | {empty} (Lookaheads: Anything else) - void parseOptChild() - { - auto token = lexer.front; - if(token.matches!"{") - { - //trace(__FUNCTION__, ": <OptChild> ::= '{' EOL <Tags> '}' (Lookaheads: '{')"); - lexer.popFront(); - token = lexer.front; - if(!token.matches!"EOL"()) - error("Expected newline or semicolon after '{', not "~token.symbol.name); - - lexer.popFront(); - parseTags(); - - token = lexer.front; - if(!token.matches!"}"()) - error("Expected '}' after child tags, not "~token.symbol.name); - lexer.popFront(); - } - else - { - //trace(__FUNCTION__, ": <OptChild> ::= {empty} (Lookaheads: Anything else)"); - // Do nothing, no error. - } - } - - /// <TagTerminator> - /// ::= EOL (Lookahead: EOL) - /// | {empty} (Lookahead: EOF) - void parseTagTerminator() - { - auto token = lexer.front; - if(token.matches!"EOL") - { - //trace(__FUNCTION__, ": <TagTerminator> ::= EOL (Lookahead: EOL)"); - lexer.popFront(); - } - else if(token.matches!"EOF") - { - //trace(__FUNCTION__, ": <TagTerminator> ::= {empty} (Lookahead: EOF)"); - // Do nothing - } - else - error("Expected end of tag (newline, semicolon or end-of-file), not " ~ token.symbol.name); - } -} - -private struct DOMParser -{ - Lexer lexer; - - Tag parseRoot() - { - auto currTag = new Tag(null, null, "root"); - currTag.location = Location(lexer.filename, 0, 0, 0); - - auto parser = PullParser(lexer); - auto eventRange = inputVisitor!ParserEvent( parser ); - - foreach(event; eventRange) - final switch(event.kind) - { - case ParserEvent.Kind.tagStart: - auto newTag = new Tag(currTag, event.namespace, event.name); - newTag.location = event.location; - - currTag = newTag; - break; - - case ParserEvent.Kind.tagEnd: - currTag = currTag.parent; - - if(!currTag) - parser.error("Internal Error: Received an extra TagEndEvent"); - break; - - case ParserEvent.Kind.value: - currTag.add((cast(ValueEvent)event).value); - break; - - case ParserEvent.Kind.attribute: - auto e = cast(AttributeEvent) event; - auto attr = new Attribute(e.namespace, e.name, e.value, e.location); - currTag.add(attr); - break; - } - - return currTag; - } -} - -// Other parser tests are part of the AST's tests over in the ast module. - -// Regression test, issue #13: https://github.com/Abscissa/SDLang-D/issues/13 -// "Incorrectly accepts ":tagname" (blank namespace, tagname prefixed with colon)" -@("parser: Regression test issue #13") -unittest -{ - import std.exception; - assertThrown!ParseException(parseSource(`:test`)); - assertThrown!ParseException(parseSource(`:4`)); -} - -// Regression test, issue #16: https://github.com/Abscissa/SDLang-D/issues/16 -@("parser: Regression test issue #16") -unittest -{ - // Shouldn't crash - foreach(event; pullParseSource(`tag "data"`)) - { - if(event.kind == ParserEvent.Kind.tagStart) - auto e = cast(TagStartEvent) event; - } -} - -// Regression test, issue #31: https://github.com/Abscissa/SDLang-D/issues/31 -// "Escape sequence results in range violation error" -@("parser: Regression test issue #31") -unittest -{ - // Shouldn't get a Range violation - parseSource(`test "\"foo\""`); -} diff --git a/src/sdlang/symbol.d b/src/sdlang/symbol.d deleted file mode 100644 index ebb2b93..0000000 --- a/src/sdlang/symbol.d +++ /dev/null @@ -1,61 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.symbol; - -import std.algorithm; - -static immutable validSymbolNames = [ - "Error", - "EOF", - "EOL", - - ":", - "=", - "{", - "}", - - "Ident", - "Value", -]; - -/// Use this to create a Symbol. Ex: symbol!"Value" or symbol!"=" -/// Invalid names (such as symbol!"FooBar") are rejected at compile-time. -template symbol(string name) -{ - static assert(validSymbolNames.find(name), "Invalid Symbol: '"~name~"'"); - immutable symbol = _symbol(name); -} - -private Symbol _symbol(string name) -{ - return Symbol(name); -} - -/// Symbol is essentially the "type" of a Token. -/// Token is like an instance of a Symbol. -/// -/// This only represents terminals. Nonterminal tokens aren't -/// constructed since the AST is built directly during parsing. -/// -/// You can't create a Symbol directly. Instead, use the `symbol` -/// template. -struct Symbol -{ - private string _name; - @property string name() - { - return _name; - } - - @disable this(); - private this(string name) - { - this._name = name; - } - - string toString() - { - return _name; - } -} diff --git a/src/sdlang/taggedalgebraic/taggedalgebraic.d b/src/sdlang/taggedalgebraic/taggedalgebraic.d deleted file mode 100644 index ffaac49..0000000 --- a/src/sdlang/taggedalgebraic/taggedalgebraic.d +++ /dev/null @@ -1,1085 +0,0 @@ -/** - * Algebraic data type implementation based on a tagged union. - * - * Copyright: Copyright 2015, Sönke Ludwig. - * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Sönke Ludwig -*/ -module taggedalgebraic; - -import std.typetuple; - -// TODO: -// - distinguish between @property and non@-property methods. -// - verify that static methods are handled properly - -/** Implements a generic algebraic type using an enum to identify the stored type. - - This struct takes a `union` or `struct` declaration as an input and builds - an algebraic data type from its fields, using an automatically generated - `Kind` enumeration to identify which field of the union is currently used. - Multiple fields with the same value are supported. - - All operators and methods are transparently forwarded to the contained - value. The caller has to make sure that the contained value supports the - requested operation. Failure to do so will result in an assertion failure. - - The return value of forwarded operations is determined as follows: - $(UL - $(LI If the type can be uniquely determined, it is used as the return - value) - $(LI If there are multiple possible return values and all of them match - the unique types defined in the `TaggedAlgebraic`, a - `TaggedAlgebraic` is returned.) - $(LI If there are multiple return values and none of them is a - `Variant`, an `Algebraic` of the set of possible return types is - returned.) - $(LI If any of the possible operations returns a `Variant`, this is used - as the return value.) - ) -*/ -struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct)) -{ - import std.algorithm : among; - import std.string : format; - import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor; - - private alias Union = U; - private alias FieldTypes = FieldTypeTuple!U; - private alias fieldNames = FieldNameTuple!U; - - static assert(FieldTypes.length > 0, "The TaggedAlgebraic's union type must have at least one field."); - static assert(FieldTypes.length == fieldNames.length); - - - private { - void[Largest!FieldTypes.sizeof] m_data = void; - Kind m_kind; - } - - /// A type enum that identifies the type of value currently stored. - alias Kind = TypeEnum!U; - - /// Compatibility alias - deprecated("Use 'Kind' instead.") alias Type = Kind; - - /// The type ID of the currently stored value. - @property Kind kind() const { return m_kind; } - - // Compatibility alias - deprecated("Use 'kind' instead.") - alias typeID = kind; - - // constructors - //pragma(msg, generateConstructors!U()); - mixin(generateConstructors!U); - - this(TaggedAlgebraic other) - { - import std.algorithm : swap; - swap(this, other); - } - - void opAssign(TaggedAlgebraic other) - { - import std.algorithm : swap; - swap(this, other); - } - - // postblit constructor - static if (anySatisfy!(hasElaborateCopyConstructor, FieldTypes)) - { - this(this) - { - switch (m_kind) { - default: break; - foreach (i, tname; fieldNames) { - alias T = typeof(__traits(getMember, U, tname)); - static if (hasElaborateCopyConstructor!T) - { - case __traits(getMember, Kind, tname): - typeid(T).postblit(cast(void*)&trustedGet!tname()); - return; - } - } - } - } - } - - // destructor - static if (anySatisfy!(hasElaborateDestructor, FieldTypes)) - { - ~this() - { - final switch (m_kind) { - foreach (i, tname; fieldNames) { - alias T = typeof(__traits(getMember, U, tname)); - case __traits(getMember, Kind, tname): - static if (hasElaborateDestructor!T) { - .destroy(trustedGet!tname); - } - return; - } - } - } - } - - /// Enables conversion or extraction of the stored value. - T opCast(T)() - { - import std.conv : to; - - final switch (m_kind) { - foreach (i, FT; FieldTypes) { - case __traits(getMember, Kind, fieldNames[i]): - static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { - return to!T(trustedGet!(fieldNames[i])); - } else { - assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); - } - } - } - assert(false); // never reached - } - /// ditto - T opCast(T)() const - { - // this method needs to be duplicated because inout doesn't work with to!() - import std.conv : to; - - final switch (m_kind) { - foreach (i, FT; FieldTypes) { - case __traits(getMember, Kind, fieldNames[i]): - static if (is(typeof(to!T(trustedGet!(fieldNames[i]))))) { - return to!T(trustedGet!(fieldNames[i])); - } else { - assert(false, "Cannot cast a "~(cast(Kind)m_kind).to!string~" value ("~FT.stringof~") to "~T.stringof); - } - } - } - assert(false); // never reached - } - - /// Uses `cast(string)`/`to!string` to return a string representation of the enclosed value. - string toString() const { return cast(string)this; } - - // NOTE: "this TA" is used here as the functional equivalent of inout, - // just that it generates one template instantiation per modifier - // combination, so that we can actually decide what to do for each - // case. - - /// Enables the invocation of methods of the stored value. - auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); } - /// Enables accessing properties/fields of the stored value. - @property auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); } - /// Enables equality comparison with the stored value. - auto opEquals(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "==", T)) { return implementOp!(OpKind.binary, "==")(this, other); } - /// Enables relational comparisons with the stored value. - auto opCmp(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "<", T)) { assert(false, "TODO!"); } - /// Enables the use of unary operators with the stored value. - auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); } - /// Enables the use of binary operators with the stored value. - auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); } - /// Enables the use of binary operators with the stored value. - auto opBinaryRight(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binaryRight, op, T)) { return implementOp!(OpKind.binaryRight, op)(this, other); } - /// Enables operator assignments on the stored value. - auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); } - /// Enables indexing operations on the stored value. - auto opIndex(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.index, null, ARGS)) { return implementOp!(OpKind.index, null)(this, args); } - /// Enables index assignments on the stored value. - auto opIndexAssign(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.indexAssign, null, ARGS)) { return implementOp!(OpKind.indexAssign, null)(this, args); } - /// Enables call syntax operations on the stored value. - auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); } - - private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); } - private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; } -} - -/// -unittest -{ - import taggedalgebraic; - - struct Foo { - string name; - void bar() {} - } - - union Base { - int i; - string str; - Foo foo; - } - - alias Tagged = TaggedAlgebraic!Base; - - // Instantiate - Tagged taggedInt = 5; - Tagged taggedString = "Hello"; - Tagged taggedFoo = Foo(); - Tagged taggedAny = taggedInt; - taggedAny = taggedString; - taggedAny = taggedFoo; - - // Check type: Tagged.Kind is an enum - assert(taggedInt.kind == Tagged.Kind.i); - assert(taggedString.kind == Tagged.Kind.str); - assert(taggedFoo.kind == Tagged.Kind.foo); - assert(taggedAny.kind == Tagged.Kind.foo); - - // In most cases, can simply use as-is - auto num = 4 + taggedInt; - auto msg = taggedString ~ " World!"; - taggedFoo.bar(); - if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! - taggedAny.bar(); - //taggedString.bar(); // AssertError: Not a Foo! - - // Convert back by casting - auto i = cast(int) taggedInt; - auto str = cast(string) taggedString; - auto foo = cast(Foo) taggedFoo; - if (taggedAny.kind == Tagged.Kind.foo) // Make sure to check type first! - auto foo2 = cast(Foo) taggedAny; - //cast(Foo) taggedString; // AssertError! - - // Kind is an enum, so final switch is supported: - final switch (taggedAny.kind) { - case Tagged.Kind.i: - // It's "int i" - break; - - case Tagged.Kind.str: - // It's "string str" - break; - - case Tagged.Kind.foo: - // It's "Foo foo" - break; - } -} - -/** Operators and methods of the contained type can be used transparently. -*/ -@safe unittest { - static struct S { - int v; - int test() { return v / 2; } - } - - static union Test { - typeof(null) null_; - int integer; - string text; - string[string] dictionary; - S custom; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - assert(ta.kind == TA.Kind.null_); - - ta = 12; - assert(ta.kind == TA.Kind.integer); - assert(ta == 12); - assert(cast(int)ta == 12); - assert(cast(long)ta == 12); - assert(cast(short)ta == 12); - - ta += 12; - assert(ta == 24); - assert(ta - 10 == 14); - - ta = ["foo" : "bar"]; - assert(ta.kind == TA.Kind.dictionary); - assert(ta["foo"] == "bar"); - - ta["foo"] = "baz"; - assert(ta["foo"] == "baz"); - - ta = S(8); - assert(ta.test() == 4); -} - -unittest { // std.conv integration - import std.conv : to; - - static struct S { - int v; - int test() { return v / 2; } - } - - static union Test { - typeof(null) null_; - int number; - string text; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - assert(ta.kind == TA.Kind.null_); - ta = "34"; - assert(ta == "34"); - assert(to!int(ta) == 34, to!string(to!int(ta))); - assert(to!string(ta) == "34", to!string(ta)); -} - -/** Multiple fields are allowed to have the same type, in which case the type - ID enum is used to disambiguate. -*/ -@safe unittest { - static union Test { - typeof(null) null_; - int count; - int difference; - } - - alias TA = TaggedAlgebraic!Test; - - TA ta; - ta = TA(12, TA.Kind.count); - assert(ta.kind == TA.Kind.count); - assert(ta == 12); - - ta = null; - assert(ta.kind == TA.Kind.null_); -} - -unittest { - // test proper type modifier support - static struct S { - void test() {} - void testI() immutable {} - void testC() const {} - void testS() shared {} - void testSC() shared const {} - } - static union U { - S s; - } - - auto u = TaggedAlgebraic!U(S.init); - const uc = u; - immutable ui = cast(immutable)u; - //const shared usc = cast(shared)u; - //shared us = cast(shared)u; - - static assert( is(typeof(u.test()))); - static assert(!is(typeof(u.testI()))); - static assert( is(typeof(u.testC()))); - static assert(!is(typeof(u.testS()))); - static assert(!is(typeof(u.testSC()))); - - static assert(!is(typeof(uc.test()))); - static assert(!is(typeof(uc.testI()))); - static assert( is(typeof(uc.testC()))); - static assert(!is(typeof(uc.testS()))); - static assert(!is(typeof(uc.testSC()))); - - static assert(!is(typeof(ui.test()))); - static assert( is(typeof(ui.testI()))); - static assert( is(typeof(ui.testC()))); - static assert(!is(typeof(ui.testS()))); - static assert( is(typeof(ui.testSC()))); - - /*static assert(!is(typeof(us.test()))); - static assert(!is(typeof(us.testI()))); - static assert(!is(typeof(us.testC()))); - static assert( is(typeof(us.testS()))); - static assert( is(typeof(us.testSC()))); - - static assert(!is(typeof(usc.test()))); - static assert(!is(typeof(usc.testI()))); - static assert(!is(typeof(usc.testC()))); - static assert(!is(typeof(usc.testS()))); - static assert( is(typeof(usc.testSC())));*/ -} - -unittest { - // test attributes on contained values - import std.typecons : Rebindable, rebindable; - - class C { - void test() {} - void testC() const {} - void testI() immutable {} - } - union U { - Rebindable!(immutable(C)) c; - } - - auto ta = TaggedAlgebraic!U(rebindable(new immutable C)); - static assert(!is(typeof(ta.test()))); - static assert( is(typeof(ta.testC()))); - static assert( is(typeof(ta.testI()))); -} - -version (unittest) { - // test recursive definition using a wrapper dummy struct - // (needed to avoid "no size yet for forward reference" errors) - template ID(What) { alias ID = What; } - private struct _test_Wrapper { - TaggedAlgebraic!_test_U u; - alias u this; - this(ARGS...)(ARGS args) { u = TaggedAlgebraic!_test_U(args); } - } - private union _test_U { - _test_Wrapper[] children; - int value; - } - unittest { - alias TA = _test_Wrapper; - auto ta = TA(null); - ta ~= TA(0); - ta ~= TA(1); - ta ~= TA([TA(2)]); - assert(ta[0] == 0); - assert(ta[1] == 1); - assert(ta[2][0] == 2); - } -} - -unittest { // postblit/destructor test - static struct S { - static int i = 0; - bool initialized = false; - this(bool) { initialized = true; i++; } - this(this) { if (initialized) i++; } - ~this() { if (initialized) i--; } - } - - static struct U { - S s; - int t; - } - alias TA = TaggedAlgebraic!U; - { - assert(S.i == 0); - auto ta = TA(S(true)); - assert(S.i == 1); - { - auto tb = ta; - assert(S.i == 2); - ta = tb; - assert(S.i == 2); - ta = 1; - assert(S.i == 1); - ta = S(true); - assert(S.i == 2); - } - assert(S.i == 1); - } - assert(S.i == 0); - - static struct U2 { - S a; - S b; - } - alias TA2 = TaggedAlgebraic!U2; - { - auto ta2 = TA2(S(true), TA2.Kind.a); - assert(S.i == 1); - } - assert(S.i == 0); -} - -unittest { - static struct S { - union U { - int i; - string s; - U[] a; - } - alias TA = TaggedAlgebraic!U; - TA p; - alias p this; - } - S s = S(S.TA("hello")); - assert(cast(string)s == "hello"); -} - -unittest { // multiple operator choices - union U { - int i; - double d; - } - alias TA = TaggedAlgebraic!U; - TA ta = 12; - static assert(is(typeof(ta + 10) == TA)); // ambiguous, could be int or double - assert((ta + 10).kind == TA.Kind.i); - assert(ta + 10 == 22); - static assert(is(typeof(ta + 10.5) == double)); - assert(ta + 10.5 == 22.5); -} - -unittest { // Binary op between two TaggedAlgebraic values - union U { int i; } - alias TA = TaggedAlgebraic!U; - - TA a = 1, b = 2; - static assert(is(typeof(a + b) == int)); - assert(a + b == 3); -} - -unittest { // Ambiguous binary op between two TaggedAlgebraic values - union U { int i; double d; } - alias TA = TaggedAlgebraic!U; - - TA a = 1, b = 2; - static assert(is(typeof(a + b) == TA)); - assert((a + b).kind == TA.Kind.i); - assert(a + b == 3); -} - -unittest { - struct S { - union U { - @disableIndex string str; - S[] array; - S[string] object; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - } - - S a = S(S.TA("hello")); - S b = S(S.TA(["foo": a])); - S c = S(S.TA([a])); - assert(b["foo"] == a); - assert(b["foo"] == "hello"); - assert(c[0] == a); - assert(c[0] == "hello"); -} - - -/** Tests if the algebraic type stores a value of a certain data type. -*/ -bool hasType(T, U)(in ref TaggedAlgebraic!U ta) -{ - alias Fields = Filter!(fieldMatchesType!(U, T), ta.fieldNames); - static assert(Fields.length > 0, "Type "~T.stringof~" cannot be stored in a "~(TaggedAlgebraic!U).stringof~"."); - - switch (ta.kind) { - default: return false; - foreach (i, fname; Fields) - case __traits(getMember, ta.Kind, fname): - return true; - } - assert(false); // never reached -} - -/// -unittest { - union Fields { - int number; - string text; - } - - TaggedAlgebraic!Fields ta = "test"; - - assert(ta.hasType!string); - assert(!ta.hasType!int); - - ta = 42; - assert(ta.hasType!int); - assert(!ta.hasType!string); -} - -unittest { // issue #1 - union U { - int a; - int b; - } - alias TA = TaggedAlgebraic!U; - - TA ta = TA(0, TA.Kind.b); - static assert(!is(typeof(ta.hasType!double))); - assert(ta.hasType!int); -} - -/** Gets the value stored in an algebraic type based on its data type. -*/ -ref inout(T) get(T, U)(ref inout(TaggedAlgebraic!U) ta) -{ - assert(hasType!(T, U)(ta)); - return ta.trustedGet!T; -} - -/// Convenience type that can be used for union fields that have no value (`void` is not allowed). -struct Void {} - -/// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member. -@property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); } - -private struct DisableOpAttribute { - OpKind kind; - string name; -} - - -private template hasOp(TA, OpKind kind, string name, ARGS...) -{ - import std.traits : CopyTypeQualifiers; - alias UQ = CopyTypeQualifiers!(TA, TA.Union); - enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0; -} - -unittest { - static struct S { - void m(int i) {} - bool opEquals(int i) { return true; } - bool opEquals(S s) { return true; } - } - - static union U { int i; string s; S st; } - alias TA = TaggedAlgebraic!U; - - static assert(hasOp!(TA, OpKind.binary, "+", int)); - static assert(hasOp!(TA, OpKind.binary, "~", string)); - static assert(hasOp!(TA, OpKind.binary, "==", int)); - static assert(hasOp!(TA, OpKind.binary, "==", string)); - static assert(hasOp!(TA, OpKind.binary, "==", int)); - static assert(hasOp!(TA, OpKind.binary, "==", S)); - static assert(hasOp!(TA, OpKind.method, "m", int)); - static assert(hasOp!(TA, OpKind.binary, "+=", int)); - static assert(!hasOp!(TA, OpKind.binary, "~", int)); - static assert(!hasOp!(TA, OpKind.binary, "~", int)); - static assert(!hasOp!(TA, OpKind.method, "m", string)); - static assert(!hasOp!(TA, OpKind.method, "m")); - static assert(!hasOp!(const(TA), OpKind.binary, "+=", int)); - static assert(!hasOp!(const(TA), OpKind.method, "m", int)); -} - -unittest { - struct S { - union U { - string s; - S[] arr; - S[string] obj; - } - alias TA = TaggedAlgebraic!(S.U); - TA payload; - alias payload this; - } - static assert(hasOp!(S.TA, OpKind.index, null, size_t)); - static assert(hasOp!(S.TA, OpKind.index, null, int)); - static assert(hasOp!(S.TA, OpKind.index, null, string)); - static assert(hasOp!(S.TA, OpKind.field, "length")); -} - -unittest { // "in" operator - union U { - string[string] dict; - } - alias TA = TaggedAlgebraic!U; - auto ta = TA(["foo": "bar"]); - assert("foo" in ta); - assert(*("foo" in ta) == "bar"); -} - -private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args) -{ - import std.array : join; - import std.traits : CopyTypeQualifiers; - import std.variant : Algebraic, Variant; - alias UQ = CopyTypeQualifiers!(T, T.Union); - - alias info = OpInfo!(UQ, kind, name, ARGS); - - static assert(hasOp!(T, kind, name, ARGS)); - - static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type."); - - //pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof); - //pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof); - //pragma(msg, typeof(T.Union.tupleof)); - //import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes)); - - switch (self.m_kind) { - default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", ")); - foreach (i, f; info.fields) { - alias FT = typeof(__traits(getMember, T.Union, f)); - case __traits(getMember, T.Kind, f): - static if (NoDuplicates!(info.ReturnTypes).length == 1) - return info.perform(self.trustedGet!FT, args); - else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes)) - return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args)); - else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) { - alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes)); - info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args); - import std.traits : isInstanceOf; - static if (isInstanceOf!(TaggedAlgebraic, typeof(ret))) return Alg(ret.payload); - else return Alg(ret); - } - else static if (is(FT == Variant)) - return info.perform(self.trustedGet!FT, args); - else - return Variant(info.perform(self.trustedGet!FT, args)); - } - } - - assert(false); // never reached -} - -unittest { // opIndex on recursive TA with closed return value set - static struct S { - union U { - char ch; - string str; - S[] arr; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - - this(T)(T t) { this.payload = t; } - } - S a = S("foo"); - S s = S([a]); - - assert(implementOp!(OpKind.field, "length")(s.payload) == 1); - static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA)); - assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - -unittest { // opIndex on recursive TA with closed return value set using @disableIndex - static struct S { - union U { - @disableIndex string str; - S[] arr; - } - alias TA = TaggedAlgebraic!U; - TA payload; - alias payload this; - - this(T)(T t) { this.payload = t; } - } - S a = S("foo"); - S s = S([a]); - - assert(implementOp!(OpKind.field, "length")(s.payload) == 1); - static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S)); - assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo"); -} - - -private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ - static if (kind == OpKind.binary) return mixin("value "~name~" args[0]"); - else static if (kind == OpKind.binaryRight) return mixin("args[0] "~name~" value"); - else static if (kind == OpKind.unary) return mixin("name "~value); - else static if (kind == OpKind.method) return __traits(getMember, value, name)(args); - else static if (kind == OpKind.field) return __traits(getMember, value, name); - else static if (kind == OpKind.index) return value[args]; - else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0]; - else static if (kind == OpKind.call) return value(args); - else static assert(false, "Unsupported kind of operator: "~kind.stringof); -} - -unittest { - union U { int i; string s; } - - { int v = 1; assert(performOpRaw!(U, OpKind.binary, "+")(v, 3) == 4); } - { string v = "foo"; assert(performOpRaw!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } -} - - -private auto performOp(U, OpKind kind, string name, T, ARGS...)(ref T value, /*auto ref*/ ARGS args) -{ - import std.traits : isInstanceOf; - static if (ARGS.length > 0 && isInstanceOf!(TaggedAlgebraic, ARGS[0])) { - static if (is(typeof(performOpRaw!(U, kind, name, T, ARGS)(value, args)))) { - return performOpRaw!(U, kind, name, T, ARGS)(value, args); - } else { - alias TA = ARGS[0]; - template MTypesImpl(size_t i) { - static if (i < TA.FieldTypes.length) { - alias FT = TA.FieldTypes[i]; - static if (is(typeof(&performOpRaw!(U, kind, name, T, FT, ARGS[1 .. $])))) - alias MTypesImpl = TypeTuple!(FT, MTypesImpl!(i+1)); - else alias MTypesImpl = TypeTuple!(MTypesImpl!(i+1)); - } else alias MTypesImpl = TypeTuple!(); - } - alias MTypes = NoDuplicates!(MTypesImpl!0); - static assert(MTypes.length > 0, "No type of the TaggedAlgebraic parameter matches any function declaration."); - static if (MTypes.length == 1) { - if (args[0].hasType!(MTypes[0])) - return performOpRaw!(U, kind, name)(value, args[0].get!(MTypes[0]), args[1 .. $]); - } else { - // TODO: allow all return types (fall back to Algebraic or Variant) - foreach (FT; MTypes) { - if (args[0].hasType!FT) - return ARGS[0](performOpRaw!(U, kind, name)(value, args[0].get!FT, args[1 .. $])); - } - } - throw new /*InvalidAgument*/Exception("Algebraic parameter type mismatch"); - } - } else return performOpRaw!(U, kind, name, T, ARGS)(value, args); -} - -unittest { - union U { int i; double d; string s; } - - { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, 3) == 4); } - { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, "bar") == "foobar"); } - { string v = "foo"; assert(performOp!(U, OpKind.binary, "~")(v, TaggedAlgebraic!U("bar")) == "foobar"); } - { int v = 1; assert(performOp!(U, OpKind.binary, "+")(v, TaggedAlgebraic!U(3)) == 4); } -} - - -private template OpInfo(U, OpKind kind, string name, ARGS...) -{ - import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType; - - private alias FieldTypes = FieldTypeTuple!U; - private alias fieldNames = FieldNameTuple!U; - - private template isOpEnabled(string field) - { - alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field))); - template impl(size_t i) { - static if (i < attribs.length) { - static if (is(typeof(attribs[i]) == DisableOpAttribute)) { - static if (kind == attribs[i].kind && name == attribs[i].name) - enum impl = false; - else enum impl = impl!(i+1); - } else enum impl = impl!(i+1); - } else enum impl = true; - } - enum isOpEnabled = impl!0; - } - - template fieldsImpl(size_t i) - { - static if (i < FieldTypes.length) { - static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) { - alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1)); - } else alias fieldsImpl = fieldsImpl!(i+1); - } else alias fieldsImpl = TypeTuple!(); - } - alias fields = fieldsImpl!0; - - template ReturnTypesImpl(size_t i) { - static if (i < fields.length) { - alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i]))); - alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1)); - } else alias ReturnTypesImpl = TypeTuple!(); - } - alias ReturnTypes = ReturnTypesImpl!0; - - static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); } -} - -private template ImplicitUnqual(T) { - import std.traits : Unqual, hasAliasing; - static if (is(T == void)) alias ImplicitUnqual = void; - else { - private static struct S { T t; } - static if (hasAliasing!S) alias ImplicitUnqual = T; - else alias ImplicitUnqual = Unqual!T; - } -} - -private enum OpKind { - binary, - binaryRight, - unary, - method, - field, - index, - indexAssign, - call -} - -private template TypeEnum(U) -{ - import std.array : join; - import std.traits : FieldNameTuple; - mixin("enum TypeEnum { " ~ [FieldNameTuple!U].join(", ") ~ " }"); -} - -private string generateConstructors(U)() -{ - import std.algorithm : map; - import std.array : join; - import std.string : format; - import std.traits : FieldTypeTuple; - - string ret; - - // disable default construction if first type is not a null/Void type - static if (!is(FieldTypeTuple!U[0] == typeof(null)) && !is(FieldTypeTuple!U[0] == Void)) - { - ret ~= q{ - @disable this(); - }; - } - - // normal type constructors - foreach (tname; UniqueTypeFields!U) - ret ~= q{ - this(typeof(U.%s) value) - { - m_data.rawEmplace(value); - m_kind = Kind.%s; - } - - void opAssign(typeof(U.%s) value) - { - if (m_kind != Kind.%s) { - // NOTE: destroy(this) doesn't work for some opDispatch-related reason - static if (is(typeof(&this.__xdtor))) - this.__xdtor(); - m_data.rawEmplace(value); - } else { - trustedGet!"%s" = value; - } - m_kind = Kind.%s; - } - }.format(tname, tname, tname, tname, tname, tname); - - // type constructors with explicit type tag - foreach (tname; AmbiguousTypeFields!U) - ret ~= q{ - this(typeof(U.%s) value, Kind type) - { - assert(type.among!(%s), format("Invalid type ID for type %%s: %%s", typeof(U.%s).stringof, type)); - m_data.rawEmplace(value); - m_kind = type; - } - }.format(tname, [SameTypeFields!(U, tname)].map!(f => "Kind."~f).join(", "), tname); - - return ret; -} - -private template UniqueTypeFields(U) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - alias T = Types[i]; - static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) < 0) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = TypeTuple!(impl!(i+1)); - } else alias impl = TypeTuple!(); - } - alias UniqueTypeFields = impl!0; -} - -private template AmbiguousTypeFields(U) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - alias T = Types[i]; - static if (staticIndexOf!(T, Types) == i && staticIndexOf!(T, Types[i+1 .. $]) >= 0) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = impl!(i+1); - } else alias impl = TypeTuple!(); - } - alias AmbiguousTypeFields = impl!0; -} - -unittest { - union U { - int a; - string b; - int c; - double d; - } - static assert([UniqueTypeFields!U] == ["b", "d"]); - static assert([AmbiguousTypeFields!U] == ["a"]); -} - -private template SameTypeFields(U, string field) { - import std.traits : FieldTypeTuple, FieldNameTuple; - - alias Types = FieldTypeTuple!U; - - alias T = typeof(__traits(getMember, U, field)); - template impl(size_t i) { - static if (i < Types.length) { - enum name = FieldNameTuple!U[i]; - static if (is(Types[i] == T)) - alias impl = TypeTuple!(name, impl!(i+1)); - else alias impl = TypeTuple!(impl!(i+1)); - } else alias impl = TypeTuple!(); - } - alias SameTypeFields = impl!0; -} - -private template MemberType(U) { - template MemberType(string name) { - alias MemberType = typeof(__traits(getMember, U, name)); - } -} - -private template isMatchingType(U) { - import std.traits : FieldTypeTuple; - enum isMatchingType(T) = staticIndexOf!(T, FieldTypeTuple!U) >= 0; -} - -private template isMatchingUniqueType(U) { - import std.traits : staticMap; - alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U); - template isMatchingUniqueType(T) { - static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true; - else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0; - } -} - -private template fieldMatchesType(U, T) -{ - enum fieldMatchesType(string field) = is(typeof(__traits(getMember, U, field)) == T); -} - -private template FieldTypeOf(U) { - template FieldTypeOf(string name) { - alias FieldTypeOf = typeof(__traits(getMember, U, name)); - } -} - -private template staticIndexOfImplicit(T, Types...) { - template impl(size_t i) { - static if (i < Types.length) { - static if (is(T : Types[i])) enum impl = i; - else enum impl = impl!(i+1); - } else enum impl = -1; - } - enum staticIndexOfImplicit = impl!0; -} - -unittest { - static assert(staticIndexOfImplicit!(immutable(char), char) == 0); - static assert(staticIndexOfImplicit!(int, long) == 0); - static assert(staticIndexOfImplicit!(long, int) < 0); - static assert(staticIndexOfImplicit!(int, int, double) == 0); - static assert(staticIndexOfImplicit!(double, int, double) == 1); -} - - -private template isNoVariant(T) { - import std.variant : Variant; - enum isNoVariant = !is(T == Variant); -} - -private void rawEmplace(T)(void[] dst, ref T src) -{ - T* tdst = () @trusted { return cast(T*)dst.ptr; } (); - static if (is(T == class)) { - *tdst = src; - } else { - import std.conv : emplace; - emplace(tdst); - *tdst = src; - } -} diff --git a/src/sdlang/token.d b/src/sdlang/token.d deleted file mode 100644 index 0a5b2fd..0000000 --- a/src/sdlang/token.d +++ /dev/null @@ -1,550 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.token; - -import std.array; -import std.base64; -import std.conv; -import std.datetime; -import std.meta; -import std.range; -import std.string; -import std.traits; -import std.typetuple; -import std.variant; - -import sdlang.exception; -import sdlang.symbol; -import sdlang.util; - -/// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does. -/// So this is needed for any SDL "Date Time" that doesn't include a time zone. -struct DateTimeFrac -{ - DateTime dateTime; - Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } -} - -/++ -If a "Date Time" literal in the SDL file has a time zone that's not found in -your system, you get one of these instead of a SysTime. (Because it's -impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.) - -The difference between this and `DateTimeFrac` is that `DateTimeFrac` -indicates that no time zone was specified in the SDL at all, whereas -`DateTimeFracUnknownZone` indicates that a time zone was specified but -data for it could not be found on your system. -+/ -struct DateTimeFracUnknownZone -{ - DateTime dateTime; - Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } - string timeZone; - - bool opEquals(const DateTimeFracUnknownZone b) const - { - return opEquals(b); - } - bool opEquals(ref const DateTimeFracUnknownZone b) const - { - return - this.dateTime == b.dateTime && - this.fracSecs == b.fracSecs && - this.timeZone == b.timeZone; - } -} - -/++ -SDLang's datatypes map to D's datatypes as described below. -Most are straightforward, but take special note of the date/time-related types. - ---------------------------------------------------------------- -Boolean: bool -Null: typeof(null) -Unicode Character: dchar -Double-Quote Unicode String: string -Raw Backtick Unicode String: string -Integer (32 bits signed): int -Long Integer (64 bits signed): long -Float (32 bits signed): float -Double Float (64 bits signed): double -Decimal (128+ bits signed): real -Binary (standard Base64): ubyte[] -Time Span: Duration - -Date (with no time at all): Date -Date Time (no timezone): DateTimeFrac -Date Time (with a known timezone): SysTime -Date Time (with an unknown timezone): DateTimeFracUnknownZone ---------------------------------------------------------------- -+/ -alias ValueTypes = TypeTuple!( - bool, - string, dchar, - int, long, - float, double, real, - Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration, - ubyte[], - typeof(null), -); - -alias Value = Algebraic!( ValueTypes ); ///ditto -enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1; - -enum isSink(T) = - isOutputRange!T && - is(ElementType!(T)[] == string); - -string toSDLString(T)(T value) if(is(T==Value) || isValueType!T) -{ - Appender!string sink; - toSDLString(value, sink); - return sink.data; -} - -/// Throws SDLangException if value is infinity, -infinity or NaN, because -/// those are not currently supported by the SDLang spec. -void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - foreach(T; ValueTypes) - { - if(value.type == typeid(T)) - { - toSDLString( value.get!T(), sink ); - return; - } - } - - throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString()); -} - -@("toSDLString on infinity and NaN") -unittest -{ - import std.exception; - - auto floatInf = float.infinity; - auto floatNegInf = -float.infinity; - auto floatNaN = float.nan; - - auto doubleInf = double.infinity; - auto doubleNegInf = -double.infinity; - auto doubleNaN = double.nan; - - auto realInf = real.infinity; - auto realNegInf = -real.infinity; - auto realNaN = real.nan; - - assertNotThrown( toSDLString(0.0F) ); - assertNotThrown( toSDLString(0.0) ); - assertNotThrown( toSDLString(0.0L) ); - - assertThrown!ValidationException( toSDLString(floatInf) ); - assertThrown!ValidationException( toSDLString(floatNegInf) ); - assertThrown!ValidationException( toSDLString(floatNaN) ); - - assertThrown!ValidationException( toSDLString(doubleInf) ); - assertThrown!ValidationException( toSDLString(doubleNegInf) ); - assertThrown!ValidationException( toSDLString(doubleNaN) ); - - assertThrown!ValidationException( toSDLString(realInf) ); - assertThrown!ValidationException( toSDLString(realNegInf) ); - assertThrown!ValidationException( toSDLString(realNaN) ); - - assertThrown!ValidationException( toSDLString(Value(floatInf)) ); - assertThrown!ValidationException( toSDLString(Value(floatNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(floatNaN)) ); - - assertThrown!ValidationException( toSDLString(Value(doubleInf)) ); - assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(doubleNaN)) ); - - assertThrown!ValidationException( toSDLString(Value(realInf)) ); - assertThrown!ValidationException( toSDLString(Value(realNegInf)) ); - assertThrown!ValidationException( toSDLString(Value(realNaN)) ); -} - -void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put("null"); -} - -void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put(value? "true" : "false"); -} - -//TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep -void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('"'); - - // This loop is UTF-safe - foreach(char ch; value) - { - if (ch == '\n') sink.put(`\n`); - else if(ch == '\r') sink.put(`\r`); - else if(ch == '\t') sink.put(`\t`); - else if(ch == '\"') sink.put(`\"`); - else if(ch == '\\') sink.put(`\\`); - else - sink.put(ch); - } - - sink.put('"'); -} - -void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('\''); - - if (value == '\n') sink.put(`\n`); - else if(value == '\r') sink.put(`\r`); - else if(value == '\t') sink.put(`\t`); - else if(value == '\'') sink.put(`\'`); - else if(value == '\\') sink.put(`\\`); - else - sink.put(value); - - sink.put('\''); -} - -void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put( "%s".format(value) ); -} - -void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put( "%sL".format(value) ); -} - -private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T) -{ - import std.exception; - import std.math; - - enforce!ValidationException( - !isInfinity(value), - "SDLang does not currently support infinity for floating-point types" - ); - - enforce!ValidationException( - !isNaN(value), - "SDLang does not currently support NaN for floating-point types" - ); -} - -void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.10sF".format(value) ); -} - -void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.30sD".format(value) ); -} - -void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - checkUnsupportedFloatingPoint(value); - sink.put( "%.30sBD".format(value) ); -} - -void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put(to!string(value.year)); - sink.put('/'); - sink.put(to!string(cast(int)value.month)); - sink.put('/'); - sink.put(to!string(value.day)); -} - -void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - toSDLString(value.dateTime.date, sink); - sink.put(' '); - sink.put("%.2s".format(value.dateTime.hour)); - sink.put(':'); - sink.put("%.2s".format(value.dateTime.minute)); - - if(value.dateTime.second != 0) - { - sink.put(':'); - sink.put("%.2s".format(value.dateTime.second)); - } - - if(value.fracSecs != 0.msecs) - { - sink.put('.'); - sink.put("%.3s".format(value.fracSecs.total!"msecs")); - } -} - -void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs); - toSDLString(dateTimeFrac, sink); - - sink.put("-"); - - auto tzString = value.timezone.name; - - // If name didn't exist, try abbreviation. - // Note that according to std.datetime docs, on Windows the - // stdName/dstName may not be properly abbreviated. - version(Windows) {} else - if(tzString == "") - { - auto tz = value.timezone; - auto stdTime = value.stdTime; - - if(tz.hasDST()) - tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName; - else - tzString = tz.stdName; - } - - if(tzString == "") - { - auto offset = value.timezone.utcOffsetAt(value.stdTime); - sink.put("GMT"); - - if(offset < seconds(0)) - { - sink.put("-"); - offset = -offset; - } - else - sink.put("+"); - - sink.put("%.2s".format(offset.split.hours)); - sink.put(":"); - sink.put("%.2s".format(offset.split.minutes)); - } - else - sink.put(tzString); -} - -void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs); - toSDLString(dateTimeFrac, sink); - - sink.put("-"); - sink.put(value.timeZone); -} - -void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - if(value < seconds(0)) - { - sink.put("-"); - value = -value; - } - - auto days = value.total!"days"(); - if(days != 0) - { - sink.put("%s".format(days)); - sink.put("d:"); - } - - sink.put("%.2s".format(value.split.hours)); - sink.put(':'); - sink.put("%.2s".format(value.split.minutes)); - sink.put(':'); - sink.put("%.2s".format(value.split.seconds)); - - if(value.split.msecs != 0) - { - sink.put('.'); - sink.put("%.3s".format(value.split.msecs)); - } -} - -void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char)) -{ - sink.put('['); - sink.put( Base64.encode(value) ); - sink.put(']'); -} - -/// This only represents terminals. Nonterminals aren't -/// constructed since the AST is directly built during parsing. -struct Token -{ - Symbol symbol = sdlang.symbol.symbol!"Error"; /// The "type" of this token - Location location; - Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null - string data; /// Original text from source - - @disable this(); - this(Symbol symbol, Location location, Value value=Value(null), string data=null) - { - this.symbol = symbol; - this.location = location; - this.value = value; - this.data = data; - } - - /// Tokens with differing symbols are always unequal. - /// Tokens with differing values are always unequal. - /// Tokens with differing Value types are always unequal. - /// Member `location` is always ignored for comparison. - /// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident. - bool opEquals(Token b) - { - return opEquals(b); - } - bool opEquals(ref Token b) ///ditto - { - if( - this.symbol != b.symbol || - this.value.type != b.value.type || - this.value != b.value - ) - return false; - - if(this.symbol == .symbol!"Ident") - return this.data == b.data; - - return true; - } - - bool matches(string symbolName)() - { - return this.symbol == .symbol!symbolName; - } -} - -@("sdlang token") -unittest -{ - auto loc = Location("", 0, 0, 0); - auto loc2 = Location("a", 1, 1, 1); - - assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc )); - assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2)); - assert(Token(symbol!":", loc) == Token(symbol!":", loc )); - assert(Token(symbol!"EOL",loc) != Token(symbol!":", loc )); - assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n")); - - assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" )); - assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" )); - assert(Token(symbol!":", loc,Value(null),"A" ) == Token(symbol!":", loc,Value(null),"BB")); - assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":", loc,Value(null),"A" )); - - assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo")); - assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR")); - - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo")); - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo")); - assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") == Token(symbol!"Value",loc, Value( 7),"BAR")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo")); - assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( 2),"foo")); - assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7))); - assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2))); -} - -@("sdlang Value.toSDLString()") -unittest -{ - // Bool and null - assert(Value(null ).toSDLString() == "null"); - assert(Value(true ).toSDLString() == "true"); - assert(Value(false).toSDLString() == "false"); - - // Base64 Binary - assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]"); - - // Integer - assert(Value(cast( int) 7).toSDLString() == "7"); - assert(Value(cast( int)-7).toSDLString() == "-7"); - assert(Value(cast( int) 0).toSDLString() == "0"); - - assert(Value(cast(long) 7).toSDLString() == "7L"); - assert(Value(cast(long)-7).toSDLString() == "-7L"); - assert(Value(cast(long) 0).toSDLString() == "0L"); - - // Floating point - assert(Value(cast(float) 1.5).toSDLString() == "1.5F"); - assert(Value(cast(float)-1.5).toSDLString() == "-1.5F"); - assert(Value(cast(float) 0).toSDLString() == "0F"); - - assert(Value(cast(double) 1.5).toSDLString() == "1.5D"); - assert(Value(cast(double)-1.5).toSDLString() == "-1.5D"); - assert(Value(cast(double) 0).toSDLString() == "0D"); - - assert(Value(cast(real) 1.5).toSDLString() == "1.5BD"); - assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD"); - assert(Value(cast(real) 0).toSDLString() == "0BD"); - - // String - assert(Value("hello" ).toSDLString() == `"hello"`); - assert(Value(" hello ").toSDLString() == `" hello "`); - assert(Value("" ).toSDLString() == `""`); - assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`); - assert(Value("日本語").toSDLString() == `"日本語"`); - - // Chars - assert(Value(cast(dchar) 'A').toSDLString() == `'A'`); - assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`); - assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`); - assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`); - assert(Value(cast(dchar)'\'').toSDLString() == `'\''`); - assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`); - assert(Value(cast(dchar) '月').toSDLString() == `'月'`); - - // Date - assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31"); - assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31"); - - // DateTimeFrac w/o Frac - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15))).toSDLString() == "2004/10/31 14:30:15"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 1, 2, 3))).toSDLString() == "2004/10/31 01:02:03"); - assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15"); - - // DateTimeFrac w/ Frac - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 12.msecs)).toSDLString() == "2004/10/31 14:30:15.012"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 1.msecs)).toSDLString() == "2004/10/31 14:30:15.001"); - assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123"); - - // DateTimeFracUnknownZone - assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar"); - - // SysTime - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00"); - assert(Value(SysTime(DateTime(2004,10,31, 1, 2, 3), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 01:02:03-GMT+00:00"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00"); - - // Duration - assert( "12:14:42" == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0)).toSDLString()); - assert("-12:14:42" == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0)).toSDLString()); - assert( "00:09:12" == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0)).toSDLString()); - assert( "00:00:01.023" == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString()); - assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString()); - assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString()); - assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString()); - assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString()); - assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString()); - assert( "23d:05:21:23" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0)).toSDLString()); -} diff --git a/src/sdlang/util.d b/src/sdlang/util.d deleted file mode 100644 index d192ea2..0000000 --- a/src/sdlang/util.d +++ /dev/null @@ -1,200 +0,0 @@ -// SDLang-D -// Written in the D programming language. - -module sdlang.util; - -import std.algorithm; -import std.array; -import std.conv; -import std.datetime; -import std.range; -import std.stdio; -import std.string; - -import sdlang.exception; -import sdlang.token; - -enum sdlangVersion = "0.9.1"; - -alias immutable(ubyte)[] ByteString; - -auto startsWith(T)(string haystack, T needle) - if( is(T:ByteString) || is(T:string) ) -{ - return std.algorithm.startsWith( cast(ByteString)haystack, cast(ByteString)needle ); -} - -struct Location -{ - string file; /// Filename (including path) - int line; /// Zero-indexed - int col; /// Zero-indexed, Tab counts as 1 - size_t index; /// Index into the source - - this(int line, int col, int index) - { - this.line = line; - this.col = col; - this.index = index; - } - - this(string file, int line, int col, int index) - { - this.file = file; - this.line = line; - this.col = col; - this.index = index; - } - - /// Convert to string. Optionally takes output range as a sink. - string toString() - { - Appender!string sink; - this.toString(sink); - return sink.data; - } - - ///ditto - void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) - { - sink.put(file); - sink.put("("); - sink.put(to!string(line+1)); - sink.put(":"); - sink.put(to!string(col+1)); - sink.put(")"); - } -} - -struct FullName -{ - string namespace; - string name; - - /// Convert to string. Optionally takes output range as a sink. - string toString() - { - if(namespace == "") - return name; - - Appender!string sink; - this.toString(sink); - return sink.data; - } - - ///ditto - void toString(Sink)(ref Sink sink) if(isOutputRange!(Sink,char)) - { - if(namespace != "") - { - sink.put(namespace); - sink.put(":"); - } - - sink.put(name); - } - - /// - static string combine(string namespace, string name) - { - return FullName(namespace, name).toString(); - } - /// - @("FullName.combine example") - unittest - { - assert(FullName.combine("", "name") == "name"); - assert(FullName.combine("*", "name") == "*:name"); - assert(FullName.combine("namespace", "name") == "namespace:name"); - } - - /// - static FullName parse(string fullName) - { - FullName result; - - auto parts = fullName.findSplit(":"); - if(parts[1] == "") // No colon - { - result.namespace = ""; - result.name = parts[0]; - } - else - { - result.namespace = parts[0]; - result.name = parts[2]; - } - - return result; - } - /// - @("FullName.parse example") - unittest - { - assert(FullName.parse("name") == FullName("", "name")); - assert(FullName.parse("*:name") == FullName("*", "name")); - assert(FullName.parse("namespace:name") == FullName("namespace", "name")); - } - - /// Throws with appropriate message if this.name is "*". - /// Wildcards are only supported for namespaces, not names. - void ensureNoWildcardName(string extaMsg = null) - { - if(name == "*") - throw new ArgumentException(`Wildcards ("*") only allowed for namespaces, not names. `~extaMsg); - } -} -struct Foo { string foo; } - -void removeIndex(E)(ref E[] arr, ptrdiff_t index) -{ - arr = arr[0..index] ~ arr[index+1..$]; -} - -void trace(string file=__FILE__, size_t line=__LINE__, TArgs...)(TArgs args) -{ - version(sdlangTrace) - { - writeln(file, "(", line, "): ", args); - stdout.flush(); - } -} - -string toString(TypeInfo ti) -{ - if (ti == typeid( bool )) return "bool"; - else if(ti == typeid( string )) return "string"; - else if(ti == typeid( dchar )) return "dchar"; - else if(ti == typeid( int )) return "int"; - else if(ti == typeid( long )) return "long"; - else if(ti == typeid( float )) return "float"; - else if(ti == typeid( double )) return "double"; - else if(ti == typeid( real )) return "real"; - else if(ti == typeid( Date )) return "Date"; - else if(ti == typeid( DateTimeFrac )) return "DateTimeFrac"; - else if(ti == typeid( DateTimeFracUnknownZone )) return "DateTimeFracUnknownZone"; - else if(ti == typeid( SysTime )) return "SysTime"; - else if(ti == typeid( Duration )) return "Duration"; - else if(ti == typeid( ubyte[] )) return "ubyte[]"; - else if(ti == typeid( typeof(null) )) return "null"; - - return "{unknown}"; -} - -enum BOM { - UTF8, /// UTF-8 - UTF16LE, /// UTF-16 (little-endian) - UTF16BE, /// UTF-16 (big-endian) - UTF32LE, /// UTF-32 (little-endian) - UTF32BE, /// UTF-32 (big-endian) -} - -enum NBOM = __traits(allMembers, BOM).length; -immutable ubyte[][NBOM] ByteOrderMarks = -[ - [0xEF, 0xBB, 0xBF], //UTF8 - [0xFF, 0xFE], //UTF16LE - [0xFE, 0xFF], //UTF16BE - [0xFF, 0xFE, 0x00, 0x00], //UTF32LE - [0x00, 0x00, 0xFE, 0xFF] //UTF32BE -]; diff --git a/src/sdp/ao_abstract_doc_source.d b/src/sdp/ao_abstract_doc_source.d index db814ac..c7e5a13 100644 --- a/src/sdp/ao_abstract_doc_source.d +++ b/src/sdp/ao_abstract_doc_source.d @@ -1,5 +1,6 @@ -/+ - document abstraction +/++ + document abstraction: + abstraction of sisu markup for downstream processing ao_abstract_doc_source.d +/ template SiSUdocAbstraction() { @@ -14,10 +15,6 @@ template SiSUdocAbstraction() { /+ ↓ abstraction mixins +/ mixin ObjectSetter; mixin InternalMarkup; - // // mixin SiSUrgxInitFlags; - // // mixin AssertionsOnBlocks; - // mixin SiSUbiblio; // issue - // mixin SiSUheader; /+ ↓ abstraction struct init +/ /+ initialize +/ auto rgx = Rgx(); @@ -106,7 +103,6 @@ template SiSUdocAbstraction() { is_ ); } - // mixin SiSUdocAbstractionFunctions; /+ ↓ abstract marked up document +/ auto abstract_doc_source( char[][] markup_sourcefile_content, @@ -129,7 +125,6 @@ template SiSUdocAbstraction() { "para" : 0, ]; auto type = flags_type_init; - mixin ScreenTxtColors; void tell_lo(int obj_cite_number, in char[] line) { writefln( "* %s %s", @@ -201,10 +196,9 @@ template SiSUdocAbstraction() { } line = replaceAll(line, rgx.true_dollar, "$$$$"); // dollar represented as $$ needed to stop submatching on $ - // (substitutions using ${identifiers} must take into account (e.g. happen earlier)) + // (substitutions using ${identifiers} must take into account (i.e. happen earlier)) debug(source) { // source lines writeln(line); - // writeln(scr_txt_marker["green"], line); } debug(srclines) { if (!line.empty) { // source lines, not empty @@ -1870,16 +1864,13 @@ template SiSUdocAbstraction() { /+ abstraction functions ↑ +/ /+ ↓ abstraction function emitters +/ struct OCNemitter { - // class OCNemitter : AssertOCN { int obj_cite_number, obj_cite_number_; int obj_cite_number_emitter(int obj_cite_number_status_flag) in { assert(obj_cite_number_status_flag <= 2); } body { - if (obj_cite_number_status_flag == 0) { - obj_cite_number=++obj_cite_number_; - } else { - obj_cite_number=0; - } + obj_cite_number=(obj_cite_number_status_flag == 0) + ? ++obj_cite_number_ + : 0; assert(obj_cite_number >= 0); return obj_cite_number; } @@ -1887,7 +1878,6 @@ template SiSUdocAbstraction() { } } struct ObjAttributes { - // class ObjAttributes : AssertObjAttributes { string[string] obj_txt; string para_and_blocks(string obj_txt_in) in { } @@ -2417,14 +2407,11 @@ template SiSUdocAbstraction() { } } struct ObjAttrib { - // struct ObjAttrib : AssertObjAttrib { - // auto sink = appender!(char[])(); auto attrib = ObjAttributes(); string[string] obj_attrib; string obj_attributes(string obj_is_, string obj_raw, string node) in { } body { - // string s = "{ \"language\": \"D\", \"rating\": 3.14, \"code\": \"42\" }"; scope(exit) { // destroy(obj_is_); destroy(obj_raw); @@ -2488,9 +2475,7 @@ template SiSUdocAbstraction() { obj_attrib["json"] = oa_j.toString(); debug(structattrib) { if (oa_j["is"].str() == "heading") { - // writeln(__LINE__); writeln(obj_attrib["json"]); - // writeln(node); writeln( "is: ", oa_j["is"].str(), "; obj_cite_number: ", oa_j["obj_cite_number"].integer() @@ -2504,7 +2489,6 @@ template SiSUdocAbstraction() { } } struct BookIndexNuggetHash { - // class BookIndexNuggetHash : AssertBookIndexNuggetHash { string main_term, sub_term, sub_term_bits; int obj_cite_number_offset, obj_cite_number_endpoint; string[] obj_cite_numbers; @@ -2859,7 +2843,6 @@ template SiSUdocAbstraction() { ++mkn; foreach (endnote; endnotes_) { attrib=""; - attrib=""; // endnotes ~= // set_abstract_object.contents_para( // obj, @@ -2957,7 +2940,6 @@ template SiSUdocAbstraction() { } } struct NodeStructureMetadata { - // class NodeStructureMetadata : AssertNodeJSON { int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7; int obj_cite_number; int[string] p_; // p_ parent_ diff --git a/src/sdp/ao_ansi_colors.d b/src/sdp/ao_ansi_colors.d index e5a46f9..dea331d 100644 --- a/src/sdp/ao_ansi_colors.d +++ b/src/sdp/ao_ansi_colors.d @@ -1,6 +1,5 @@ -/+ - utils - ao_util.d +/++ + ansi colors, depreciate use +/ template ScreenTxtColors() { string[string] scr_txt_color = [ diff --git a/src/sdp/ao_conf_make_meta.d b/src/sdp/ao_conf_make_meta.d index 04a9d7a..5bc9694 100644 --- a/src/sdp/ao_conf_make_meta.d +++ b/src/sdp/ao_conf_make_meta.d @@ -1,5 +1,12 @@ -/+ - extract native/orig header return associative array +/++ + extract native/orig header return associative array<BR> + + the header is passed as text (lopped off top of a sisu markup file until the + required first heading ^A~), determine whether is a native header or sdlang one + with a regex check if whether it contains the "native header" required tag/field + @title: then process accordingly as a "native header" or "sdlang header" + converting the metadata and make instructions to a common json format used by + program internally. Moved to associative array. +/ template SiSUheaderExtractHub() { private import diff --git a/src/sdp/ao_conf_make_meta_native.d b/src/sdp/ao_conf_make_meta_native.d index 9f0ad63..f70a7bf 100644 --- a/src/sdp/ao_conf_make_meta_native.d +++ b/src/sdp/ao_conf_make_meta_native.d @@ -1,4 +1,5 @@ -/+ +/++ + native headers using<br>@title:<BR>:subtitle:<BR>type tags<BR> extract native/orig header return associative array +/ template SiSUheaderExtractNative() { diff --git a/src/sdp/ao_conf_make_meta_sdlang.d b/src/sdp/ao_conf_make_meta_sdlang.d index 1cc3498..61b4960 100644 --- a/src/sdp/ao_conf_make_meta_sdlang.d +++ b/src/sdp/ao_conf_make_meta_sdlang.d @@ -1,5 +1,6 @@ -/+ - extract sdl header return sdl +/++ + sdlang headers<BR> + extract sdlang header return sdlang +/ template SiSUheaderExtractSDLang() { private import diff --git a/src/sdp/ao_defaults.d b/src/sdp/ao_defaults.d index ea5caae..8db42e2 100644 --- a/src/sdp/ao_defaults.d +++ b/src/sdp/ao_defaults.d @@ -1,6 +1,5 @@ -/+ - defaults - ao_defaults.d +/++ + default settings +/ template SiSUregisters() { string[string][string] conf_aa() { diff --git a/src/sdp/ao_object_setter.d b/src/sdp/ao_object_setter.d index 745de4e..6cb359b 100644 --- a/src/sdp/ao_object_setter.d +++ b/src/sdp/ao_object_setter.d @@ -1,5 +1,6 @@ -/+ - object setter +/++ + object setter: + setting of sisu objects for downstream processing ao_object_setter.d +/ template ObjectSetter() { diff --git a/src/sdp/ao_output_debugs.d b/src/sdp/ao_output_debugs.d index b5f96fa..9111cd6 100644 --- a/src/sdp/ao_output_debugs.d +++ b/src/sdp/ao_output_debugs.d @@ -1,6 +1,5 @@ -/+ +/++ output debugs - ao_output_debugs.d +/ template SiSUoutputDebugs() { struct BookIndexReport { diff --git a/src/sdp/ao_read_config_files.d b/src/sdp/ao_read_config_files.d index 49efe7b..013acdd 100644 --- a/src/sdp/ao_read_config_files.d +++ b/src/sdp/ao_read_config_files.d @@ -1,6 +1,7 @@ -/+ +/++ + read configuration files<BR> + - read config files<BR> ao_config_files.d - - read config files +/ template SiSUconfigIn() { private import diff --git a/src/sdp/ao_read_source_files.d b/src/sdp/ao_read_source_files.d index eabc4dc..5aef05d 100644 --- a/src/sdp/ao_read_source_files.d +++ b/src/sdp/ao_read_source_files.d @@ -1,9 +1,8 @@ -/+ - ao_read_source_files.d - - open markup files +/++ + module ao_read_source_files;<BR> + - open markup files<BR> - if master file scan for addional files to import/insert +/ -// module ao_read_source_files; template SiSUmarkupRaw() { private import std.exception, diff --git a/src/sdp/ao_rgx.d b/src/sdp/ao_rgx.d index ccaf1bd..2a10d53 100644 --- a/src/sdp/ao_rgx.d +++ b/src/sdp/ao_rgx.d @@ -1,6 +1,5 @@ -/+ - regex - ao_rgx.d +/++ + regex: regular expressions used in sisu document parser +/ template RgxInit() { struct Rgx { diff --git a/src/sdp/compile_time_info.d b/src/sdp/compile_time_info.d index 783ac62..2b0151d 100644 --- a/src/sdp/compile_time_info.d +++ b/src/sdp/compile_time_info.d @@ -1,6 +1,5 @@ -/+ +/++ compile_time_info - compile_time_info.d +/ template CompileTimeInfo() { version(Windows) { diff --git a/src/sdp/output_hub.d b/src/sdp/output_hub.d index 0206bf5..e7c0c9e 100644 --- a/src/sdp/output_hub.d +++ b/src/sdp/output_hub.d @@ -1,6 +1,6 @@ -/+ - output_hub.d - output_html.d +/++ + output hub<BR> + check & generate output types requested +/ template SiSUoutputHub() { struct SDPoutput { diff --git a/src/undead/doformat.d b/src/undead/doformat.d deleted file mode 100644 index 4fc0daf..0000000 --- a/src/undead/doformat.d +++ /dev/null @@ -1,1620 +0,0 @@ -// Written in the D programming language. - -/** - Copyright: Copyright Digital Mars 2000-2013. - - License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). - - Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, - Andrei Alexandrescu), and Kenji Hara - - Source: $(PHOBOSSRC std/_format.d) - */ -module undead.doformat; - -//debug=format; // uncomment to turn on debugging printf's - -import core.vararg; -import std.exception; -import std.meta; -import std.range.primitives; -import std.traits; -import std.format; - -version(CRuntime_DigitalMars) -{ - version = DigitalMarsC; -} - -version (DigitalMarsC) -{ - // This is DMC's internal floating point formatting function - extern (C) - { - extern shared char* function(int c, int flags, int precision, - in real* pdval, - char* buf, size_t* psl, int width) __pfloatfmt; - } -} - -/********************************************************************** - * Signals a mismatch between a format and its corresponding argument. - */ -class FormatException : Exception -{ - @safe pure nothrow - this() - { - super("format error"); - } - - @safe pure nothrow - this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) - { - super(msg, fn, ln, next); - } -} - - -// Legacy implementation - -enum Mangle : char -{ - Tvoid = 'v', - Tbool = 'b', - Tbyte = 'g', - Tubyte = 'h', - Tshort = 's', - Tushort = 't', - Tint = 'i', - Tuint = 'k', - Tlong = 'l', - Tulong = 'm', - Tfloat = 'f', - Tdouble = 'd', - Treal = 'e', - - Tifloat = 'o', - Tidouble = 'p', - Tireal = 'j', - Tcfloat = 'q', - Tcdouble = 'r', - Tcreal = 'c', - - Tchar = 'a', - Twchar = 'u', - Tdchar = 'w', - - Tarray = 'A', - Tsarray = 'G', - Taarray = 'H', - Tpointer = 'P', - Tfunction = 'F', - Tident = 'I', - Tclass = 'C', - Tstruct = 'S', - Tenum = 'E', - Ttypedef = 'T', - Tdelegate = 'D', - - Tconst = 'x', - Timmutable = 'y', -} - -// return the TypeInfo for a primitive type and null otherwise. This -// is required since for arrays of ints we only have the mangled char -// to work from. If arrays always subclassed TypeInfo_Array this -// routine could go away. -private TypeInfo primitiveTypeInfo(Mangle m) -{ - // BUG: should fix this in static this() to avoid double checked locking bug - __gshared TypeInfo[Mangle] dic; - if (!dic.length) - { - dic = [ - Mangle.Tvoid : typeid(void), - Mangle.Tbool : typeid(bool), - Mangle.Tbyte : typeid(byte), - Mangle.Tubyte : typeid(ubyte), - Mangle.Tshort : typeid(short), - Mangle.Tushort : typeid(ushort), - Mangle.Tint : typeid(int), - Mangle.Tuint : typeid(uint), - Mangle.Tlong : typeid(long), - Mangle.Tulong : typeid(ulong), - Mangle.Tfloat : typeid(float), - Mangle.Tdouble : typeid(double), - Mangle.Treal : typeid(real), - Mangle.Tifloat : typeid(ifloat), - Mangle.Tidouble : typeid(idouble), - Mangle.Tireal : typeid(ireal), - Mangle.Tcfloat : typeid(cfloat), - Mangle.Tcdouble : typeid(cdouble), - Mangle.Tcreal : typeid(creal), - Mangle.Tchar : typeid(char), - Mangle.Twchar : typeid(wchar), - Mangle.Tdchar : typeid(dchar) - ]; - } - auto p = m in dic; - return p ? *p : null; -} - -// This stuff has been removed from the docs and is planned for deprecation. -/* - * Interprets variadic argument list pointed to by argptr whose types - * are given by arguments[], formats them according to embedded format - * strings in the variadic argument list, and sends the resulting - * characters to putc. - * - * The variadic arguments are consumed in order. Each is formatted - * into a sequence of chars, using the default format specification - * for its type, and the characters are sequentially passed to putc. - * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is - * encountered, it is interpreted as a format string. As many - * arguments as specified in the format string are consumed and - * formatted according to the format specifications in that string and - * passed to putc. If there are too few remaining arguments, a - * $(D FormatException) is thrown. If there are more remaining arguments than - * needed by the format specification, the default processing of - * arguments resumes until they are all consumed. - * - * Params: - * putc = Output is sent do this delegate, character by character. - * arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. - * argptr = Points to variadic argument list. - * - * Throws: - * Mismatched arguments and formats result in a $(D FormatException) being thrown. - * - * Format_String: - * <a name="format-string">$(I Format strings)</a> - * consist of characters interspersed with - * $(I format specifications). Characters are simply copied - * to the output (such as putc) after any necessary conversion - * to the corresponding UTF-8 sequence. - * - * A $(I format specification) starts with a '%' character, - * and has the following grammar: - -$(CONSOLE -$(I FormatSpecification): - $(B '%%') - $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) - -$(I Flags): - $(I empty) - $(B '-') $(I Flags) - $(B '+') $(I Flags) - $(B '#') $(I Flags) - $(B '0') $(I Flags) - $(B ' ') $(I Flags) - -$(I Width): - $(I empty) - $(I Integer) - $(B '*') - -$(I Precision): - $(I empty) - $(B '.') - $(B '.') $(I Integer) - $(B '.*') - -$(I Integer): - $(I Digit) - $(I Digit) $(I Integer) - -$(I Digit): - $(B '0') - $(B '1') - $(B '2') - $(B '3') - $(B '4') - $(B '5') - $(B '6') - $(B '7') - $(B '8') - $(B '9') - -$(I FormatChar): - $(B 's') - $(B 'b') - $(B 'd') - $(B 'o') - $(B 'x') - $(B 'X') - $(B 'e') - $(B 'E') - $(B 'f') - $(B 'F') - $(B 'g') - $(B 'G') - $(B 'a') - $(B 'A') -) - $(DL - $(DT $(I Flags)) - $(DL - $(DT $(B '-')) - $(DD - Left justify the result in the field. - It overrides any $(B 0) flag.) - - $(DT $(B '+')) - $(DD Prefix positive numbers in a signed conversion with a $(B +). - It overrides any $(I space) flag.) - - $(DT $(B '#')) - $(DD Use alternative formatting: - $(DL - $(DT For $(B 'o'):) - $(DD Add to precision as necessary so that the first digit - of the octal formatting is a '0', even if both the argument - and the $(I Precision) are zero.) - $(DT For $(B 'x') ($(B 'X')):) - $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).) - $(DT For floating point formatting:) - $(DD Always insert the decimal point.) - $(DT For $(B 'g') ($(B 'G')):) - $(DD Do not elide trailing zeros.) - )) - - $(DT $(B '0')) - $(DD For integer and floating point formatting when not nan or - infinity, use leading zeros - to pad rather than spaces. - Ignore if there's a $(I Precision).) - - $(DT $(B ' ')) - $(DD Prefix positive numbers in a signed conversion with a space.) - ) - - $(DT $(I Width)) - $(DD - Specifies the minimum field width. - If the width is a $(B *), the next argument, which must be - of type $(B int), is taken as the width. - If the width is negative, it is as if the $(B -) was given - as a $(I Flags) character.) - - $(DT $(I Precision)) - $(DD Gives the precision for numeric conversions. - If the precision is a $(B *), the next argument, which must be - of type $(B int), is taken as the precision. If it is negative, - it is as if there was no $(I Precision).) - - $(DT $(I FormatChar)) - $(DD - $(DL - $(DT $(B 's')) - $(DD The corresponding argument is formatted in a manner consistent - with its type: - $(DL - $(DT $(B bool)) - $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.) - $(DT integral types) - $(DD The $(B %d) format is used.) - $(DT floating point types) - $(DD The $(B %g) format is used.) - $(DT string types) - $(DD The result is the string converted to UTF-8.) - A $(I Precision) specifies the maximum number of characters - to use in the result. - $(DT classes derived from $(B Object)) - $(DD The result is the string returned from the class instance's - $(B .toString()) method. - A $(I Precision) specifies the maximum number of characters - to use in the result.) - $(DT non-string static and dynamic arrays) - $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...] - where s<sub>k</sub> is the kth element - formatted with the default format.) - )) - - $(DT $(B 'b','d','o','x','X')) - $(DD The corresponding argument must be an integral type - and is formatted as an integer. If the argument is a signed type - and the $(I FormatChar) is $(B d) it is converted to - a signed string of characters, otherwise it is treated as - unsigned. An argument of type $(B bool) is formatted as '1' - or '0'. The base used is binary for $(B b), octal for $(B o), - decimal - for $(B d), and hexadecimal for $(B x) or $(B X). - $(B x) formats using lower case letters, $(B X) uppercase. - If there are fewer resulting digits than the $(I Precision), - leading zeros are used as necessary. - If the $(I Precision) is 0 and the number is 0, no digits - result.) - - $(DT $(B 'e','E')) - $(DD A floating point number is formatted as one digit before - the decimal point, $(I Precision) digits after, the $(I FormatChar), - ±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±dd). - If there is no $(I Precision), six - digits are generated after the decimal point. - If the $(I Precision) is 0, no decimal point is generated.) - - $(DT $(B 'f','F')) - $(DD A floating point number is formatted in decimal notation. - The $(I Precision) specifies the number of digits generated - after the decimal point. It defaults to six. At least one digit - is generated before the decimal point. If the $(I Precision) - is zero, no decimal point is generated.) - - $(DT $(B 'g','G')) - $(DD A floating point number is formatted in either $(B e) or - $(B f) format for $(B g); $(B E) or $(B F) format for - $(B G). - The $(B f) format is used if the exponent for an $(B e) format - is greater than -5 and less than the $(I Precision). - The $(I Precision) specifies the number of significant - digits, and defaults to six. - Trailing zeros are elided after the decimal point, if the fractional - part is zero then no decimal point is generated.) - - $(DT $(B 'a','A')) - $(DD A floating point number is formatted in hexadecimal - exponential notation 0x$(I h.hhhhhh)p$(I ±d). - There is one hexadecimal digit before the decimal point, and as - many after as specified by the $(I Precision). - If the $(I Precision) is zero, no decimal point is generated. - If there is no $(I Precision), as many hexadecimal digits as - necessary to exactly represent the mantissa are generated. - The exponent is written in as few digits as possible, - but at least one, is in decimal, and represents a power of 2 as in - $(I h.hhhhhh)*2<sup>$(I ±d)</sup>. - The exponent for zero is zero. - The hexadecimal digits, x and p are in upper case if the - $(I FormatChar) is upper case.) - ) - - Floating point NaN's are formatted as $(B nan) if the - $(I FormatChar) is lower case, or $(B NAN) if upper. - Floating point infinities are formatted as $(B inf) or - $(B infinity) if the - $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. - )) - -Example: - -------------------------- -import core.stdc.stdio; -import std.format; - -void myPrint(...) -{ - void putc(dchar c) - { - fputc(c, stdout); - } - - std.format.doFormat(&putc, _arguments, _argptr); -} - -void main() -{ - int x = 27; - - // prints 'The answer is 27:6' - myPrint("The answer is %s:", x, 6); -} ------------------------- - */ -void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap) -{ - import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8; - import core.stdc.string : strlen; - import core.stdc.stdlib : alloca, malloc, realloc, free; - import core.stdc.stdio : snprintf; - - size_t bufLength = 1024; - void* argBuffer = malloc(bufLength); - scope(exit) free(argBuffer); - - size_t bufUsed = 0; - foreach (ti; arguments) - { - // Ensure the required alignment - bufUsed += ti.talign - 1; - bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1); - auto pos = bufUsed; - // Align to next word boundary - bufUsed += ti.tsize + size_t.sizeof - 1; - bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1); - // Resize buffer if necessary - while (bufUsed > bufLength) - { - bufLength *= 2; - argBuffer = realloc(argBuffer, bufLength); - } - // Copy argument into buffer - va_arg(ap, ti, argBuffer + pos); - } - - auto argptr = argBuffer; - void* skipArg(TypeInfo ti) - { - // Ensure the required alignment - argptr += ti.talign - 1; - argptr -= cast(size_t)argptr & (ti.talign - 1); - auto p = argptr; - // Align to next word boundary - argptr += ti.tsize + size_t.sizeof - 1; - argptr -= cast(size_t)argptr & (size_t.sizeof - 1); - return p; - } - auto getArg(T)() - { - return *cast(T*)skipArg(typeid(T)); - } - - TypeInfo ti; - Mangle m; - uint flags; - int field_width; - int precision; - - enum : uint - { - FLdash = 1, - FLplus = 2, - FLspace = 4, - FLhash = 8, - FLlngdbl = 0x20, - FL0pad = 0x40, - FLprecision = 0x80, - } - - static TypeInfo skipCI(TypeInfo valti) - { - for (;;) - { - if (typeid(valti).name.length == 18 && - typeid(valti).name[9..18] == "Invariant") - valti = (cast(TypeInfo_Invariant)valti).base; - else if (typeid(valti).name.length == 14 && - typeid(valti).name[9..14] == "Const") - valti = (cast(TypeInfo_Const)valti).base; - else - break; - } - - return valti; - } - - void formatArg(char fc) - { - bool vbit; - ulong vnumber; - char vchar; - dchar vdchar; - Object vobject; - real vreal; - creal vcreal; - Mangle m2; - int signed = 0; - uint base = 10; - int uc; - char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary - const(char)* prefix = ""; - string s; - - void putstr(const char[] s) - { - //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags); - ptrdiff_t padding = field_width - - (strlen(prefix) + toUCSindex(s, s.length)); - ptrdiff_t prepad = 0; - ptrdiff_t postpad = 0; - if (padding > 0) - { - if (flags & FLdash) - postpad = padding; - else - prepad = padding; - } - - if (flags & FL0pad) - { - while (*prefix) - putc(*prefix++); - while (prepad--) - putc('0'); - } - else - { - while (prepad--) - putc(' '); - while (*prefix) - putc(*prefix++); - } - - foreach (dchar c; s) - putc(c); - - while (postpad--) - putc(' '); - } - - void putreal(real v) - { - //printf("putreal %Lg\n", vreal); - - switch (fc) - { - case 's': - fc = 'g'; - break; - - case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': - break; - - default: - //printf("fc = '%c'\n", fc); - Lerror: - throw new FormatException("incompatible format character for floating point type"); - } - version (DigitalMarsC) - { - uint sl; - char[] fbuf = tmpbuf; - if (!(flags & FLprecision)) - precision = 6; - while (1) - { - sl = fbuf.length; - prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, - precision, &v, cast(char*)fbuf, &sl, field_width); - if (sl != -1) - break; - sl = fbuf.length * 2; - fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; - } - putstr(fbuf[0 .. sl]); - } - else - { - ptrdiff_t sl; - char[] fbuf = tmpbuf; - char[12] format; - format[0] = '%'; - int i = 1; - if (flags & FLdash) - format[i++] = '-'; - if (flags & FLplus) - format[i++] = '+'; - if (flags & FLspace) - format[i++] = ' '; - if (flags & FLhash) - format[i++] = '#'; - if (flags & FL0pad) - format[i++] = '0'; - format[i + 0] = '*'; - format[i + 1] = '.'; - format[i + 2] = '*'; - format[i + 3] = 'L'; - format[i + 4] = fc; - format[i + 5] = 0; - if (!(flags & FLprecision)) - precision = -1; - while (1) - { - sl = fbuf.length; - int n; - version (CRuntime_Microsoft) - { - import std.math : isNaN, isInfinity; - if (isNaN(v)) // snprintf writes 1.#QNAN - n = snprintf(fbuf.ptr, sl, "nan"); - else if (isInfinity(v)) // snprintf writes 1.#INF - n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf"); - else - n = snprintf(fbuf.ptr, sl, format.ptr, field_width, - precision, cast(double)v); - } - else - n = snprintf(fbuf.ptr, sl, format.ptr, field_width, - precision, v); - //printf("format = '%s', n = %d\n", cast(char*)format, n); - if (n >= 0 && n < sl) - { sl = n; - break; - } - if (n < 0) - sl = sl * 2; - else - sl = n + 1; - fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; - } - putstr(fbuf[0 .. sl]); - } - return; - } - - static Mangle getMan(TypeInfo ti) - { - auto m = cast(Mangle)typeid(ti).name[9]; - if (typeid(ti).name.length == 20 && - typeid(ti).name[9..20] == "StaticArray") - m = cast(Mangle)'G'; - return m; - } - - /* p = pointer to the first element in the array - * len = number of elements in the array - * valti = type of the elements - */ - void putArray(void* p, size_t len, TypeInfo valti) - { - //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize); - putc('['); - valti = skipCI(valti); - size_t tsize = valti.tsize; - auto argptrSave = argptr; - auto tiSave = ti; - auto mSave = m; - ti = valti; - //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr); - m = getMan(valti); - while (len--) - { - //doFormat(putc, (&valti)[0 .. 1], p); - argptr = p; - formatArg('s'); - p += tsize; - if (len > 0) putc(','); - } - m = mSave; - ti = tiSave; - argptr = argptrSave; - putc(']'); - } - - void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) - { - putc('['); - bool comma=false; - auto argptrSave = argptr; - auto tiSave = ti; - auto mSave = m; - valti = skipCI(valti); - keyti = skipCI(keyti); - foreach (ref fakevalue; vaa) - { - if (comma) putc(','); - comma = true; - void *pkey = &fakevalue; - version (D_LP64) - pkey -= (long.sizeof + 15) & ~(15); - else - pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - - // the key comes before the value - auto keysize = keyti.tsize; - version (D_LP64) - auto keysizet = (keysize + 15) & ~(15); - else - auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - - void* pvalue = pkey + keysizet; - - //doFormat(putc, (&keyti)[0..1], pkey); - m = getMan(keyti); - argptr = pkey; - - ti = keyti; - formatArg('s'); - - putc(':'); - //doFormat(putc, (&valti)[0..1], pvalue); - m = getMan(valti); - argptr = pvalue; - - ti = valti; - formatArg('s'); - } - m = mSave; - ti = tiSave; - argptr = argptrSave; - putc(']'); - } - - //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); - int mi; - switch (m) - { - case Mangle.Tbool: - vbit = getArg!(bool)(); - if (fc != 's') - { vnumber = vbit; - goto Lnumber; - } - putstr(vbit ? "true" : "false"); - return; - - case Mangle.Tchar: - vchar = getArg!(char)(); - if (fc != 's') - { vnumber = vchar; - goto Lnumber; - } - L2: - putstr((&vchar)[0 .. 1]); - return; - - case Mangle.Twchar: - vdchar = getArg!(wchar)(); - goto L1; - - case Mangle.Tdchar: - vdchar = getArg!(dchar)(); - L1: - if (fc != 's') - { vnumber = vdchar; - goto Lnumber; - } - if (vdchar <= 0x7F) - { vchar = cast(char)vdchar; - goto L2; - } - else - { if (!isValidDchar(vdchar)) - throw new UTFException("invalid dchar in format"); - char[4] vbuf; - putstr(toUTF8(vbuf, vdchar)); - } - return; - - case Mangle.Tbyte: - signed = 1; - vnumber = getArg!(byte)(); - goto Lnumber; - - case Mangle.Tubyte: - vnumber = getArg!(ubyte)(); - goto Lnumber; - - case Mangle.Tshort: - signed = 1; - vnumber = getArg!(short)(); - goto Lnumber; - - case Mangle.Tushort: - vnumber = getArg!(ushort)(); - goto Lnumber; - - case Mangle.Tint: - signed = 1; - vnumber = getArg!(int)(); - goto Lnumber; - - case Mangle.Tuint: - Luint: - vnumber = getArg!(uint)(); - goto Lnumber; - - case Mangle.Tlong: - signed = 1; - vnumber = cast(ulong)getArg!(long)(); - goto Lnumber; - - case Mangle.Tulong: - Lulong: - vnumber = getArg!(ulong)(); - goto Lnumber; - - case Mangle.Tclass: - vobject = getArg!(Object)(); - if (vobject is null) - s = "null"; - else - s = vobject.toString(); - goto Lputstr; - - case Mangle.Tpointer: - vnumber = cast(ulong)getArg!(void*)(); - if (fc != 'x') uc = 1; - flags |= FL0pad; - if (!(flags & FLprecision)) - { flags |= FLprecision; - precision = (void*).sizeof; - } - base = 16; - goto Lnumber; - - case Mangle.Tfloat: - case Mangle.Tifloat: - if (fc == 'x' || fc == 'X') - goto Luint; - vreal = getArg!(float)(); - goto Lreal; - - case Mangle.Tdouble: - case Mangle.Tidouble: - if (fc == 'x' || fc == 'X') - goto Lulong; - vreal = getArg!(double)(); - goto Lreal; - - case Mangle.Treal: - case Mangle.Tireal: - vreal = getArg!(real)(); - goto Lreal; - - case Mangle.Tcfloat: - vcreal = getArg!(cfloat)(); - goto Lcomplex; - - case Mangle.Tcdouble: - vcreal = getArg!(cdouble)(); - goto Lcomplex; - - case Mangle.Tcreal: - vcreal = getArg!(creal)(); - goto Lcomplex; - - case Mangle.Tsarray: - putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); - return; - - case Mangle.Tarray: - mi = 10; - if (typeid(ti).name.length == 14 && - typeid(ti).name[9..14] == "Array") - { // array of non-primitive types - TypeInfo tn = (cast(TypeInfo_Array)ti).next; - tn = skipCI(tn); - switch (cast(Mangle)typeid(tn).name[9]) - { - case Mangle.Tchar: goto LarrayChar; - case Mangle.Twchar: goto LarrayWchar; - case Mangle.Tdchar: goto LarrayDchar; - default: - break; - } - void[] va = getArg!(void[])(); - putArray(va.ptr, va.length, tn); - return; - } - if (typeid(ti).name.length == 25 && - typeid(ti).name[9..25] == "AssociativeArray") - { // associative array - ubyte[long] vaa = getArg!(ubyte[long])(); - putAArray(vaa, - (cast(TypeInfo_AssociativeArray)ti).next, - (cast(TypeInfo_AssociativeArray)ti).key); - return; - } - - while (1) - { - m2 = cast(Mangle)typeid(ti).name[mi]; - switch (m2) - { - case Mangle.Tchar: - LarrayChar: - s = getArg!(string)(); - goto Lputstr; - - case Mangle.Twchar: - LarrayWchar: - wchar[] sw = getArg!(wchar[])(); - s = toUTF8(sw); - goto Lputstr; - - case Mangle.Tdchar: - LarrayDchar: - s = toUTF8(getArg!(dstring)()); - Lputstr: - if (fc != 's') - throw new FormatException("string"); - if (flags & FLprecision && precision < s.length) - s = s[0 .. precision]; - putstr(s); - break; - - case Mangle.Tconst: - case Mangle.Timmutable: - mi++; - continue; - - default: - TypeInfo ti2 = primitiveTypeInfo(m2); - if (!ti2) - goto Lerror; - void[] va = getArg!(void[])(); - putArray(va.ptr, va.length, ti2); - } - return; - } - assert(0); - - case Mangle.Ttypedef: - ti = (cast(TypeInfo_Typedef)ti).base; - m = cast(Mangle)typeid(ti).name[9]; - formatArg(fc); - return; - - case Mangle.Tenum: - ti = (cast(TypeInfo_Enum)ti).base; - m = cast(Mangle)typeid(ti).name[9]; - formatArg(fc); - return; - - case Mangle.Tstruct: - { TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; - if (tis.xtoString is null) - throw new FormatException("Can't convert " ~ tis.toString() - ~ " to string: \"string toString()\" not defined"); - s = tis.xtoString(skipArg(tis)); - goto Lputstr; - } - - default: - goto Lerror; - } - - Lnumber: - switch (fc) - { - case 's': - case 'd': - if (signed) - { if (cast(long)vnumber < 0) - { prefix = "-"; - vnumber = -vnumber; - } - else if (flags & FLplus) - prefix = "+"; - else if (flags & FLspace) - prefix = " "; - } - break; - - case 'b': - signed = 0; - base = 2; - break; - - case 'o': - signed = 0; - base = 8; - break; - - case 'X': - uc = 1; - if (flags & FLhash && vnumber) - prefix = "0X"; - signed = 0; - base = 16; - break; - - case 'x': - if (flags & FLhash && vnumber) - prefix = "0x"; - signed = 0; - base = 16; - break; - - default: - goto Lerror; - } - - if (!signed) - { - switch (m) - { - case Mangle.Tbyte: - vnumber &= 0xFF; - break; - - case Mangle.Tshort: - vnumber &= 0xFFFF; - break; - - case Mangle.Tint: - vnumber &= 0xFFFFFFFF; - break; - - default: - break; - } - } - - if (flags & FLprecision && fc != 'p') - flags &= ~FL0pad; - - if (vnumber < base) - { - if (vnumber == 0 && precision == 0 && flags & FLprecision && - !(fc == 'o' && flags & FLhash)) - { - putstr(null); - return; - } - if (precision == 0 || !(flags & FLprecision)) - { vchar = cast(char)('0' + vnumber); - if (vnumber < 10) - vchar = cast(char)('0' + vnumber); - else - vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); - goto L2; - } - } - - { - ptrdiff_t n = tmpbuf.length; - char c; - int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); - - while (vnumber) - { - c = cast(char)((vnumber % base) + '0'); - if (c > '9') - c += hexoffset; - vnumber /= base; - tmpbuf[--n] = c; - } - if (tmpbuf.length - n < precision && precision < tmpbuf.length) - { - ptrdiff_t m = tmpbuf.length - precision; - tmpbuf[m .. n] = '0'; - n = m; - } - else if (flags & FLhash && fc == 'o') - prefix = "0"; - putstr(tmpbuf[n .. tmpbuf.length]); - return; - } - - Lreal: - putreal(vreal); - return; - - Lcomplex: - putreal(vcreal.re); - if (vcreal.im >= 0) - { - putc('+'); - } - putreal(vcreal.im); - putc('i'); - return; - - Lerror: - throw new FormatException("formatArg"); - } - - for (int j = 0; j < arguments.length; ) - { - ti = arguments[j++]; - //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length); - //ti.print(); - - flags = 0; - precision = 0; - field_width = 0; - - ti = skipCI(ti); - int mi = 9; - do - { - if (typeid(ti).name.length <= mi) - goto Lerror; - m = cast(Mangle)typeid(ti).name[mi++]; - } while (m == Mangle.Tconst || m == Mangle.Timmutable); - - if (m == Mangle.Tarray) - { - if (typeid(ti).name.length == 14 && - typeid(ti).name[9..14] == "Array") - { - TypeInfo tn = (cast(TypeInfo_Array)ti).next; - tn = skipCI(tn); - switch (cast(Mangle)typeid(tn).name[9]) - { - case Mangle.Tchar: - case Mangle.Twchar: - case Mangle.Tdchar: - ti = tn; - mi = 9; - break; - default: - break; - } - } - L1: - Mangle m2 = cast(Mangle)typeid(ti).name[mi]; - string fmt; // format string - wstring wfmt; - dstring dfmt; - - /* For performance reasons, this code takes advantage of the - * fact that most format strings will be ASCII, and that the - * format specifiers are always ASCII. This means we only need - * to deal with UTF in a couple of isolated spots. - */ - - switch (m2) - { - case Mangle.Tchar: - fmt = getArg!(string)(); - break; - - case Mangle.Twchar: - wfmt = getArg!(wstring)(); - fmt = toUTF8(wfmt); - break; - - case Mangle.Tdchar: - dfmt = getArg!(dstring)(); - fmt = toUTF8(dfmt); - break; - - case Mangle.Tconst: - case Mangle.Timmutable: - mi++; - goto L1; - - default: - formatArg('s'); - continue; - } - - for (size_t i = 0; i < fmt.length; ) - { dchar c = fmt[i++]; - - dchar getFmtChar() - { // Valid format specifier characters will never be UTF - if (i == fmt.length) - throw new FormatException("invalid specifier"); - return fmt[i++]; - } - - int getFmtInt() - { int n; - - while (1) - { - n = n * 10 + (c - '0'); - if (n < 0) // overflow - throw new FormatException("int overflow"); - c = getFmtChar(); - if (c < '0' || c > '9') - break; - } - return n; - } - - int getFmtStar() - { Mangle m; - TypeInfo ti; - - if (j == arguments.length) - throw new FormatException("too few arguments"); - ti = arguments[j++]; - m = cast(Mangle)typeid(ti).name[9]; - if (m != Mangle.Tint) - throw new FormatException("int argument expected"); - return getArg!(int)(); - } - - if (c != '%') - { - if (c > 0x7F) // if UTF sequence - { - i--; // back up and decode UTF sequence - import std.utf : decode; - c = decode(fmt, i); - } - Lputc: - putc(c); - continue; - } - - // Get flags {-+ #} - flags = 0; - while (1) - { - c = getFmtChar(); - switch (c) - { - case '-': flags |= FLdash; continue; - case '+': flags |= FLplus; continue; - case ' ': flags |= FLspace; continue; - case '#': flags |= FLhash; continue; - case '0': flags |= FL0pad; continue; - - case '%': if (flags == 0) - goto Lputc; - break; - - default: break; - } - break; - } - - // Get field width - field_width = 0; - if (c == '*') - { - field_width = getFmtStar(); - if (field_width < 0) - { flags |= FLdash; - field_width = -field_width; - } - - c = getFmtChar(); - } - else if (c >= '0' && c <= '9') - field_width = getFmtInt(); - - if (flags & FLplus) - flags &= ~FLspace; - if (flags & FLdash) - flags &= ~FL0pad; - - // Get precision - precision = 0; - if (c == '.') - { flags |= FLprecision; - //flags &= ~FL0pad; - - c = getFmtChar(); - if (c == '*') - { - precision = getFmtStar(); - if (precision < 0) - { precision = 0; - flags &= ~FLprecision; - } - - c = getFmtChar(); - } - else if (c >= '0' && c <= '9') - precision = getFmtInt(); - } - - if (j == arguments.length) - goto Lerror; - ti = arguments[j++]; - ti = skipCI(ti); - mi = 9; - do - { - m = cast(Mangle)typeid(ti).name[mi++]; - } while (m == Mangle.Tconst || m == Mangle.Timmutable); - - if (c > 0x7F) // if UTF sequence - goto Lerror; // format specifiers can't be UTF - formatArg(cast(char)c); - } - } - else - { - formatArg('s'); - } - } - return; - - Lerror: - throw new FormatException(); -} - - -private bool needToSwapEndianess(Char)(ref FormatSpec!Char f) -{ - import std.system : endian, Endian; - - return endian == Endian.littleEndian && f.flPlus - || endian == Endian.bigEndian && f.flDash; -} - -/* ======================== Unit Tests ====================================== */ - -unittest -{ - import std.conv : octal; - - int i; - string s; - - debug(format) printf("std.format.format.unittest\n"); - - s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); - assert(s == "hello world! true 57 1000000000x foo"); - - s = format("%s %A %s", 1.67, -1.28, float.nan); - /* The host C library is used to format floats. - * C99 doesn't specify what the hex digit before the decimal point - * is for %A. - */ - //version (linux) - // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); - //else version (OSX) - // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); - //else - version (MinGW) - assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); - else version (CRuntime_Microsoft) - assert(s == "1.67 -0X1.47AE14P+0 nan" - || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) - else - assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); - - s = format("%x %X", 0x1234AF, 0xAFAFAFAF); - assert(s == "1234af AFAFAFAF"); - - s = format("%b %o", 0x1234AF, 0xAFAFAFAF); - assert(s == "100100011010010101111 25753727657"); - - s = format("%d %s", 0x1234AF, 0xAFAFAFAF); - assert(s == "1193135 2947526575"); - - //version(X86_64) - //{ - // pragma(msg, "several format tests disabled on x86_64 due to bug 5625"); - //} - //else - //{ - s = format("%s", 1.2 + 3.4i); - assert(s == "1.2+3.4i", s); - - //s = format("%x %X", 1.32, 6.78f); - //assert(s == "3ff51eb851eb851f 40D8F5C3"); - - //} - - s = format("%#06.*f",2,12.345); - assert(s == "012.35"); - - s = format("%#0*.*f",6,2,12.345); - assert(s == "012.35"); - - s = format("%7.4g:", 12.678); - assert(s == " 12.68:"); - - s = format("%7.4g:", 12.678L); - assert(s == " 12.68:"); - - s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); - assert(s == "-4.000000|-0010|0x001| 0x1"); - - i = -10; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "-10|-10|-10|-10|-10.0000"); - - i = -5; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "-5| -5|-05|-5|-5.0000"); - - i = 0; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "0| 0|000|0|0.0000"); - - i = 5; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "5| 5|005|5|5.0000"); - - i = 10; - s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); - assert(s == "10| 10|010|10|10.0000"); - - s = format("%.0d", 0); - assert(s == ""); - - s = format("%.g", .34); - assert(s == "0.3"); - - s = format("%.0g", .34); - assert(s == "0.3"); - - s = format("%.2g", .34); - assert(s == "0.34"); - - s = format("%0.0008f", 1e-08); - assert(s == "0.00000001"); - - s = format("%0.0008f", 1e-05); - assert(s == "0.00001000"); - - s = "helloworld"; - string r; - r = format("%.2s", s[0..5]); - assert(r == "he"); - r = format("%.20s", s[0..5]); - assert(r == "hello"); - r = format("%8s", s[0..5]); - assert(r == " hello"); - - byte[] arrbyte = new byte[4]; - arrbyte[0] = 100; - arrbyte[1] = -99; - arrbyte[3] = 0; - r = format("%s", arrbyte); - assert(r == "[100, -99, 0, 0]"); - - ubyte[] arrubyte = new ubyte[4]; - arrubyte[0] = 100; - arrubyte[1] = 200; - arrubyte[3] = 0; - r = format("%s", arrubyte); - assert(r == "[100, 200, 0, 0]"); - - short[] arrshort = new short[4]; - arrshort[0] = 100; - arrshort[1] = -999; - arrshort[3] = 0; - r = format("%s", arrshort); - assert(r == "[100, -999, 0, 0]"); - - ushort[] arrushort = new ushort[4]; - arrushort[0] = 100; - arrushort[1] = 20_000; - arrushort[3] = 0; - r = format("%s", arrushort); - assert(r == "[100, 20000, 0, 0]"); - - int[] arrint = new int[4]; - arrint[0] = 100; - arrint[1] = -999; - arrint[3] = 0; - r = format("%s", arrint); - assert(r == "[100, -999, 0, 0]"); - - long[] arrlong = new long[4]; - arrlong[0] = 100; - arrlong[1] = -999; - arrlong[3] = 0; - r = format("%s", arrlong); - assert(r == "[100, -999, 0, 0]"); - - ulong[] arrulong = new ulong[4]; - arrulong[0] = 100; - arrulong[1] = 999; - arrulong[3] = 0; - r = format("%s", arrulong); - assert(r == "[100, 999, 0, 0]"); - - string[] arr2 = new string[4]; - arr2[0] = "hello"; - arr2[1] = "world"; - arr2[3] = "foo"; - r = format("%s", arr2); - assert(r == `["hello", "world", "", "foo"]`); - - r = format("%.8d", 7); - assert(r == "00000007"); - r = format("%.8x", 10); - assert(r == "0000000a"); - - r = format("%-3d", 7); - assert(r == "7 "); - - r = format("%*d", -3, 7); - assert(r == "7 "); - - r = format("%.*d", -3, 7); - assert(r == "7"); - - r = format("abc"c); - assert(r == "abc"); - - //format() returns the same type as inputted. - wstring wr; - wr = format("def"w); - assert(wr == "def"w); - - dstring dr; - dr = format("ghi"d); - assert(dr == "ghi"d); - - void* p = cast(void*)0xDEADBEEF; - r = format("%s", p); - assert(r == "DEADBEEF"); - - r = format("%#x", 0xabcd); - assert(r == "0xabcd"); - r = format("%#X", 0xABCD); - assert(r == "0XABCD"); - - r = format("%#o", octal!12345); - assert(r == "012345"); - r = format("%o", 9); - assert(r == "11"); - r = format("%#o", 0); // issue 15663 - assert(r == "0"); - - r = format("%+d", 123); - assert(r == "+123"); - r = format("%+d", -123); - assert(r == "-123"); - r = format("% d", 123); - assert(r == " 123"); - r = format("% d", -123); - assert(r == "-123"); - - r = format("%%"); - assert(r == "%"); - - r = format("%d", true); - assert(r == "1"); - r = format("%d", false); - assert(r == "0"); - - r = format("%d", 'a'); - assert(r == "97"); - wchar wc = 'a'; - r = format("%d", wc); - assert(r == "97"); - dchar dc = 'a'; - r = format("%d", dc); - assert(r == "97"); - - byte b = byte.max; - r = format("%x", b); - assert(r == "7f"); - r = format("%x", ++b); - assert(r == "80"); - r = format("%x", ++b); - assert(r == "81"); - - short sh = short.max; - r = format("%x", sh); - assert(r == "7fff"); - r = format("%x", ++sh); - assert(r == "8000"); - r = format("%x", ++sh); - assert(r == "8001"); - - i = int.max; - r = format("%x", i); - assert(r == "7fffffff"); - r = format("%x", ++i); - assert(r == "80000000"); - r = format("%x", ++i); - assert(r == "80000001"); - - r = format("%x", 10); - assert(r == "a"); - r = format("%X", 10); - assert(r == "A"); - r = format("%x", 15); - assert(r == "f"); - r = format("%X", 15); - assert(r == "F"); - - Object c = null; - r = format("%s", c); - assert(r == "null"); - - enum TestEnum - { - Value1, Value2 - } - r = format("%s", TestEnum.Value2); - assert(r == "Value2"); - - immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); - r = format("%s", aa.values); - assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); - r = format("%s", aa); - assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); - - static const dchar[] ds = ['a','b']; - for (int j = 0; j < ds.length; ++j) - { - r = format(" %d", ds[j]); - if (j == 0) - assert(r == " 97"); - else - assert(r == " 98"); - } - - r = format(">%14d<, %s", 15, [1,2,3]); - assert(r == "> 15<, [1, 2, 3]"); - - assert(format("%8s", "bar") == " bar"); - assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); -} diff --git a/src/undead/internal/file.d b/src/undead/internal/file.d deleted file mode 100644 index f756674..0000000 --- a/src/undead/internal/file.d +++ /dev/null @@ -1,25 +0,0 @@ -// Written in the D programming language - -module undead.internal.file; - -// Copied from std.file. undead doesn't have access to it, but some modules -// in undead used std.file.deleteme when they were in Phobos, so this gives -// them access to a version of it. -public @property string deleteme() @safe -{ - import std.conv : to; - import std.file : tempDir; - import std.path : buildPath; - import std.process : thisProcessID; - static _deleteme = "deleteme.dmd.unittest.pid"; - static _first = true; - - if(_first) - { - _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID); - _first = false; - } - - return _deleteme; -} - diff --git a/src/undead/stream.d b/src/undead/stream.d deleted file mode 100644 index dc81b7f..0000000 --- a/src/undead/stream.d +++ /dev/null @@ -1,3076 +0,0 @@ -// Written in the D programming language - -/** - * $(RED Deprecated: This module is considered out-dated and not up to Phobos' - * current standards.) - * - * Source: $(PHOBOSSRC std/_stream.d) - * Macros: - * WIKI = Phobos/StdStream - */ - -/* - * Copyright (c) 2001-2005 - * Pavel "EvilOne" Minayev - * with buffering and endian support added by Ben Hinkle - * with buffered readLine performance improvements by Dave Fladebo - * with opApply inspired by (and mostly copied from) Regan Heath - * with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Author makes no representations about - * the suitability of this software for any purpose. It is provided - * "as is" without express or implied warranty. - */ -module undead.stream; - -import std.internal.cstring; - -/* Class structure: - * InputStream interface for reading - * OutputStream interface for writing - * Stream abstract base of stream implementations - * File an OS file stream - * FilterStream a base-class for wrappers around another stream - * BufferedStream a buffered stream wrapping another stream - * BufferedFile a buffered File - * EndianStream a wrapper stream for swapping byte order and BOMs - * SliceStream a portion of another stream - * MemoryStream a stream entirely stored in main memory - * TArrayStream a stream wrapping an array-like buffer - */ - -/// A base class for stream exceptions. -class StreamException: Exception { - /// Construct a StreamException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to read data from Stream. -class ReadException: StreamException { - /// Construct a ReadException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to write data to Stream. -class WriteException: StreamException { - /// Construct a WriteException with given error message. - this(string msg) { super(msg); } -} - -/// Thrown when unable to move Stream pointer. -class SeekException: StreamException { - /// Construct a SeekException with given error message. - this(string msg) { super(msg); } -} - -// seek whence... -enum SeekPos { - Set, - Current, - End -} - -private { - import std.conv; - import std.algorithm; - import std.ascii; - //import std.format; - import std.system; // for Endian enumeration - import std.utf; - import core.bitop; // for bswap - import core.vararg; - import std.file; - import undead.internal.file; - import undead.doformat; -} - -/// InputStream is the interface for readable streams. - -interface InputStream { - - /*** - * Read exactly size bytes into the buffer. - * - * Throws a ReadException if it is not correct. - */ - void readExact(void* buffer, size_t size); - - /*** - * Read a block of data big enough to fill the given array buffer. - * - * Returns: the actual number of bytes read. Unfilled bytes are not modified. - */ - size_t read(ubyte[] buffer); - - /*** - * Read a basic type or counted string. - * - * Throw a ReadException if it could not be read. - * Outside of byte, ubyte, and char, the format is - * implementation-specific and should not be used except as opposite actions - * to write. - */ - void read(out byte x); - void read(out ubyte x); /// ditto - void read(out short x); /// ditto - void read(out ushort x); /// ditto - void read(out int x); /// ditto - void read(out uint x); /// ditto - void read(out long x); /// ditto - void read(out ulong x); /// ditto - void read(out float x); /// ditto - void read(out double x); /// ditto - void read(out real x); /// ditto - void read(out ifloat x); /// ditto - void read(out idouble x); /// ditto - void read(out ireal x); /// ditto - void read(out cfloat x); /// ditto - void read(out cdouble x); /// ditto - void read(out creal x); /// ditto - void read(out char x); /// ditto - void read(out wchar x); /// ditto - void read(out dchar x); /// ditto - - // reads a string, written earlier by write() - void read(out char[] s); /// ditto - - // reads a Unicode string, written earlier by write() - void read(out wchar[] s); /// ditto - - /*** - * Read a line that is terminated with some combination of carriage return and - * line feed or end-of-file. - * - * The terminators are not included. The wchar version - * is identical. The optional buffer parameter is filled (reallocating - * it if necessary) and a slice of the result is returned. - */ - char[] readLine(); - char[] readLine(char[] result); /// ditto - wchar[] readLineW(); /// ditto - wchar[] readLineW(wchar[] result); /// ditto - - /*** - * Overload foreach statements to read the stream line by line and call the - * supplied delegate with each line or with each line with line number. - * - * The string passed in line may be reused between calls to the delegate. - * Line numbering starts at 1. - * Breaking out of the foreach will leave the stream - * position at the beginning of the next line to be read. - * For example, to echo a file line-by-line with line numbers run: - * ------------------------------------ - * Stream file = new BufferedFile("sample.txt"); - * foreach(ulong n, char[] line; file) - * { - * writefln("line %d: %s", n, line); - * } - * file.close(); - * ------------------------------------ - */ - - // iterate through the stream line-by-line - int opApply(scope int delegate(ref char[] line) dg); - int opApply(scope int delegate(ref ulong n, ref char[] line) dg); /// ditto - int opApply(scope int delegate(ref wchar[] line) dg); /// ditto - int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto - - /// Read a string of the given length, - /// throwing ReadException if there was a problem. - char[] readString(size_t length); - - /*** - * Read a string of the given length, throwing ReadException if there was a - * problem. - * - * The file format is implementation-specific and should not be used - * except as opposite actions to <b>write</b>. - */ - - wchar[] readStringW(size_t length); - - - /*** - * Read and return the next character in the stream. - * - * This is the only method that will handle ungetc properly. - * getcw's format is implementation-specific. - * If EOF is reached then getc returns char.init and getcw returns wchar.init. - */ - - char getc(); - wchar getcw(); /// ditto - - /*** - * Push a character back onto the stream. - * - * They will be returned in first-in last-out order from getc/getcw. - * Only has effect on further calls to getc() and getcw(). - */ - char ungetc(char c); - wchar ungetcw(wchar c); /// ditto - - /*** - * Scan a string from the input using a similar form to C's scanf - * and <a href="std_format.html">std.format</a>. - * - * An argument of type string is interpreted as a format string. - * All other arguments must be pointer types. - * If a format string is not present a default will be supplied computed from - * the base type of the pointer type. An argument of type string* is filled - * (possibly with appending characters) and a slice of the result is assigned - * back into the argument. For example the following readf statements - * are equivalent: - * -------------------------- - * int x; - * double y; - * string s; - * file.readf(&x, " hello ", &y, &s); - * file.readf("%d hello %f %s", &x, &y, &s); - * file.readf("%d hello %f", &x, &y, "%s", &s); - * -------------------------- - */ - int vreadf(TypeInfo[] arguments, va_list args); - int readf(...); /// ditto - - /// Retrieve the number of bytes available for immediate reading. - @property size_t available(); - - /*** - * Return whether the current file position is the same as the end of the - * file. - * - * This does not require actually reading past the end, as with stdio. For - * non-seekable streams this might only return true after attempting to read - * past the end. - */ - - @property bool eof(); - - @property bool isOpen(); /// Return true if the stream is currently open. -} - -/// Interface for writable streams. -interface OutputStream { - - /*** - * Write exactly size bytes from buffer, or throw a WriteException if that - * could not be done. - */ - void writeExact(const void* buffer, size_t size); - - /*** - * Write as much of the buffer as possible, - * returning the number of bytes written. - */ - size_t write(const(ubyte)[] buffer); - - /*** - * Write a basic type. - * - * Outside of byte, ubyte, and char, the format is implementation-specific - * and should only be used in conjunction with read. - * Throw WriteException on error. - */ - void write(byte x); - void write(ubyte x); /// ditto - void write(short x); /// ditto - void write(ushort x); /// ditto - void write(int x); /// ditto - void write(uint x); /// ditto - void write(long x); /// ditto - void write(ulong x); /// ditto - void write(float x); /// ditto - void write(double x); /// ditto - void write(real x); /// ditto - void write(ifloat x); /// ditto - void write(idouble x); /// ditto - void write(ireal x); /// ditto - void write(cfloat x); /// ditto - void write(cdouble x); /// ditto - void write(creal x); /// ditto - void write(char x); /// ditto - void write(wchar x); /// ditto - void write(dchar x); /// ditto - - /*** - * Writes a string, together with its length. - * - * The format is implementation-specific - * and should only be used in conjunction with read. - * Throw WriteException on error. - */ - void write(const(char)[] s); - void write(const(wchar)[] s); /// ditto - - /*** - * Write a line of text, - * appending the line with an operating-system-specific line ending. - * - * Throws WriteException on error. - */ - void writeLine(const(char)[] s); - - /*** - * Write a line of text, - * appending the line with an operating-system-specific line ending. - * - * The format is implementation-specific. - * Throws WriteException on error. - */ - void writeLineW(const(wchar)[] s); - - /*** - * Write a string of text. - * - * Throws WriteException if it could not be fully written. - */ - void writeString(const(char)[] s); - - /*** - * Write a string of text. - * - * The format is implementation-specific. - * Throws WriteException if it could not be fully written. - */ - void writeStringW(const(wchar)[] s); - - /*** - * Print a formatted string into the stream using printf-style syntax, - * returning the number of bytes written. - */ - size_t vprintf(const(char)[] format, va_list args); - size_t printf(const(char)[] format, ...); /// ditto - - /*** - * Print a formatted string into the stream using writef-style syntax. - * References: <a href="std_format.html">std.format</a>. - * Returns: self to chain with other stream commands like flush. - */ - OutputStream writef(...); - OutputStream writefln(...); /// ditto - OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false); /// ditto - - void flush(); /// Flush pending output if appropriate. - void close(); /// Close the stream, flushing output if appropriate. - @property bool isOpen(); /// Return true if the stream is currently open. -} - - -/*** - * Stream is the base abstract class from which the other stream classes derive. - * - * Stream's byte order is the format native to the computer. - * - * Reading: - * These methods require that the readable flag be set. - * Problems with reading result in a ReadException being thrown. - * Stream implements the InputStream interface in addition to the - * readBlock method. - * - * Writing: - * These methods require that the writeable flag be set. Problems with writing - * result in a WriteException being thrown. Stream implements the OutputStream - * interface in addition to the following methods: - * writeBlock - * copyFrom - * copyFrom - * - * Seeking: - * These methods require that the seekable flag be set. - * Problems with seeking result in a SeekException being thrown. - * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash - */ - -// not really abstract, but its instances will do nothing useful -class Stream : InputStream, OutputStream { - private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio; - - // stream abilities - bool readable = false; /// Indicates whether this stream can be read from. - bool writeable = false; /// Indicates whether this stream can be written to. - bool seekable = false; /// Indicates whether this stream can be seeked within. - protected bool isopen = true; /// Indicates whether this stream is open. - - protected bool readEOF = false; /** Indicates whether this stream is at eof - * after the last read attempt. - */ - - protected bool prevCr = false; /** For a non-seekable stream indicates that - * the last readLine or readLineW ended on a - * '\r' character. - */ - - this() {} - - /*** - * Read up to size bytes into the buffer and return the number of bytes - * actually read. A return value of 0 indicates end-of-file. - */ - abstract size_t readBlock(void* buffer, size_t size); - - // reads block of data of specified size, - // throws ReadException on error - void readExact(void* buffer, size_t size) { - for(;;) { - if (!size) return; - size_t readsize = readBlock(buffer, size); // return 0 on eof - if (readsize == 0) break; - buffer += readsize; - size -= readsize; - } - if (size != 0) - throw new ReadException("not enough data in stream"); - } - - // reads block of data big enough to fill the given - // array, returns actual number of bytes read - size_t read(ubyte[] buffer) { - return readBlock(buffer.ptr, buffer.length); - } - - // read a single value of desired type, - // throw ReadException on error - void read(out byte x) { readExact(&x, x.sizeof); } - void read(out ubyte x) { readExact(&x, x.sizeof); } - void read(out short x) { readExact(&x, x.sizeof); } - void read(out ushort x) { readExact(&x, x.sizeof); } - void read(out int x) { readExact(&x, x.sizeof); } - void read(out uint x) { readExact(&x, x.sizeof); } - void read(out long x) { readExact(&x, x.sizeof); } - void read(out ulong x) { readExact(&x, x.sizeof); } - void read(out float x) { readExact(&x, x.sizeof); } - void read(out double x) { readExact(&x, x.sizeof); } - void read(out real x) { readExact(&x, x.sizeof); } - void read(out ifloat x) { readExact(&x, x.sizeof); } - void read(out idouble x) { readExact(&x, x.sizeof); } - void read(out ireal x) { readExact(&x, x.sizeof); } - void read(out cfloat x) { readExact(&x, x.sizeof); } - void read(out cdouble x) { readExact(&x, x.sizeof); } - void read(out creal x) { readExact(&x, x.sizeof); } - void read(out char x) { readExact(&x, x.sizeof); } - void read(out wchar x) { readExact(&x, x.sizeof); } - void read(out dchar x) { readExact(&x, x.sizeof); } - - // reads a string, written earlier by write() - void read(out char[] s) { - size_t len; - read(len); - s = readString(len); - } - - // reads a Unicode string, written earlier by write() - void read(out wchar[] s) { - size_t len; - read(len); - s = readStringW(len); - } - - // reads a line, terminated by either CR, LF, CR/LF, or EOF - char[] readLine() { - return readLine(null); - } - - // reads a line, terminated by either CR, LF, CR/LF, or EOF - // reusing the memory in buffer if result will fit and otherwise - // allocates a new string - char[] readLine(char[] result) { - size_t strlen = 0; - char ch = getc(); - while (readable) { - switch (ch) { - case '\r': - if (seekable) { - ch = getc(); - if (ch != '\n') - ungetc(ch); - } else { - prevCr = true; - } - goto case; - case '\n': - case char.init: - result.length = strlen; - return result; - - default: - if (strlen < result.length) { - result[strlen] = ch; - } else { - result ~= ch; - } - strlen++; - } - ch = getc(); - } - result.length = strlen; - return result; - } - - // reads a Unicode line, terminated by either CR, LF, CR/LF, - // or EOF; pretty much the same as the above, working with - // wchars rather than chars - wchar[] readLineW() { - return readLineW(null); - } - - // reads a Unicode line, terminated by either CR, LF, CR/LF, - // or EOF; - // fills supplied buffer if line fits and otherwise allocates a new string. - wchar[] readLineW(wchar[] result) { - size_t strlen = 0; - wchar c = getcw(); - while (readable) { - switch (c) { - case '\r': - if (seekable) { - c = getcw(); - if (c != '\n') - ungetcw(c); - } else { - prevCr = true; - } - goto case; - case '\n': - case wchar.init: - result.length = strlen; - return result; - - default: - if (strlen < result.length) { - result[strlen] = c; - } else { - result ~= c; - } - strlen++; - } - c = getcw(); - } - result.length = strlen; - return result; - } - - // iterate through the stream line-by-line - due to Regan Heath - int opApply(scope int delegate(ref char[] line) dg) { - int res = 0; - char[128] buf; - while (!eof) { - char[] line = readLine(buf); - res = dg(line); - if (res) break; - } - return res; - } - - // iterate through the stream line-by-line with line count and string - int opApply(scope int delegate(ref ulong n, ref char[] line) dg) { - int res = 0; - ulong n = 1; - char[128] buf; - while (!eof) { - auto line = readLine(buf); - res = dg(n,line); - if (res) break; - n++; - } - return res; - } - - // iterate through the stream line-by-line with wchar[] - int opApply(scope int delegate(ref wchar[] line) dg) { - int res = 0; - wchar[128] buf; - while (!eof) { - auto line = readLineW(buf); - res = dg(line); - if (res) break; - } - return res; - } - - // iterate through the stream line-by-line with line count and wchar[] - int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) { - int res = 0; - ulong n = 1; - wchar[128] buf; - while (!eof) { - auto line = readLineW(buf); - res = dg(n,line); - if (res) break; - n++; - } - return res; - } - - // reads a string of given length, throws - // ReadException on error - char[] readString(size_t length) { - char[] result = new char[length]; - readExact(result.ptr, length); - return result; - } - - // reads a Unicode string of given length, throws - // ReadException on error - wchar[] readStringW(size_t length) { - auto result = new wchar[length]; - readExact(result.ptr, result.length * wchar.sizeof); - return result; - } - - // unget buffer - private wchar[] unget; - final bool ungetAvailable() { return unget.length > 1; } - - // reads and returns next character from the stream, - // handles characters pushed back by ungetc() - // returns char.init on eof. - char getc() { - char c; - if (prevCr) { - prevCr = false; - c = getc(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = cast(char)unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - readBlock(&c,1); - } - return c; - } - - // reads and returns next Unicode character from the - // stream, handles characters pushed back by ungetc() - // returns wchar.init on eof. - wchar getcw() { - wchar c; - if (prevCr) { - prevCr = false; - c = getcw(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - void* buf = &c; - size_t n = readBlock(buf,2); - if (n == 1 && readBlock(buf+1,1) == 0) - throw new ReadException("not enough data in stream"); - } - return c; - } - - // pushes back character c into the stream; only has - // effect on further calls to getc() and getcw() - char ungetc(char c) { - if (c == c.init) return c; - // first byte is a dummy so that we never set length to 0 - if (unget.length == 0) - unget.length = 1; - unget ~= c; - return c; - } - - // pushes back Unicode character c into the stream; only - // has effect on further calls to getc() and getcw() - wchar ungetcw(wchar c) { - if (c == c.init) return c; - // first byte is a dummy so that we never set length to 0 - if (unget.length == 0) - unget.length = 1; - unget ~= c; - return c; - } - - int vreadf(TypeInfo[] arguments, va_list args) { - string fmt; - int j = 0; - int count = 0, i = 0; - char c; - bool firstCharacter = true; - while ((j < arguments.length || i < fmt.length) && !eof) { - if(firstCharacter) { - c = getc(); - firstCharacter = false; - } - if (fmt.length == 0 || i == fmt.length) { - i = 0; - if (arguments[j] is typeid(string) || arguments[j] is typeid(char[]) - || arguments[j] is typeid(const(char)[])) { - fmt = va_arg!(string)(args); - j++; - continue; - } else if (arguments[j] is typeid(int*) || - arguments[j] is typeid(byte*) || - arguments[j] is typeid(short*) || - arguments[j] is typeid(long*)) { - fmt = "%d"; - } else if (arguments[j] is typeid(uint*) || - arguments[j] is typeid(ubyte*) || - arguments[j] is typeid(ushort*) || - arguments[j] is typeid(ulong*)) { - fmt = "%d"; - } else if (arguments[j] is typeid(float*) || - arguments[j] is typeid(double*) || - arguments[j] is typeid(real*)) { - fmt = "%f"; - } else if (arguments[j] is typeid(char[]*) || - arguments[j] is typeid(wchar[]*) || - arguments[j] is typeid(dchar[]*)) { - fmt = "%s"; - } else if (arguments[j] is typeid(char*)) { - fmt = "%c"; - } - } - if (fmt[i] == '%') { // a field - i++; - bool suppress = false; - if (fmt[i] == '*') { // suppress assignment - suppress = true; - i++; - } - // read field width - int width = 0; - while (isDigit(fmt[i])) { - width = width * 10 + (fmt[i] - '0'); - i++; - } - if (width == 0) - width = -1; - // skip any modifier if present - if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L') - i++; - // check the typechar and act accordingly - switch (fmt[i]) { - case 'd': // decimal/hexadecimal/octal integer - case 'D': - case 'u': - case 'U': - case 'o': - case 'O': - case 'x': - case 'X': - case 'i': - case 'I': - { - while (isWhite(c)) { - c = getc(); - count++; - } - bool neg = false; - if (c == '-') { - neg = true; - c = getc(); - count++; - } else if (c == '+') { - c = getc(); - count++; - } - char ifmt = cast(char)(fmt[i] | 0x20); - if (ifmt == 'i') { // undetermined base - if (c == '0') { // octal or hex - c = getc(); - count++; - if (c == 'x' || c == 'X') { // hex - ifmt = 'x'; - c = getc(); - count++; - } else { // octal - ifmt = 'o'; - } - } - else // decimal - ifmt = 'd'; - } - long n = 0; - switch (ifmt) - { - case 'd': // decimal - case 'u': { - while (isDigit(c) && width) { - n = n * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - } break; - - case 'o': { // octal - while (isOctalDigit(c) && width) { - n = n * 8 + (c - '0'); - width--; - c = getc(); - count++; - } - } break; - - case 'x': { // hexadecimal - while (isHexDigit(c) && width) { - n *= 0x10; - if (isDigit(c)) - n += c - '0'; - else - n += 0xA + (c | 0x20) - 'a'; - width--; - c = getc(); - count++; - } - } break; - - default: - assert(0); - } - if (neg) - n = -n; - if (arguments[j] is typeid(int*)) { - int* p = va_arg!(int*)(args); - *p = cast(int)n; - } else if (arguments[j] is typeid(short*)) { - short* p = va_arg!(short*)(args); - *p = cast(short)n; - } else if (arguments[j] is typeid(byte*)) { - byte* p = va_arg!(byte*)(args); - *p = cast(byte)n; - } else if (arguments[j] is typeid(long*)) { - long* p = va_arg!(long*)(args); - *p = n; - } else if (arguments[j] is typeid(uint*)) { - uint* p = va_arg!(uint*)(args); - *p = cast(uint)n; - } else if (arguments[j] is typeid(ushort*)) { - ushort* p = va_arg!(ushort*)(args); - *p = cast(ushort)n; - } else if (arguments[j] is typeid(ubyte*)) { - ubyte* p = va_arg!(ubyte*)(args); - *p = cast(ubyte)n; - } else if (arguments[j] is typeid(ulong*)) { - ulong* p = va_arg!(ulong*)(args); - *p = cast(ulong)n; - } - j++; - i++; - } break; - - case 'f': // float - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - { - while (isWhite(c)) { - c = getc(); - count++; - } - bool neg = false; - if (c == '-') { - neg = true; - c = getc(); - count++; - } else if (c == '+') { - c = getc(); - count++; - } - real r = 0; - while (isDigit(c) && width) { - r = r * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - if (width && c == '.') { - width--; - c = getc(); - count++; - double frac = 1; - while (isDigit(c) && width) { - r = r * 10 + (c - '0'); - frac *= 10; - width--; - c = getc(); - count++; - } - r /= frac; - } - if (width && (c == 'e' || c == 'E')) { - width--; - c = getc(); - count++; - if (width) { - bool expneg = false; - if (c == '-') { - expneg = true; - width--; - c = getc(); - count++; - } else if (c == '+') { - width--; - c = getc(); - count++; - } - real exp = 0; - while (isDigit(c) && width) { - exp = exp * 10 + (c - '0'); - width--; - c = getc(); - count++; - } - if (expneg) { - while (exp--) - r /= 10; - } else { - while (exp--) - r *= 10; - } - } - } - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - if(width && (c == 'a' || c == 'A')) { - width--; - c = getc(); - count++; - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - r = real.nan; - } - } - } - if(width && (c == 'i' || c == 'I')) { - width--; - c = getc(); - count++; - if(width && (c == 'n' || c == 'N')) { - width--; - c = getc(); - count++; - if(width && (c == 'f' || c == 'F')) { - width--; - c = getc(); - count++; - r = real.infinity; - } - } - } - if (neg) - r = -r; - if (arguments[j] is typeid(float*)) { - float* p = va_arg!(float*)(args); - *p = r; - } else if (arguments[j] is typeid(double*)) { - double* p = va_arg!(double*)(args); - *p = r; - } else if (arguments[j] is typeid(real*)) { - real* p = va_arg!(real*)(args); - *p = r; - } - j++; - i++; - } break; - - case 's': { // string - while (isWhite(c)) { - c = getc(); - count++; - } - char[] s; - char[]* p; - size_t strlen; - if (arguments[j] is typeid(char[]*)) { - p = va_arg!(char[]*)(args); - s = *p; - } - while (!isWhite(c) && c != char.init) { - if (strlen < s.length) { - s[strlen] = c; - } else { - s ~= c; - } - strlen++; - c = getc(); - count++; - } - s = s[0 .. strlen]; - if (arguments[j] is typeid(char[]*)) { - *p = s; - } else if (arguments[j] is typeid(char*)) { - s ~= 0; - auto q = va_arg!(char*)(args); - q[0 .. s.length] = s[]; - } else if (arguments[j] is typeid(wchar[]*)) { - auto q = va_arg!(const(wchar)[]*)(args); - *q = toUTF16(s); - } else if (arguments[j] is typeid(dchar[]*)) { - auto q = va_arg!(const(dchar)[]*)(args); - *q = toUTF32(s); - } - j++; - i++; - } break; - - case 'c': { // character(s) - char* s = va_arg!(char*)(args); - if (width < 0) - width = 1; - else - while (isWhite(c)) { - c = getc(); - count++; - } - while (width-- && !eof) { - *(s++) = c; - c = getc(); - count++; - } - j++; - i++; - } break; - - case 'n': { // number of chars read so far - int* p = va_arg!(int*)(args); - *p = count; - j++; - i++; - } break; - - default: // read character as is - goto nws; - } - } else if (isWhite(fmt[i])) { // skip whitespace - while (isWhite(c)) - c = getc(); - i++; - } else { // read character as is - nws: - if (fmt[i] != c) - break; - c = getc(); - i++; - } - } - ungetc(c); - return count; - } - - int readf(...) { - return vreadf(_arguments, _argptr); - } - - // returns estimated number of bytes available for immediate reading - @property size_t available() { return 0; } - - /*** - * Write up to size bytes from buffer in the stream, returning the actual - * number of bytes that were written. - */ - abstract size_t writeBlock(const void* buffer, size_t size); - - // writes block of data of specified size, - // throws WriteException on error - void writeExact(const void* buffer, size_t size) { - const(void)* p = buffer; - for(;;) { - if (!size) return; - size_t writesize = writeBlock(p, size); - if (writesize == 0) break; - p += writesize; - size -= writesize; - } - if (size != 0) - throw new WriteException("unable to write to stream"); - } - - // writes the given array of bytes, returns - // actual number of bytes written - size_t write(const(ubyte)[] buffer) { - return writeBlock(buffer.ptr, buffer.length); - } - - // write a single value of desired type, - // throw WriteException on error - void write(byte x) { writeExact(&x, x.sizeof); } - void write(ubyte x) { writeExact(&x, x.sizeof); } - void write(short x) { writeExact(&x, x.sizeof); } - void write(ushort x) { writeExact(&x, x.sizeof); } - void write(int x) { writeExact(&x, x.sizeof); } - void write(uint x) { writeExact(&x, x.sizeof); } - void write(long x) { writeExact(&x, x.sizeof); } - void write(ulong x) { writeExact(&x, x.sizeof); } - void write(float x) { writeExact(&x, x.sizeof); } - void write(double x) { writeExact(&x, x.sizeof); } - void write(real x) { writeExact(&x, x.sizeof); } - void write(ifloat x) { writeExact(&x, x.sizeof); } - void write(idouble x) { writeExact(&x, x.sizeof); } - void write(ireal x) { writeExact(&x, x.sizeof); } - void write(cfloat x) { writeExact(&x, x.sizeof); } - void write(cdouble x) { writeExact(&x, x.sizeof); } - void write(creal x) { writeExact(&x, x.sizeof); } - void write(char x) { writeExact(&x, x.sizeof); } - void write(wchar x) { writeExact(&x, x.sizeof); } - void write(dchar x) { writeExact(&x, x.sizeof); } - - // writes a string, together with its length - void write(const(char)[] s) { - write(s.length); - writeString(s); - } - - // writes a Unicode string, together with its length - void write(const(wchar)[] s) { - write(s.length); - writeStringW(s); - } - - // writes a line, throws WriteException on error - void writeLine(const(char)[] s) { - writeString(s); - version (Windows) - writeString("\r\n"); - else version (Mac) - writeString("\r"); - else - writeString("\n"); - } - - // writes a Unicode line, throws WriteException on error - void writeLineW(const(wchar)[] s) { - writeStringW(s); - version (Windows) - writeStringW("\r\n"); - else version (Mac) - writeStringW("\r"); - else - writeStringW("\n"); - } - - // writes a string, throws WriteException on error - void writeString(const(char)[] s) { - writeExact(s.ptr, s.length); - } - - // writes a Unicode string, throws WriteException on error - void writeStringW(const(wchar)[] s) { - writeExact(s.ptr, s.length * wchar.sizeof); - } - - // writes data to stream using vprintf() syntax, - // returns number of bytes written - size_t vprintf(const(char)[] format, va_list args) { - // shamelessly stolen from OutBuffer, - // by Walter's permission - char[1024] buffer; - char* p = buffer.ptr; - // Can't use `tempCString()` here as it will result in compilation error: - // "cannot mix core.std.stdlib.alloca() and exception handling". - auto f = toStringz(format); - size_t psize = buffer.length; - size_t count; - while (true) { - version (Windows) { - count = vsnprintf(p, psize, f, args); - if (count != -1) - break; - psize *= 2; - p = cast(char*) alloca(psize); - } else version (Posix) { - count = vsnprintf(p, psize, f, args); - if (count == -1) - psize *= 2; - else if (count >= psize) - psize = count + 1; - else - break; - p = cast(char*) alloca(psize); - } else - throw new Exception("unsupported platform"); - } - writeString(p[0 .. count]); - return count; - } - - // writes data to stream using printf() syntax, - // returns number of bytes written - size_t printf(const(char)[] format, ...) { - va_list ap; - va_start(ap, format); - auto result = vprintf(format, ap); - va_end(ap); - return result; - } - - private void doFormatCallback(dchar c) { - char[4] buf; - auto b = std.utf.toUTF8(buf, c); - writeString(b); - } - - // writes data to stream using writef() syntax, - OutputStream writef(...) { - return writefx(_arguments,_argptr,0); - } - - // writes data with trailing newline - OutputStream writefln(...) { - return writefx(_arguments,_argptr,1); - } - - // writes data with optional trailing newline - OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) { - doFormat(&doFormatCallback,arguments,argptr); - if (newline) - writeLine(""); - return this; - } - - /*** - * Copies all data from s into this stream. - * This may throw ReadException or WriteException on failure. - * This restores the file position of s so that it is unchanged. - */ - void copyFrom(Stream s) { - if (seekable) { - ulong pos = s.position; - s.position = 0; - copyFrom(s, s.size); - s.position = pos; - } else { - ubyte[128] buf; - while (!s.eof) { - size_t m = s.readBlock(buf.ptr, buf.length); - writeExact(buf.ptr, m); - } - } - } - - /*** - * Copy a specified number of bytes from the given stream into this one. - * This may throw ReadException or WriteException on failure. - * Unlike the previous form, this doesn't restore the file position of s. - */ - void copyFrom(Stream s, ulong count) { - ubyte[128] buf; - while (count > 0) { - size_t n = cast(size_t)(count<buf.length ? count : buf.length); - s.readExact(buf.ptr, n); - writeExact(buf.ptr, n); - count -= n; - } - } - - /*** - * Change the current position of the stream. whence is either SeekPos.Set, in - which case the offset is an absolute index from the beginning of the stream, - SeekPos.Current, in which case the offset is a delta from the current - position, or SeekPos.End, in which case the offset is a delta from the end of - the stream (negative or zero offsets only make sense in that case). This - returns the new file position. - */ - abstract ulong seek(long offset, SeekPos whence); - - /*** - * Aliases for their normal seek counterparts. - */ - ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } - ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto - ulong seekEnd(long offset) { return seek (offset, SeekPos.End); } /// ditto - - /*** - * Sets file position. Equivalent to calling seek(pos, SeekPos.Set). - */ - @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); } - - /*** - * Returns current file position. Equivalent to seek(0, SeekPos.Current). - */ - @property ulong position() { return seek(0, SeekPos.Current); } - - /*** - * Retrieve the size of the stream in bytes. - * The stream must be seekable or a SeekException is thrown. - */ - @property ulong size() { - assertSeekable(); - ulong pos = position, result = seek(0, SeekPos.End); - position = pos; - return result; - } - - // returns true if end of stream is reached, false otherwise - @property bool eof() { - // for unseekable streams we only know the end when we read it - if (readEOF && !ungetAvailable()) - return true; - else if (seekable) - return position == size; - else - return false; - } - - // returns true if the stream is open - @property bool isOpen() { return isopen; } - - // flush the buffer if writeable - void flush() { - if (unget.length > 1) - unget.length = 1; // keep at least 1 so that data ptr stays - } - - // close the stream somehow; the default just flushes the buffer - void close() { - if (isopen) - flush(); - readEOF = prevCr = isopen = readable = writeable = seekable = false; - } - - /*** - * Read the entire stream and return it as a string. - * If the stream is not seekable the contents from the current position to eof - * is read and returned. - */ - override string toString() { - if (!readable) - return super.toString(); - try - { - size_t pos; - size_t rdlen; - size_t blockSize; - char[] result; - if (seekable) { - ulong orig_pos = position; - scope(exit) position = orig_pos; - position = 0; - blockSize = cast(size_t)size; - result = new char[blockSize]; - while (blockSize > 0) { - rdlen = readBlock(&result[pos], blockSize); - pos += rdlen; - blockSize -= rdlen; - } - } else { - blockSize = 4096; - result = new char[blockSize]; - while ((rdlen = readBlock(&result[pos], blockSize)) > 0) { - pos += rdlen; - blockSize += rdlen; - result.length = result.length + blockSize; - } - } - return cast(string) result[0 .. pos]; - } - catch (Throwable) - { - return super.toString(); - } - } - - /*** - * Get a hash of the stream by reading each byte and using it in a CRC-32 - * checksum. - */ - override size_t toHash() @trusted { - if (!readable || !seekable) - return super.toHash(); - try - { - ulong pos = position; - scope(exit) position = pos; - CRC32 crc; - crc.start(); - position = 0; - ulong len = size; - for (ulong i = 0; i < len; i++) - { - ubyte c; - read(c); - crc.put(c); - } - - union resUnion - { - size_t hash; - ubyte[4] crcVal; - } - resUnion res; - res.crcVal = crc.finish(); - return res.hash; - } - catch (Throwable) - { - return super.toHash(); - } - } - - // helper for checking that the stream is readable - final protected void assertReadable() { - if (!readable) - throw new ReadException("Stream is not readable"); - } - // helper for checking that the stream is writeable - final protected void assertWriteable() { - if (!writeable) - throw new WriteException("Stream is not writeable"); - } - // helper for checking that the stream is seekable - final protected void assertSeekable() { - if (!seekable) - throw new SeekException("Stream is not seekable"); - } - /+ - unittest { // unit test for Issue 3363 - import std.stdio; - immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt"; - auto w = File(fileName, "w"); - scope (exit) remove(fileName.ptr); - w.write("one two three"); - w.close(); - auto r = File(fileName, "r"); - const(char)[] constChar; - string str; - char[] chars; - r.readf("%s %s %s", &constChar, &str, &chars); - assert (constChar == "one", constChar); - assert (str == "two", str); - assert (chars == "three", chars); - } - - unittest { //unit tests for Issue 1668 - void tryFloatRoundtrip(float x, string fmt = "", string pad = "") { - auto s = new MemoryStream(); - s.writef(fmt, x, pad); - s.position = 0; - - float f; - assert(s.readf(&f)); - assert(x == f || (x != x && f != f)); //either equal or both NaN - } - - tryFloatRoundtrip(1.0); - tryFloatRoundtrip(1.0, "%f"); - tryFloatRoundtrip(1.0, "", " "); - tryFloatRoundtrip(1.0, "%f", " "); - - tryFloatRoundtrip(3.14); - tryFloatRoundtrip(3.14, "%f"); - tryFloatRoundtrip(3.14, "", " "); - tryFloatRoundtrip(3.14, "%f", " "); - - float nan = float.nan; - tryFloatRoundtrip(nan); - tryFloatRoundtrip(nan, "%f"); - tryFloatRoundtrip(nan, "", " "); - tryFloatRoundtrip(nan, "%f", " "); - - float inf = 1.0/0.0; - tryFloatRoundtrip(inf); - tryFloatRoundtrip(inf, "%f"); - tryFloatRoundtrip(inf, "", " "); - tryFloatRoundtrip(inf, "%f", " "); - - tryFloatRoundtrip(-inf); - tryFloatRoundtrip(-inf,"%f"); - tryFloatRoundtrip(-inf, "", " "); - tryFloatRoundtrip(-inf, "%f", " "); - } - +/ -} - -/*** - * A base class for streams that wrap a source stream with additional - * functionality. - * - * The method implementations forward read/write/seek calls to the - * source stream. A FilterStream can change the position of the source stream - * arbitrarily and may not keep the source stream state in sync with the - * FilterStream, even upon flushing and closing the FilterStream. It is - * recommended to not make any assumptions about the state of the source position - * and read/write state after a FilterStream has acted upon it. Specifc subclasses - * of FilterStream should document how they modify the source stream and if any - * invariants hold true between the source and filter. - */ -class FilterStream : Stream { - private Stream s; // source stream - - /// Property indicating when this stream closes to close the source stream as - /// well. - /// Defaults to true. - bool nestClose = true; - - /// Construct a FilterStream for the given source. - this(Stream source) { - s = source; - resetSource(); - } - - // source getter/setter - - /*** - * Get the current source stream. - */ - final Stream source(){return s;} - - /*** - * Set the current source stream. - * - * Setting the source stream closes this stream before attaching the new - * source. Attaching an open stream reopens this stream and resets the stream - * state. - */ - void source(Stream s) { - close(); - this.s = s; - resetSource(); - } - - /*** - * Indicates the source stream changed state and that this stream should reset - * any readable, writeable, seekable, isopen and buffering flags. - */ - void resetSource() { - if (s !is null) { - readable = s.readable; - writeable = s.writeable; - seekable = s.seekable; - isopen = s.isOpen; - } else { - readable = writeable = seekable = false; - isopen = false; - } - readEOF = prevCr = false; - } - - // read from source - override size_t readBlock(void* buffer, size_t size) { - size_t res = s.readBlock(buffer,size); - readEOF = res == 0; - return res; - } - - // write to source - override size_t writeBlock(const void* buffer, size_t size) { - return s.writeBlock(buffer,size); - } - - // close stream - override void close() { - if (isopen) { - super.close(); - if (nestClose) - s.close(); - } - } - - // seek on source - override ulong seek(long offset, SeekPos whence) { - readEOF = false; - return s.seek(offset,whence); - } - - override @property size_t available() { return s.available; } - override void flush() { super.flush(); s.flush(); } -} - -/*** - * This subclass is for buffering a source stream. - * - * A buffered stream must be - * closed explicitly to ensure the final buffer content is written to the source - * stream. The source stream position is changed according to the block size so - * reading or writing to the BufferedStream may not change the source stream - * position by the same amount. - */ -class BufferedStream : FilterStream { - ubyte[] buffer; // buffer, if any - size_t bufferCurPos; // current position in buffer - size_t bufferLen; // amount of data in buffer - bool bufferDirty = false; - size_t bufferSourcePos; // position in buffer of source stream position - ulong streamPos; // absolute position in source stream - - /* Example of relationship between fields: - * - * s ...01234567890123456789012EOF - * buffer |-- --| - * bufferCurPos | - * bufferLen |-- --| - * bufferSourcePos | - * - */ - - invariant() { - assert(bufferSourcePos <= bufferLen); - assert(bufferCurPos <= bufferLen); - assert(bufferLen <= buffer.length); - } - - enum size_t DefaultBufferSize = 8192; - - /*** - * Create a buffered stream for the stream source with the buffer size - * bufferSize. - */ - this(Stream source, size_t bufferSize = DefaultBufferSize) { - super(source); - if (bufferSize) - buffer = new ubyte[bufferSize]; - } - - override protected void resetSource() { - super.resetSource(); - streamPos = 0; - bufferLen = bufferSourcePos = bufferCurPos = 0; - bufferDirty = false; - } - - // reads block of data of specified size using any buffered data - // returns actual number of bytes read - override size_t readBlock(void* result, size_t len) { - if (len == 0) return 0; - - assertReadable(); - - ubyte* outbuf = cast(ubyte*)result; - size_t readsize = 0; - - if (bufferCurPos + len < bufferLen) { - // buffer has all the data so copy it - outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len]; - bufferCurPos += len; - readsize = len; - goto ExitRead; - } - - readsize = bufferLen - bufferCurPos; - if (readsize > 0) { - // buffer has some data so copy what is left - outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; - outbuf += readsize; - bufferCurPos += readsize; - len -= readsize; - } - - flush(); - - if (len >= buffer.length) { - // buffer can't hold the data so fill output buffer directly - size_t siz = super.readBlock(outbuf, len); - readsize += siz; - streamPos += siz; - } else { - // read a new block into buffer - bufferLen = super.readBlock(buffer.ptr, buffer.length); - if (bufferLen < len) len = bufferLen; - outbuf[0 .. len] = buffer[0 .. len]; - bufferSourcePos = bufferLen; - streamPos += bufferLen; - bufferCurPos = len; - readsize += len; - } - - ExitRead: - return readsize; - } - - // write block of data of specified size - // returns actual number of bytes written - override size_t writeBlock(const void* result, size_t len) { - assertWriteable(); - - ubyte* buf = cast(ubyte*)result; - size_t writesize = 0; - - if (bufferLen == 0) { - // buffer is empty so fill it if possible - if ((len < buffer.length) && (readable)) { - // read in data if the buffer is currently empty - bufferLen = s.readBlock(buffer.ptr, buffer.length); - bufferSourcePos = bufferLen; - streamPos += bufferLen; - - } else if (len >= buffer.length) { - // buffer can't hold the data so write it directly and exit - writesize = s.writeBlock(buf,len); - streamPos += writesize; - goto ExitWrite; - } - } - - if (bufferCurPos + len <= buffer.length) { - // buffer has space for all the data so copy it and exit - buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len]; - bufferCurPos += len; - bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; - writesize = len; - bufferDirty = true; - goto ExitWrite; - } - - writesize = buffer.length - bufferCurPos; - if (writesize > 0) { - // buffer can take some data - buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize]; - bufferCurPos = bufferLen = buffer.length; - buf += writesize; - len -= writesize; - bufferDirty = true; - } - - assert(bufferCurPos == buffer.length); - assert(bufferLen == buffer.length); - - flush(); - - writesize += writeBlock(buf,len); - - ExitWrite: - return writesize; - } - - override ulong seek(long offset, SeekPos whence) { - assertSeekable(); - - if ((whence != SeekPos.Current) || - (offset + bufferCurPos < 0) || - (offset + bufferCurPos >= bufferLen)) { - flush(); - streamPos = s.seek(offset,whence); - } else { - bufferCurPos += offset; - } - readEOF = false; - return streamPos-bufferSourcePos+bufferCurPos; - } - - // Buffered readLine - Dave Fladebo - // reads a line, terminated by either CR, LF, CR/LF, or EOF - // reusing the memory in buffer if result will fit, otherwise - // will reallocate (using concatenation) - template TreadLine(T) { - T[] readLine(T[] inBuffer) - { - size_t lineSize = 0; - bool haveCR = false; - T c = '\0'; - size_t idx = 0; - ubyte* pc = cast(ubyte*)&c; - - L0: - for(;;) { - size_t start = bufferCurPos; - L1: - foreach(ubyte b; buffer[start .. bufferLen]) { - bufferCurPos++; - pc[idx] = b; - if(idx < T.sizeof - 1) { - idx++; - continue L1; - } else { - idx = 0; - } - if(c == '\n' || haveCR) { - if(haveCR && c != '\n') bufferCurPos--; - break L0; - } else { - if(c == '\r') { - haveCR = true; - } else { - if(lineSize < inBuffer.length) { - inBuffer[lineSize] = c; - } else { - inBuffer ~= c; - } - lineSize++; - } - } - } - flush(); - size_t res = super.readBlock(buffer.ptr, buffer.length); - if(!res) break L0; // EOF - bufferSourcePos = bufferLen = res; - streamPos += res; - } - return inBuffer[0 .. lineSize]; - } - } // template TreadLine(T) - - override char[] readLine(char[] inBuffer) { - if (ungetAvailable()) - return super.readLine(inBuffer); - else - return TreadLine!(char).readLine(inBuffer); - } - alias readLine = Stream.readLine; - - override wchar[] readLineW(wchar[] inBuffer) { - if (ungetAvailable()) - return super.readLineW(inBuffer); - else - return TreadLine!(wchar).readLine(inBuffer); - } - alias readLineW = Stream.readLineW; - - override void flush() - out { - assert(bufferCurPos == 0); - assert(bufferSourcePos == 0); - assert(bufferLen == 0); - } - body { - if (writeable && bufferDirty) { - if (bufferSourcePos != 0 && seekable) { - // move actual file pointer to front of buffer - streamPos = s.seek(-bufferSourcePos, SeekPos.Current); - } - // write buffer out - bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen); - if (bufferSourcePos != bufferLen) { - throw new WriteException("Unable to write to stream"); - } - } - super.flush(); - long diff = cast(long)bufferCurPos-bufferSourcePos; - if (diff != 0 && seekable) { - // move actual file pointer to current position - streamPos = s.seek(diff, SeekPos.Current); - } - // reset buffer data to be empty - bufferSourcePos = bufferCurPos = bufferLen = 0; - bufferDirty = false; - } - - // returns true if end of stream is reached, false otherwise - override @property bool eof() { - if ((buffer.length == 0) || !readable) { - return super.eof; - } - // some simple tests to avoid flushing - if (ungetAvailable() || bufferCurPos != bufferLen) - return false; - if (bufferLen == buffer.length) - flush(); - size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen); - bufferSourcePos += res; - bufferLen += res; - streamPos += res; - return readEOF; - } - - // returns size of stream - override @property ulong size() { - if (bufferDirty) flush(); - return s.size; - } - - // returns estimated number of bytes available for immediate reading - override @property size_t available() { - return bufferLen - bufferCurPos; - } -} - -/// An exception for File errors. -class StreamFileException: StreamException { - /// Construct a StreamFileException with given error message. - this(string msg) { super(msg); } -} - -/// An exception for errors during File.open. -class OpenException: StreamFileException { - /// Construct an OpenFileException with given error message. - this(string msg) { super(msg); } -} - -/// Specifies the $(LREF File) access mode used when opening the file. -enum FileMode { - In = 1, /// Opens the file for reading. - Out = 2, /// Opens the file for writing. - OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist. - Append = 10 /// Opens the file for writing, appending new data to the end of the file. -} - -version (Windows) { - private import core.sys.windows.windows; - extern (Windows) { - void FlushFileBuffers(HANDLE hFile); - DWORD GetFileType(HANDLE hFile); - } -} -version (Posix) { - private import core.sys.posix.fcntl; - private import core.sys.posix.unistd; - alias HANDLE = int; -} - -/// This subclass is for unbuffered file system streams. -class File: Stream { - - version (Windows) { - private HANDLE hFile; - } - version (Posix) { - private HANDLE hFile = -1; - } - - this() { - super(); - version (Windows) { - hFile = null; - } - version (Posix) { - hFile = -1; - } - isopen = false; - } - - // opens existing handle; use with care! - this(HANDLE hFile, FileMode mode) { - super(); - this.hFile = hFile; - readable = cast(bool)(mode & FileMode.In); - writeable = cast(bool)(mode & FileMode.Out); - version(Windows) { - seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK - } else { - auto result = lseek(hFile, 0, 0); - seekable = (result != ~0); - } - } - - /*** - * Create the stream with no open file, an open file in read mode, or an open - * file with explicit file mode. - * mode, if given, is a combination of FileMode.In - * (indicating a file that can be read) and FileMode.Out (indicating a file - * that can be written). - * Opening a file for reading that doesn't exist will error. - * Opening a file for writing that doesn't exist will create the file. - * The FileMode.OutNew mode will open the file for writing and reset the - * length to zero. - * The FileMode.Append mode will open the file for writing and move the - * file position to the end of the file. - */ - this(string filename, FileMode mode = FileMode.In) - { - this(); - open(filename, mode); - } - - - /*** - * Open a file for the stream, in an identical manner to the constructors. - * If an error occurs an OpenException is thrown. - */ - void open(string filename, FileMode mode = FileMode.In) { - close(); - int access, share, createMode; - parseMode(mode, access, share, createMode); - seekable = true; - readable = cast(bool)(mode & FileMode.In); - writeable = cast(bool)(mode & FileMode.Out); - version (Windows) { - hFile = CreateFileW(filename.tempCStringW(), access, share, - null, createMode, 0, null); - isopen = hFile != INVALID_HANDLE_VALUE; - } - version (Posix) { - hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share); - isopen = hFile != -1; - } - if (!isopen) - throw new OpenException(cast(string) ("Cannot open or create file '" - ~ filename ~ "'")); - else if ((mode & FileMode.Append) == FileMode.Append) - seekEnd(0); - } - - private void parseMode(int mode, - out int access, - out int share, - out int createMode) { - version (Windows) { - share |= FILE_SHARE_READ | FILE_SHARE_WRITE; - if (mode & FileMode.In) { - access |= GENERIC_READ; - createMode = OPEN_EXISTING; - } - if (mode & FileMode.Out) { - access |= GENERIC_WRITE; - createMode = OPEN_ALWAYS; // will create if not present - } - if ((mode & FileMode.OutNew) == FileMode.OutNew) { - createMode = CREATE_ALWAYS; // resets file - } - } - version (Posix) { - share = octal!666; - if (mode & FileMode.In) { - access = O_RDONLY; - } - if (mode & FileMode.Out) { - createMode = O_CREAT; // will create if not present - access = O_WRONLY; - } - if (access == (O_WRONLY | O_RDONLY)) { - access = O_RDWR; - } - if ((mode & FileMode.OutNew) == FileMode.OutNew) { - access |= O_TRUNC; // resets file - } - } - } - - /// Create a file for writing. - void create(string filename) { - create(filename, FileMode.OutNew); - } - - /// ditto - void create(string filename, FileMode mode) { - close(); - open(filename, mode | FileMode.OutNew); - } - - /// Close the current file if it is open; otherwise it does nothing. - override void close() { - if (isopen) { - super.close(); - if (hFile) { - version (Windows) { - CloseHandle(hFile); - hFile = null; - } else version (Posix) { - core.sys.posix.unistd.close(hFile); - hFile = -1; - } - } - } - } - - // destructor, closes file if still opened - ~this() { close(); } - - version (Windows) { - // returns size of stream - override @property ulong size() { - assertSeekable(); - uint sizehi; - uint sizelow = GetFileSize(hFile,&sizehi); - return (cast(ulong)sizehi << 32) + sizelow; - } - } - - override size_t readBlock(void* buffer, size_t size) { - assertReadable(); - version (Windows) { - auto dwSize = to!DWORD(size); - ReadFile(hFile, buffer, dwSize, &dwSize, null); - size = dwSize; - } else version (Posix) { - size = core.sys.posix.unistd.read(hFile, buffer, size); - if (size == -1) - size = 0; - } - readEOF = (size == 0); - return size; - } - - override size_t writeBlock(const void* buffer, size_t size) { - assertWriteable(); - version (Windows) { - auto dwSize = to!DWORD(size); - WriteFile(hFile, buffer, dwSize, &dwSize, null); - size = dwSize; - } else version (Posix) { - size = core.sys.posix.unistd.write(hFile, buffer, size); - if (size == -1) - size = 0; - } - return size; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - version (Windows) { - int hi = cast(int)(offset>>32); - uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel); - if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0)) - throw new SeekException("unable to move file pointer"); - ulong result = (cast(ulong)hi << 32) + low; - } else version (Posix) { - auto result = lseek(hFile, cast(off_t)offset, rel); - if (result == cast(typeof(result))-1) - throw new SeekException("unable to move file pointer"); - } - readEOF = false; - return cast(ulong)result; - } - - /*** - * For a seekable file returns the difference of the size and position and - * otherwise returns 0. - */ - - override @property size_t available() { - if (seekable) { - ulong lavail = size - position; - if (lavail > size_t.max) lavail = size_t.max; - return cast(size_t)lavail; - } - return 0; - } - - // OS-specific property, just in case somebody wants - // to mess with underlying API - HANDLE handle() { return hFile; } - - // run a few tests - /+ - unittest { - import std.internal.cstring : tempCString; - - File file = new File; - int i = 666; - auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; - file.create(stream_file); - // should be ok to write - assert(file.writeable); - file.writeLine("Testing stream.d:"); - file.writeString("Hello, world!"); - file.write(i); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - // no operations are allowed when file is closed - assert(!file.readable && !file.writeable && !file.seekable); - file.open(stream_file); - // should be ok to read - assert(file.readable); - assert(file.available == file.size); - char[] line = file.readLine(); - char[] exp = "Testing stream.d:".dup; - assert(line[0] == 'T'); - assert(line.length == exp.length); - assert(!std.algorithm.cmp(line, "Testing stream.d:")); - // jump over "Hello, " - file.seek(7, SeekPos.Current); - version (Windows) - assert(file.position == 19 + 7); - version (Posix) - assert(file.position == 18 + 7); - assert(!std.algorithm.cmp(file.readString(6), "world!")); - i = 0; file.read(i); - assert(i == 666); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - file.open(stream_file,FileMode.OutNew | FileMode.In); - file.writeLine("Testing stream.d:"); - file.writeLine("Another line"); - file.writeLine(""); - file.writeLine("That was blank"); - file.position = 0; - char[][] lines; - foreach(char[] line; file) { - lines ~= line.dup; - } - assert( lines.length == 4 ); - assert( lines[0] == "Testing stream.d:"); - assert( lines[1] == "Another line"); - assert( lines[2] == ""); - assert( lines[3] == "That was blank"); - file.position = 0; - lines = new char[][4]; - foreach(ulong n, char[] line; file) { - lines[cast(size_t)(n-1)] = line.dup; - } - assert( lines[0] == "Testing stream.d:"); - assert( lines[1] == "Another line"); - assert( lines[2] == ""); - assert( lines[3] == "That was blank"); - file.close(); - remove(stream_file.tempCString()); - } - +/ -} - -/*** - * This subclass is for buffered file system streams. - * - * It is a convenience class for wrapping a File in a BufferedStream. - * A buffered stream must be closed explicitly to ensure the final buffer - * content is written to the file. - */ -class BufferedFile: BufferedStream { - - /// opens file for reading - this() { super(new File()); } - - /// opens file in requested mode and buffer size - this(string filename, FileMode mode = FileMode.In, - size_t bufferSize = DefaultBufferSize) { - super(new File(filename,mode),bufferSize); - } - - /// opens file for reading with requested buffer size - this(File file, size_t bufferSize = DefaultBufferSize) { - super(file,bufferSize); - } - - /// opens existing handle; use with care! - this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) { - super(new File(hFile,mode),buffersize); - } - - /// opens file in requested mode - void open(string filename, FileMode mode = FileMode.In) { - File sf = cast(File)s; - sf.open(filename,mode); - resetSource(); - } - - /// creates file in requested mode - void create(string filename, FileMode mode = FileMode.OutNew) { - File sf = cast(File)s; - sf.create(filename,mode); - resetSource(); - } - - // run a few tests same as File - /+ - unittest { - import std.internal.cstring : tempCString; - - BufferedFile file = new BufferedFile; - int i = 666; - auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; - file.create(stream_file); - // should be ok to write - assert(file.writeable); - file.writeLine("Testing stream.d:"); - file.writeString("Hello, world!"); - file.write(i); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - long oldsize = cast(long)file.size; - file.close(); - // no operations are allowed when file is closed - assert(!file.readable && !file.writeable && !file.seekable); - file.open(stream_file); - // should be ok to read - assert(file.readable); - // test getc/ungetc and size - char c1 = file.getc(); - file.ungetc(c1); - assert( file.size == oldsize ); - assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:")); - // jump over "Hello, " - file.seek(7, SeekPos.Current); - version (Windows) - assert(file.position == 19 + 7); - version (Posix) - assert(file.position == 18 + 7); - assert(!std.algorithm.cmp(file.readString(6), "world!")); - i = 0; file.read(i); - assert(i == 666); - // string#1 + string#2 + int should give exacly that - version (Windows) - assert(file.position == 19 + 13 + 4); - version (Posix) - assert(file.position == 18 + 13 + 4); - // we must be at the end of file - assert(file.eof); - file.close(); - remove(stream_file.tempCString()); - } - +/ - -} - -/// UTF byte-order-mark signatures -enum BOM { - UTF8, /// UTF-8 - UTF16LE, /// UTF-16 Little Endian - UTF16BE, /// UTF-16 Big Endian - UTF32LE, /// UTF-32 Little Endian - UTF32BE, /// UTF-32 Big Endian -} - -private enum int NBOMS = 5; -immutable Endian[NBOMS] BOMEndian = -[ std.system.endian, - Endian.littleEndian, Endian.bigEndian, - Endian.littleEndian, Endian.bigEndian - ]; - -immutable ubyte[][NBOMS] ByteOrderMarks = -[ [0xEF, 0xBB, 0xBF], - [0xFF, 0xFE], - [0xFE, 0xFF], - [0xFF, 0xFE, 0x00, 0x00], - [0x00, 0x00, 0xFE, 0xFF] - ]; - - -/*** - * This subclass wraps a stream with big-endian or little-endian byte order - * swapping. - * - * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or - * written. - * Note that an EndianStream should not be used as the source of another - * FilterStream since a FilterStream call the source with byte-oriented - * read/write requests and the EndianStream will not perform any byte swapping. - * The EndianStream reads and writes binary data (non-getc functions) in a - * one-to-one - * manner with the source stream so the source stream's position and state will be - * kept in sync with the EndianStream if only non-getc functions are called. - */ -class EndianStream : FilterStream { - - Endian endian; /// Endianness property of the source stream. - - /*** - * Create the endian stream for the source stream source with endianness end. - * The default endianness is the native byte order. - * The Endian type is defined - * in the std.system module. - */ - this(Stream source, Endian end = std.system.endian) { - super(source); - endian = end; - } - - /*** - * Return -1 if no BOM and otherwise read the BOM and return it. - * - * If there is no BOM or if bytes beyond the BOM are read then the bytes read - * are pushed back onto the ungetc buffer or ungetcw buffer. - * Pass ungetCharSize == 2 to use - * ungetcw instead of ungetc when no BOM is present. - */ - int readBOM(int ungetCharSize = 1) { - ubyte[4] BOM_buffer; - int n = 0; // the number of read bytes - int result = -1; // the last match or -1 - for (int i=0; i < NBOMS; ++i) { - int j; - immutable ubyte[] bom = ByteOrderMarks[i]; - for (j=0; j < bom.length; ++j) { - if (n <= j) { // have to read more - if (eof) - break; - readExact(&BOM_buffer[n++],1); - } - if (BOM_buffer[j] != bom[j]) - break; - } - if (j == bom.length) // found a match - result = i; - } - ptrdiff_t m = 0; - if (result != -1) { - endian = BOMEndian[result]; // set stream endianness - m = ByteOrderMarks[result].length; - } - if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) { - while (n-- > m) - ungetc(BOM_buffer[n]); - } else { // should eventually support unget for dchar as well - if (n & 1) // make sure we have an even number of bytes - readExact(&BOM_buffer[n++],1); - while (n > m) { - n -= 2; - wchar cw = *(cast(wchar*)&BOM_buffer[n]); - fixBO(&cw,2); - ungetcw(cw); - } - } - return result; - } - - /*** - * Correct the byte order of buffer to match native endianness. - * size must be even. - */ - final void fixBO(const(void)* buffer, size_t size) { - if (endian != std.system.endian) { - ubyte* startb = cast(ubyte*)buffer; - uint* start = cast(uint*)buffer; - switch (size) { - case 0: break; - case 2: { - ubyte x = *startb; - *startb = *(startb+1); - *(startb+1) = x; - break; - } - case 4: { - *start = bswap(*start); - break; - } - default: { - uint* end = cast(uint*)(buffer + size - uint.sizeof); - while (start < end) { - uint x = bswap(*start); - *start = bswap(*end); - *end = x; - ++start; - --end; - } - startb = cast(ubyte*)start; - ubyte* endb = cast(ubyte*)end; - auto len = uint.sizeof - (startb - endb); - if (len > 0) - fixBO(startb,len); - } - } - } - } - - /*** - * Correct the byte order of the given buffer in blocks of the given size and - * repeated the given number of times. - * size must be even. - */ - final void fixBlockBO(void* buffer, uint size, size_t repeat) { - while (repeat--) { - fixBO(buffer,size); - buffer += size; - } - } - - override void read(out byte x) { readExact(&x, x.sizeof); } - override void read(out ubyte x) { readExact(&x, x.sizeof); } - override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); } - override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); } - override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); } - override void read(out char x) { readExact(&x, x.sizeof); } - override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } - - override wchar getcw() { - wchar c; - if (prevCr) { - prevCr = false; - c = getcw(); - if (c != '\n') - return c; - } - if (unget.length > 1) { - c = unget[unget.length - 1]; - unget.length = unget.length - 1; - } else { - void* buf = &c; - size_t n = readBlock(buf,2); - if (n == 1 && readBlock(buf+1,1) == 0) - throw new ReadException("not enough data in stream"); - fixBO(&c,c.sizeof); - } - return c; - } - - override wchar[] readStringW(size_t length) { - wchar[] result = new wchar[length]; - readExact(result.ptr, length * wchar.sizeof); - fixBlockBO(result.ptr, wchar.sizeof, length); - return result; - } - - /// Write the specified BOM b to the source stream. - void writeBOM(BOM b) { - immutable ubyte[] bom = ByteOrderMarks[b]; - writeBlock(bom.ptr, bom.length); - } - - override void write(byte x) { writeExact(&x, x.sizeof); } - override void write(ubyte x) { writeExact(&x, x.sizeof); } - override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); } - override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); } - override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof); } - override void write(char x) { writeExact(&x, x.sizeof); } - override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } - - override void writeStringW(const(wchar)[] str) { - foreach(wchar cw;str) { - fixBO(&cw,2); - s.writeExact(&cw, 2); - } - } - - override @property bool eof() { return s.eof && !ungetAvailable(); } - override @property ulong size() { return s.size; } - - unittest { - MemoryStream m; - m = new MemoryStream (); - EndianStream em = new EndianStream(m,Endian.bigEndian); - uint x = 0x11223344; - em.write(x); - assert( m.data[0] == 0x11 ); - assert( m.data[1] == 0x22 ); - assert( m.data[2] == 0x33 ); - assert( m.data[3] == 0x44 ); - em.position = 0; - ushort x2 = 0x5566; - em.write(x2); - assert( m.data[0] == 0x55 ); - assert( m.data[1] == 0x66 ); - em.position = 0; - static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12]; - em.fixBO(x3.ptr,12); - if (std.system.endian == Endian.littleEndian) { - assert( x3[0] == 12 ); - assert( x3[1] == 11 ); - assert( x3[2] == 10 ); - assert( x3[4] == 8 ); - assert( x3[5] == 7 ); - assert( x3[6] == 6 ); - assert( x3[8] == 4 ); - assert( x3[9] == 3 ); - assert( x3[10] == 2 ); - assert( x3[11] == 1 ); - } - em.endian = Endian.littleEndian; - em.write(x); - assert( m.data[0] == 0x44 ); - assert( m.data[1] == 0x33 ); - assert( m.data[2] == 0x22 ); - assert( m.data[3] == 0x11 ); - em.position = 0; - em.write(x2); - assert( m.data[0] == 0x66 ); - assert( m.data[1] == 0x55 ); - em.position = 0; - em.fixBO(x3.ptr,12); - if (std.system.endian == Endian.bigEndian) { - assert( x3[0] == 12 ); - assert( x3[1] == 11 ); - assert( x3[2] == 10 ); - assert( x3[4] == 8 ); - assert( x3[5] == 7 ); - assert( x3[6] == 6 ); - assert( x3[8] == 4 ); - assert( x3[9] == 3 ); - assert( x3[10] == 2 ); - assert( x3[11] == 1 ); - } - em.writeBOM(BOM.UTF8); - assert( m.position == 3 ); - assert( m.data[0] == 0xEF ); - assert( m.data[1] == 0xBB ); - assert( m.data[2] == 0xBF ); - em.writeString ("Hello, world"); - em.position = 0; - assert( m.position == 0 ); - assert( em.readBOM() == BOM.UTF8 ); - assert( m.position == 3 ); - assert( em.getc() == 'H' ); - em.position = 0; - em.writeBOM(BOM.UTF16BE); - assert( m.data[0] == 0xFE ); - assert( m.data[1] == 0xFF ); - em.position = 0; - em.writeBOM(BOM.UTF16LE); - assert( m.data[0] == 0xFF ); - assert( m.data[1] == 0xFE ); - em.position = 0; - em.writeString ("Hello, world"); - em.position = 0; - assert( em.readBOM() == -1 ); - assert( em.getc() == 'H' ); - assert( em.getc() == 'e' ); - assert( em.getc() == 'l' ); - assert( em.getc() == 'l' ); - em.position = 0; - } -} - -/*** - * Parameterized subclass that wraps an array-like buffer with a stream - * interface. - * - * The type Buffer must support the length property, opIndex and opSlice. - * Compile in release mode when directly instantiating a TArrayStream to avoid - * link errors. - */ -class TArrayStream(Buffer): Stream { - Buffer buf; // current data - ulong len; // current data length - ulong cur; // current file position - - /// Create the stream for the the buffer buf. Non-copying. - this(Buffer buf) { - super (); - this.buf = buf; - this.len = buf.length; - readable = writeable = seekable = true; - } - - // ensure subclasses don't violate this - invariant() { - assert(len <= buf.length); - assert(cur <= len); - } - - override size_t readBlock(void* buffer, size_t size) { - assertReadable(); - ubyte* cbuf = cast(ubyte*) buffer; - if (len - cur < size) - size = cast(size_t)(len - cur); - ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; - cbuf[0 .. size] = ubuf[]; - cur += size; - return size; - } - - override size_t writeBlock(const void* buffer, size_t size) { - assertWriteable(); - ubyte* cbuf = cast(ubyte*) buffer; - ulong blen = buf.length; - if (cur + size > blen) - size = cast(size_t)(blen - cur); - ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; - ubuf[] = cbuf[0 .. size]; - cur += size; - if (cur > len) - len = cur; - return size; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - long scur; // signed to saturate to 0 properly - - switch (rel) { - case SeekPos.Set: scur = offset; break; - case SeekPos.Current: scur = cast(long)(cur + offset); break; - case SeekPos.End: scur = cast(long)(len + offset); break; - default: - assert(0); - } - - if (scur < 0) - cur = 0; - else if (scur > len) - cur = len; - else - cur = cast(ulong)scur; - - return cur; - } - - override @property size_t available () { return cast(size_t)(len - cur); } - - /// Get the current memory data in total. - @property ubyte[] data() { - if (len > size_t.max) - throw new StreamException("Stream too big"); - const(void)[] res = buf[0 .. cast(size_t)len]; - return cast(ubyte[])res; - } - - override string toString() { - // assume data is UTF8 - return to!(string)(cast(char[])data); - } -} - -/* Test the TArrayStream */ -unittest { - char[100] buf; - TArrayStream!(char[]) m; - - m = new TArrayStream!(char[]) (buf); - assert (m.isOpen); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.available == 88); - assert (m.seekSet (0) == 0); - assert (m.available == 100); - assert (m.seekCur (4) == 4); - assert (m.available == 96); - assert (m.seekEnd (-8) == 92); - assert (m.available == 8); - assert (m.size == 100); - assert (m.seekSet (4) == 4); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - assert (buf[0..12] == "Hello, wield"); - assert (m.position == 10); - assert (m.available == 90); - assert (m.size == 100); - m.seekSet (0); - assert (m.printf ("Answer is %d", 42) == 12); - assert (buf[0..12] == "Answer is 42"); -} - -/// This subclass reads and constructs an array of bytes in memory. -class MemoryStream: TArrayStream!(ubyte[]) { - - /// Create the output buffer and setup for reading, writing, and seeking. - // clear to an empty buffer. - this() { this(cast(ubyte[]) null); } - - /*** - * Create the output buffer and setup for reading, writing, and seeking. - * Load it with specific input data. - */ - this(ubyte[] buf) { super (buf); } - this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto - this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto - - /// Ensure the stream can write count extra bytes from cursor position without an allocation. - void reserve(size_t count) { - if (cur + count > buf.length) - buf.length = cast(uint)((cur + count) * 2); - } - - override size_t writeBlock(const void* buffer, size_t size) { - reserve(size); - return super.writeBlock(buffer,size); - } - - unittest { - MemoryStream m; - - m = new MemoryStream (); - assert (m.isOpen); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.seekSet (0) == 0); - assert (m.available == 12); - assert (m.seekCur (4) == 4); - assert (m.available == 8); - assert (m.seekEnd (-8) == 4); - assert (m.available == 8); - assert (m.size == 12); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - assert (cast(char[]) m.data == "Hello, wield"); - m.seekEnd (0); - m.writeString ("Foo"); - assert (m.position == 15); - assert (m.available == 0); - m.writeString ("Foo foo foo foo foo foo foo"); - assert (m.position == 42); - m.position = 0; - assert (m.available == 42); - m.writef("%d %d %s",100,345,"hello"); - auto str = m.toString(); - assert (str[0..13] == "100 345 hello", str[0 .. 13]); - assert (m.available == 29); - assert (m.position == 13); - - MemoryStream m2; - m.position = 3; - m2 = new MemoryStream (); - m2.writeString("before"); - m2.copyFrom(m,10); - str = m2.toString(); - assert (str[0..16] == "before 345 hello"); - m2.position = 3; - m2.copyFrom(m); - auto str2 = m.toString(); - str = m2.toString(); - assert (str == ("bef" ~ str2)); - } -} - -import std.mmfile; - -/*** - * This subclass wraps a memory-mapped file with the stream API. - * See std.mmfile module. - */ -class MmFileStream : TArrayStream!(MmFile) { - - /// Create stream wrapper for file. - this(MmFile file) { - super (file); - MmFile.Mode mode = file.mode(); - writeable = mode > MmFile.Mode.read; - } - - override void flush() { - if (isopen) { - super.flush(); - buf.flush(); - } - } - - override void close() { - if (isopen) { - super.close(); - delete buf; - buf = null; - } - } -} - -unittest { - auto test_file = undead.internal.file.deleteme ~ "-testing.txt"; - MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null); - MmFileStream m; - m = new MmFileStream (mf); - m.writeString ("Hello, world"); - assert (m.position == 12); - assert (m.seekSet (0) == 0); - assert (m.seekCur (4) == 4); - assert (m.seekEnd (-8) == 92); - assert (m.size == 100); - assert (m.seekSet (4)); - assert (m.readString (4) == "o, w"); - m.writeString ("ie"); - ubyte[] dd = m.data; - assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield"); - m.position = 12; - m.writeString ("Foo"); - assert (m.position == 15); - m.writeString ("Foo foo foo foo foo foo foo"); - assert (m.position == 42); - m.close(); - mf = new MmFile(test_file); - m = new MmFileStream (mf); - assert (!m.writeable); - char[] str = m.readString(12); - assert (str == "Hello, wield"); - m.close(); - std.file.remove(test_file); -} - - -/*** - * This subclass slices off a portion of another stream, making seeking relative - * to the boundaries of the slice. - * - * It could be used to section a large file into a - * set of smaller files, such as with tar archives. Reading and writing a - * SliceStream does not modify the position of the source stream if it is - * seekable. - */ -class SliceStream : FilterStream { - private { - ulong pos; // our position relative to low - ulong low; // low stream offset. - ulong high; // high stream offset. - bool bounded; // upper-bounded by high. - } - - /*** - * Indicate both the source stream to use for reading from and the low part of - * the slice. - * - * The high part of the slice is dependent upon the end of the source - * stream, so that if you write beyond the end it resizes the stream normally. - */ - this (Stream s, ulong low) - in { - assert (low <= s.size); - } - body { - super(s); - this.low = low; - this.high = 0; - this.bounded = false; - } - - /*** - * Indicate the high index as well. - * - * Attempting to read or write past the high - * index results in the end being clipped off. - */ - this (Stream s, ulong low, ulong high) - in { - assert (low <= high); - assert (high <= s.size); - } - body { - super(s); - this.low = low; - this.high = high; - this.bounded = true; - } - - invariant() { - if (bounded) - assert (pos <= high - low); - else - // size() does not appear to be const, though it should be - assert (pos <= (cast()s).size - low); - } - - override size_t readBlock (void *buffer, size_t size) { - assertReadable(); - if (bounded && size > high - low - pos) - size = cast(size_t)(high - low - pos); - ulong bp = s.position; - if (seekable) - s.position = low + pos; - size_t ret = super.readBlock(buffer, size); - if (seekable) { - pos = s.position - low; - s.position = bp; - } - return ret; - } - - override size_t writeBlock (const void *buffer, size_t size) { - assertWriteable(); - if (bounded && size > high - low - pos) - size = cast(size_t)(high - low - pos); - ulong bp = s.position; - if (seekable) - s.position = low + pos; - size_t ret = s.writeBlock(buffer, size); - if (seekable) { - pos = s.position - low; - s.position = bp; - } - return ret; - } - - override ulong seek(long offset, SeekPos rel) { - assertSeekable(); - long spos; - - switch (rel) { - case SeekPos.Set: - spos = offset; - break; - case SeekPos.Current: - spos = cast(long)(pos + offset); - break; - case SeekPos.End: - if (bounded) - spos = cast(long)(high - low + offset); - else - spos = cast(long)(s.size - low + offset); - break; - default: - assert(0); - } - - if (spos < 0) - pos = 0; - else if (bounded && spos > high - low) - pos = high - low; - else if (!bounded && spos > s.size - low) - pos = s.size - low; - else - pos = cast(ulong)spos; - - readEOF = false; - return pos; - } - - override @property size_t available() { - size_t res = s.available; - ulong bp = s.position; - if (bp <= pos+low && pos+low <= bp+res) { - if (!bounded || bp+res <= high) - return cast(size_t)(bp + res - pos - low); - else if (high <= bp+res) - return cast(size_t)(high - pos - low); - } - return 0; - } - - unittest { - MemoryStream m; - SliceStream s; - - m = new MemoryStream ((cast(char[])"Hello, world").dup); - s = new SliceStream (m, 4, 8); - assert (s.size == 4); - assert (m.position == 0); - assert (s.position == 0); - assert (m.available == 12); - assert (s.available == 4); - - assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); - assert (m.position == 0); - assert (s.position == 4); - assert (m.available == 12); - assert (s.available == 0); - assert (s.seekEnd (-2) == 2); - assert (s.available == 2); - assert (s.seekEnd (2) == 4); - assert (s.available == 0); - assert (m.position == 0); - assert (m.available == 12); - - m.seekEnd(0); - m.writeString("\nBlaho"); - assert (m.position == 18); - assert (m.available == 0); - assert (s.position == 4); - assert (s.available == 0); - - s = new SliceStream (m, 4); - assert (s.size == 14); - assert (s.toString () == "Vrooorld\nBlaho"); - s.seekEnd (0); - assert (s.available == 0); - - s.writeString (", etcetera."); - assert (s.position == 25); - assert (s.seekSet (0) == 0); - assert (s.size == 25); - assert (m.position == 18); - assert (m.size == 29); - assert (m.toString() == "HellVrooorld\nBlaho, etcetera."); - } -} diff --git a/views/version.txt b/views/version.txt index 8aa3390..c2634ef 100644 --- a/views/version.txt +++ b/views/version.txt @@ -4,4 +4,4 @@ struct Version { int minor; int patch; } -enum ver = Version(0, 6, 8); +enum ver = Version(0, 7, 0); |