summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/pgset/main.go12
-rw-r--r--go.mod6
-rw-r--r--lib/parse/parse.go21
-rw-r--r--lib/translate/translate.go306
4 files changed, 286 insertions, 59 deletions
diff --git a/cmd/pgset/main.go b/cmd/pgset/main.go
index f871f2b..b58a85e 100644
--- a/cmd/pgset/main.go
+++ b/cmd/pgset/main.go
@@ -7,6 +7,7 @@ import (
"os"
"pgset/lib/parse"
"strings"
+ "pgset/lib/translate"
)
func main() {
@@ -22,8 +23,11 @@ func main() {
if err != nil {
log.Fatal(err)
}
- fmt.Println(document[0])
- //neatroff(tree)
- //tex(tree)
- //sile(tree)
+ if document == nil {
+ fmt.Println("o");
+ }
+ err = translate.Translate(document, "Neatroff", os.Stdout)
+ if err != nil {
+ log.Fatal("wow")
+ }
}
diff --git a/go.mod b/go.mod
index 32c7315..d000c63 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,7 @@
module pgset
-go 1.19
+go 1.23.0
+
+toolchain go1.24.5
+
+require golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc
diff --git a/lib/parse/parse.go b/lib/parse/parse.go
index a86e166..54a77c3 100644
--- a/lib/parse/parse.go
+++ b/lib/parse/parse.go
@@ -7,13 +7,13 @@ import (
)
type Tag struct {
- name string
- attributes map[string]string
+ Name string
+ Attributes map[string]string
}
func (t *Tag) String() string {
- s := "<" + t.name
- for k, v := range t.attributes {
+ s := "<" + t.Name
+ for k, v := range t.Attributes {
s += " " + k + `="` + v + `"`
}
s += ">"
@@ -52,14 +52,21 @@ func ReadUntil(r io.Reader, sentinels []byte) (string, byte, error) {
func ReadTag(r io.Reader) (*Tag, error) {
e := new(Tag)
- e.attributes = make(map[string]string)
+ e.Attributes = make(map[string]string)
var err error
var foundSentinel byte
- e.name, foundSentinel, err = ReadUntil(r, []byte{' ', '>'})
+ e.Name, foundSentinel, err = ReadUntil(r, []byte{' ', '>'})
if err != nil {
return nil, err
}
+ if e.Name[0] == '!' {
+ // Not An Element
+ e.Name = "NAE"
+ // Consume rest of tag
+ _, _, err := ReadUntil(r, []byte{'>'})
+ return e, err
+ }
for {
if foundSentinel == '>' {
@@ -88,7 +95,7 @@ func ReadTag(r io.Reader) (*Tag, error) {
value, foundSentinel, err = ReadUntil(r, []byte{' ', '>'})
value = string(peek) + value
}
- e.attributes[key] = value
+ e.Attributes[key] = value
}
return e, err
diff --git a/lib/translate/translate.go b/lib/translate/translate.go
index d02e8b4..613da3a 100644
--- a/lib/translate/translate.go
+++ b/lib/translate/translate.go
@@ -1,25 +1,116 @@
package translate
import (
- "fmt"
"io"
"pgset/lib/parse"
+ "errors"
+ "strings"
)
-func a(language string) string {
- return ""
+type State struct {
+ // Which type of anchor was last begun: href or id?
+ lastAnchor string
+ // Consume input until further notice?
+ consume bool
+ // Stay on the same line of output until further notice?
+ sameLine bool
+ // Stat on the same line of output at least for this input?
+ sameLineOnce bool
+
+ // metadata
+ title string
+ language string
+ creator string
+ subject string
+}
+
+func style(state *State) {
+ state.consume = true
+}
+func slashStyle(state *State) {
+ state.consume = false
}
-func img(language string) string {
- return ""
+func href(t *parse.Tag, language string) (string, error) {
+ switch language {
+ case "Neatroff":
+ // .post.url #chapter23\ntext\n\n
+ return ".post.url " + t.Attributes["href"], nil
+ default:
+ return "", errors.New("Unimplemented")
+ }
+}
+
+func id(t *parse.Tag, language string) (string, error) {
+ switch language {
+ case "Neatroff":
+ // .post.name chapter23
+ return ".post.name " + strings.Replace(t.Attributes["id"], "#", "", 1), nil
+ default:
+ return "", errors.New("Unimplemented")
+ }
}
-func header(language string) string {
- return ""
+func a(t *parse.Tag, language string, state *State) (string, error) {
+ // There are two kinds of anchors.
+ // href: refer to a resource, internal or external
+ // id: serve as an anchor to be referred to
+ types := []string{"href", "id"}
+ // Least bad expression of this idea in this language
+ m := map[string]func (*parse.Tag, string) (string, error) {
+ "href": href,
+ "id": id,
+ }
+ for _, i := range types {
+ if _, ok := t.Attributes[i]; ok {
+ state.lastAnchor = i
+ s, err := m[i](t, language)
+ if err != nil {
+ return "", err
+ }
+ return s, nil
+ }
+ }
+ return "", errors.New("Broken anchor. " + t.String())
+}
+func slashA(t *parse.Tag, language string, state *State) (string, error) {
+ var s string
+ switch state.lastAnchor {
+ case "href":
+ fallthrough
+ case "id":
+ fallthrough
+ default:
+ s = "\n"
+ }
+ state.lastAnchor = ""
+ return s, nil
}
-func footer(language string) string {
- return ""
+func meta(t *parse.Tag, language string, state *State) (string, error) {
+ // Multiple types of this tag. Only those with "name" attribute are interesting
+ s := ""
+ if _, ok := t.Attributes["name"]; ok {
+ s += "name"
+ }
+ return s, nil
+}
+
+func i(state *State) (string, error) {
+ state.sameLine = true
+ return `\fI`, nil
+}
+func slashI(state *State) (string, error) {
+ state.sameLine = false
+ state.sameLineOnce = true
+ return `\fP`, nil
+}
+func strong(state *State) (string, error) {
+ state.sameLine = true
+ return `\fB`, nil
+}
+func slashStrong(state *State) (string, error) {
+ return slashI(state)
}
// Among all languages, these are defined:
@@ -32,28 +123,74 @@ func footer(language string) string {
// - a: anchors to bookmarks and urls, switch on attribute
// - img: images
// - p: paragraphs
-// - meta: metadata
+// - meta, link: metadata
// - h{1,2,3,4,5}: headings
// - strong, i: emphasis
// - table, tbody, tr, td: Table of Contents
// - div: chapter separations
// - br: line breaks
+// - blockquote: block quotations
+// - hr: horizontal rule
//
// All other tags are ignored.
-// - span: Unwanted metadata, contents to be consumed
+// - span: not meaningful, essentially empty
+// - body: not meaningful
+// - head: not meaningful
+// - Non-standard and atypical extensions
var Definitions = map[string]map[string]any{
"ConTeXt": {
// Must generate.
- "a": a,
+ "a": a,
"/a": ``,
- "meta": meta,
+ // Standardized metadata.
+ // charset: character set
+ //
+ "meta": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
"/meta": ``,
- "div": div,
+ "div": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
"/div": ``,
+ "Header": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
+ "Footer": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
+ // link tags are empty
+ "link": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
// img tags are empty
- "img": img,
- "Header": header,
- "Footer": footer,
+ "img": func(t *parse.Tag) (string, error) {
+ var s string
+ for k, v := range t.Attributes {
+ s = k + v
+ }
+ return s, nil
+ },
// Constants.
"p": ``,
@@ -80,10 +217,14 @@ var Definitions = map[string]map[string]any{
"/h4": ``,
"h5": ``,
"/h5": ``,
+ "blockquote": ``,
+ "/blockquote": ``,
// br tags are empty
"br": ``,
+ // hr tags are empty
+ "hr": ``,
"Prefix": ``,
- "Postfix", ``,
+ "Postfix": ``,
},
"kerTeX": {},
"XeTeX": {},
@@ -95,6 +236,9 @@ var Definitions = map[string]map[string]any{
.ssh 15
.kn 1
.hlm 2
+.de empty
+..
+.blm empty
.ds margin 1.25i
.po \*[margin]
.de header
@@ -103,6 +247,9 @@ var Definitions = map[string]map[string]any{
.de footer
' bp
..
+.de pg
+. ti +1m
+..
.wh 0 header
.wh -\*[margin] footer
.ds measure 6i
@@ -173,6 +320,65 @@ var Definitions = map[string]map[string]any{
. /h2
..`,
"Footer": ``,
+ "Prefix": "",
+ "Postfix": "",
+ "NAE": ``,
+ "a": a,
+ "/a": slashA,
+ // Constants.
+ "p": `.pg`,
+ "/p": ``,
+ "i": i,
+ "/i": slashI,
+ "strong": strong,
+ "/strong": slashStrong,
+ "table": ``,
+ "/table": ``,
+ "tr": ``,
+ "/tr": ``,
+ "tbody": ``,
+ "/tbody": ``,
+ "td": ``,
+ "/td": ``,
+ "h1": ``,
+ "/h1": ``,
+ "h2": ``,
+ "/h2": ``,
+ "h3": ``,
+ "/h3": ``,
+ "h4": ``,
+ "/h4": ``,
+ "h5": ``,
+ "/h5": ``,
+ "blockquote": ``,
+ "/blockquote": ``,
+ // br tags are empty
+ "br": ``,
+ // hr tags are empty
+ "hr": ``,
+ "head": ``,
+ "/head": ``,
+ "title": ``,
+ "/title": ``,
+ "style": style,
+ "/style": slashStyle,
+ "html": ``,
+ "/html": ``,
+ "link": ``,
+ "meta": ``,
+ "body": ``,
+ "/body": ``,
+ "div": ``,
+ "/div": ``,
+ "section": ``,
+ "/section": ``,
+ "span": ``,
+ "/span": ``,
+ "li": ``,
+ "/li": ``,
+ "ul": ``,
+ "/ul": ``,
+ "img": ``,
},
"Utmac": {},
"groff": {},
@@ -402,39 +608,45 @@ It's a versatile family providing users with plenty of features and moods. It sp
},
}
-// Templates.
-// Optically correct scaling.
-// Some people need larger body type.
-
-func style(language, key, value string) (string, error) {
- return "", nil
-}
-
-func generate(mark, language string) (string, error) {
- if Definitions[language] == nil {
- return errors.New(language + " is a new language, define it")
- }
-}
-
-func Generate(language string, tag Tag) string {
- switch t := Definitions[language][mark].(type) {
- case string:
- return t
- case func(Tag):
- t(tag)
- }
-}
-
-func Translate(document []any, language string, output io.Writer) {
+func Translate(document []any, language string, output io.Writer) error {
+ output.Write([]byte(Definitions[language]["Header"].(string) + "\n"))
+ state := new(State)
+ var err error
for _, i := range document {
+ var out string
switch t := i.(type) {
case string:
- output.Write(generate(language, *Tag{name: "Prefix"}))
- output.Write(definitions[language]["Prefix"])
- output.Write(t)
- output.Write(generate(language, *Tag{name: "Postfix"}))
+ out = Definitions[language]["Prefix"].(string) + t + Definitions[language]["Postfix"].(string)
case *parse.Tag:
- fmt.Println(i.name)
+ switch u := Definitions[language][t.Name].(type) {
+ case string:
+ out = u
+ case func(*parse.Tag) (string, error):
+ out, err = u(t)
+ case func(*parse.Tag, string, *State) (string, error):
+ out, err = u(t, language, state)
+ case func(*State) (string, error):
+ out, err = u(state)
+ case func(*State):
+ u(state)
+ out = ""
+ case nil:
+ out = "dne" + t.String() + "\n"
+ }
+ }
+ if err != nil {
+ return err
+ }
+ if out != "" && !state.consume {
+ if !state.sameLine && !state.sameLineOnce {
+ out += "\n"
+ }
+ if state.sameLineOnce {
+ state.sameLineOnce = false
+ }
+ output.Write([]byte(out))
}
}
+ output.Write([]byte(Definitions[language]["Footer"].(string) + "\n"))
+ return nil
}