Index: api/const.go ================================================================== --- api/const.go +++ api/const.go @@ -117,27 +117,31 @@ CommandRefresh = Command("refresh") ) // Supported search operator representations. const ( - BackwardDirective = "BACKWARD" // Backward-only context + BackwardDirective = "BACKWARD" // Backward-only context / thread ContextDirective = "CONTEXT" // Context directive CostDirective = "COST" // Maximum cost of a context operation - ForwardDirective = "FORWARD" // Forward-only context + DirectedDirective = "DIRECTED" // Context/thread collection can have general directions + FolgeDirective = "FOLGE" // Folge thread + ForwardDirective = "FORWARD" // Forward-only context / thread FullDirective = "FULL" // Include tags in context IdentDirective = "IDENT" // Use only specified zettel ItemsDirective = "ITEMS" // Select list elements in a zettel - MaxDirective = "MAX" // Maximum number of context results + MaxDirective = "MAX" // Maximum number of context / thread results MinDirective = "MIN" // Minimum number of context results LimitDirective = "LIMIT" // Maximum number of zettel OffsetDirective = "OFFSET" // Offset to start returned zettel list OrDirective = "OR" // Combine several search expression with an "or" OrderDirective = "ORDER" // Specify metadata keys for the order of returned list PhraseDirective = "PHRASE" // Only unlinked zettel with given phrase PickDirective = "PICK" // Pick some random zettel RandomDirective = "RANDOM" // Order zettel list randomly ReverseDirective = "REVERSE" // Reverse the order of a zettel list + SequelDirective = "SEQUEL" // Sequel / branching thread + ThreadDirective = "THREAD" // Both folge and Sequel thread UnlinkedDirective = "UNLINKED" // Search for zettel that contain a phase(s) but do not link ActionSeparator = "|" // Separates action list of previous elements of query expression KeysAction = "KEYS" // Provide metadata key used Index: client/client_test.go ================================================================== --- client/client_test.go +++ client/client_test.go @@ -25,27 +25,21 @@ "t73f.de/r/zsc/domain/id" ) func TestZettelList(t *testing.T) { c := getClient() - _, err := c.QueryZettel(context.Background(), "") - if err != nil { + if _, err := c.QueryZettel(context.Background(), ""); err != nil { t.Error(err) - return } } func TestGetProtectedZettel(t *testing.T) { c := getClient() - _, err := c.GetZettel(context.Background(), id.ZidStartupConfiguration, api.PartZettel) - if err != nil { - if cErr, ok := err.(*client.Error); ok && cErr.StatusCode == http.StatusForbidden { - return - } else { + if _, err := c.GetZettel(context.Background(), id.ZidStartupConfiguration, api.PartZettel); err != nil { + if cErr, ok := err.(*client.Error); !ok || cErr.StatusCode != http.StatusForbidden { t.Error(err) } - return } } func TestGetSzZettel(t *testing.T) { c := getClient() Index: domain/id/id.go ================================================================== --- domain/id/id.go +++ domain/id/id.go @@ -87,11 +87,10 @@ // WebUI image zettel are in the range 40000..49999 ZidEmoji = Zid(40001) // Other sxn code zettel are in the range 50000..59999 - ZidSxnPrelude = Zid(59900) // Predefined Zettelmarkup zettel are in the range 60000..69999 ZidRoleZettelZettel = Zid(60010) ZidRoleConfigurationZettel = Zid(60020) ZidRoleRoleZettel = Zid(60030) Index: domain/id/idset/idset.go ================================================================== --- domain/id/idset/idset.go +++ domain/id/idset/idset.go @@ -131,12 +131,16 @@ // Both sets can be modified by this method. One of them is the set returned. // It contains the intersection of both, if s is not nil. // // If s == nil, then the other set is always returned. func (s *Set) IntersectOrSet(other *Set) *Set { - if s == nil || other == nil { - return other.Clone() + if s == nil { + return other // no other.Clone(), since other != nil, i.e. "not found" + } + if other == nil { + s.seq = s.seq[:0] + return s } topos, spos, opos := 0, 0, 0 for spos < len(s.seq) && opos < len(other.seq) { sz, oz := s.seq[spos], other.seq[opos] if sz < oz { Index: domain/meta/meta.go ================================================================== --- domain/meta/meta.go +++ domain/meta/meta.go @@ -144,18 +144,16 @@ KeyForward = "forward" KeyLang = "lang" KeyLicense = "license" KeyModified = "modified" KeyPrecursor = "precursor" - KeyPredecessor = "predecessor" KeyPrequel = "prequel" KeyPublished = "published" KeyQuery = "query" KeyReadOnly = "read-only" KeySequel = "sequel" KeySubordinates = "subordinates" - KeySuccessors = "successors" KeySummary = "summary" KeySuperior = "superior" KeyURL = "url" KeyUselessFiles = "useless-files" KeyUserID = "user-id" @@ -172,11 +170,10 @@ registerKey(KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(KeyFolge, TypeIDSet, usageProperty, "") registerKey(KeySequel, TypeIDSet, usageProperty, "") - registerKey(KeySuccessors, TypeIDSet, usageProperty, "") registerKey(KeySubordinates, TypeIDSet, usageProperty, "") // Non-inverse keys registerKey(KeyAuthor, TypeString, usageUser, "") registerKey(KeyBack, TypeIDSet, usageProperty, "") @@ -191,11 +188,10 @@ registerKey(KeyForward, TypeIDSet, usageProperty, "") registerKey(KeyLang, TypeWord, usageUser, "") registerKey(KeyLicense, TypeEmpty, usageUser, "") registerKey(KeyModified, TypeTimestamp, usageComputed, "") registerKey(KeyPrecursor, TypeIDSet, usageUser, KeyFolge) - registerKey(KeyPredecessor, TypeID, usageUser, KeySuccessors) registerKey(KeyPrequel, TypeIDSet, usageUser, KeySequel) registerKey(KeyPublished, TypeTimestamp, usageProperty, "") registerKey(KeyQuery, TypeEmpty, usageUser, "") registerKey(KeyReadOnly, TypeWord, usageUser, "") registerKey(KeySummary, TypeString, usageUser, "") @@ -277,27 +273,40 @@ // SetNonEmpty stores the given value under the given key, if the value is non-empty. // An empty value will delete the previous association. func (m *Meta) SetNonEmpty(key string, value Value) { if value == "" { - delete(m.pairs, key) // TODO: key != KeyID + if key != KeyID { + delete(m.pairs, key) + } } else { m.Set(key, value.TrimSpace()) } } + +// Has returns true, if the given key is used in the metadata. +func (m *Meta) Has(key string) bool { + if m != nil { + if _, found := m.pairs[key]; found || key == KeyID { + return true + } + } + return false +} // Get retrieves the string value of a given key. The bool value signals, // whether there was a value stored or not. func (m *Meta) Get(key string) (Value, bool) { - if m == nil { - return "", false - } - if key == KeyID { - return Value(m.Zid.String()), true - } - value, ok := m.pairs[key] - return value, ok + if m != nil { + if value, found := m.pairs[key]; found { + return value, true + } + if key == KeyID { + return Value(m.Zid.String()), true + } + } + return "", false } // GetDefault retrieves the string value of the given key. If no value was // stored, the given default value is returned. func (m *Meta) GetDefault(key string, def Value) Value { Index: domain/meta/type.go ================================================================== --- domain/meta/type.go +++ domain/meta/type.go @@ -92,10 +92,12 @@ cachedTypedKeys = make(map[string]*DescriptionType) mxTypedKey sync.RWMutex suffixTypes = map[string]*DescriptionType{ "-date": TypeTimestamp, "-number": TypeNumber, + "-ref": TypeID, + "-refs": TypeIDSet, SuffixKeyRole: TypeWord, "-time": TypeTimestamp, SuffixKeyURL: TypeURL, "-zettel": TypeID, "-zid": TypeID, Index: domain/meta/values.go ================================================================== --- domain/meta/values.go +++ domain/meta/values.go @@ -20,10 +20,11 @@ "strings" "time" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/domain/id" + "t73f.de/r/zsx" "t73f.de/r/zsx/input" ) // Value ist a single metadata value. type Value string @@ -130,11 +131,11 @@ ValueSyntaxMarkdown = "markdown" // Syntax: Markdown / CommonMark ValueSyntaxMD = "md" // Syntax: Markdown / CommonMark ValueSyntaxNone = "none" // Syntax: no syntax / content, just metadata ValueSyntaxPlain = "plain" // Syntax: plain text ValueSyntaxPNG = "png" // Syntax: PNG image - ValueSyntaxSVG = "svg" // Syntax: SVG + ValueSyntaxSVG = zsx.SyntaxSVG // Syntax: SVG ValueSyntaxSxn = "sxn" // Syntax: S-Expression ValueSyntaxText = "text" // Syntax: plain text ValueSyntaxTxt = "txt" // Syntax: plain text ValueSyntaxWebp = "webp" // Syntax: WEBP image ValueSyntaxZmk = "zmk" // Syntax: Zettelmarkup Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -1,11 +1,11 @@ module t73f.de/r/zsc -go 1.24 +go 1.25 require ( - t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc - t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae - t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5 - t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 - t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce + t73f.de/r/sx v0.0.0-20250902163016-7a7a55d68dd6 + t73f.de/r/sxwebs v0.0.0-20250902155414-673110c9fb48 + t73f.de/r/webs v0.0.0-20250916082100-37e0307e3368 + t73f.de/r/zero v0.0.0-20250904163008-d96be3da2aef + t73f.de/r/zsx v0.0.0-20251007133258-f1844bbf951e ) Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,10 +1,10 @@ -t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc h1:tlsP+47Rf8i9Zv1TqRnwfbQx3nN/F/92RkT6iCA6SVA= -t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc/go.mod h1:hzg05uSCMk3D/DWaL0pdlowfL2aWQeGIfD1S04vV+Xg= -t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae h1:K6nxN/bb0BCSiDffwNPGTF2uf5WcTdxcQXzByXNuJ7M= -t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae/go.mod h1:0LQ9T1svSg9ADY/6vQLKNUu6LqpPi8FGr7fd2qDT5H8= -t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5 h1:nnKfs/2i9n3S5VjbSj98odcwZKGcL96qPSIUATT/2P8= -t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5/go.mod h1:zk92hSKB4iWyT290+163seNzu350TA9XLATC9kOldqo= -t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 h1:OuzHSfniY8UzLmo5zp1w23Kd9h7x9CSXP2jQ+kppeqU= -t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7/go.mod h1:T1vFcHoymUQcr7+vENBkS1yryZRZ3YB8uRtnMy8yRBA= -t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce h1:R9rtg4ecx4YYixsMmsh+wdcqLdY9GxoC5HZ9mMS33to= -t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce/go.mod h1:tXOlmsQBoY4mY7Plu0LCCMZNSJZJbng98fFarZXAWvM= +t73f.de/r/sx v0.0.0-20250902163016-7a7a55d68dd6 h1:vOyPEfxXze8QjcfxJK5GHb68PzDw2EomRwdUyDLnBVw= +t73f.de/r/sx v0.0.0-20250902163016-7a7a55d68dd6/go.mod h1:D4NKzI2FRtNW0zCcvf+yCEplfoSnZAXiax9otvasu0w= +t73f.de/r/sxwebs v0.0.0-20250902155414-673110c9fb48 h1:LCZQsACNivE0NkA6B296yTdHIN1wl88iAdx0J5pHzlA= +t73f.de/r/sxwebs v0.0.0-20250902155414-673110c9fb48/go.mod h1:yOrGohF5eS9fcVR6Posxfv7PVDldqxEC1ZucTddhBPI= +t73f.de/r/webs v0.0.0-20250916082100-37e0307e3368 h1:ul8l/OIWIlMDyt1Zc8N9dcTfXiT+p6o+6QUZbUp1Wqc= +t73f.de/r/webs v0.0.0-20250916082100-37e0307e3368/go.mod h1:G3vn6fCTvYWwQby5cVNmXzHlOGhgBDfbbo/9OgIxy0g= +t73f.de/r/zero v0.0.0-20250904163008-d96be3da2aef h1:ShqDx4DjirNThBlObVbmUqUwDuQ7qndxdFxBHs5Ui2Y= +t73f.de/r/zero v0.0.0-20250904163008-d96be3da2aef/go.mod h1:cNaE2o9BWPFqLkmDuYaWrMJQS7GOo+wwmB9y8VfAF6c= +t73f.de/r/zsx v0.0.0-20251007133258-f1844bbf951e h1:k0rbUZtWYeqSy8qrY3yOd8oyiT0hl8ekhpmVkx6AAfg= +t73f.de/r/zsx v0.0.0-20251007133258-f1844bbf951e/go.mod h1:/JvaST9OR796HoQv23GBfRlbZAtn+qgEuYOnesf1qyo= Index: sexp/sexp.go ================================================================== --- sexp/sexp.go +++ sexp/sexp.go @@ -19,10 +19,11 @@ "errors" "fmt" "sort" "t73f.de/r/sx" + "t73f.de/r/sx/sxbuiltins" "t73f.de/r/zsc/api" ) // EncodeZettel transforms zettel data into a sx object. func EncodeZettel(zettel api.ZettelData) sx.Object { @@ -80,11 +81,11 @@ } // EncodeMetaRights translates metadata/rights into a sx object. func EncodeMetaRights(mr api.MetaRights) *sx.Pair { return sx.MakeList( - sx.SymbolList, + sx.MakeSymbol(sxbuiltins.List.Name), meta2sz(mr.Meta), sx.MakeList(sx.MakeSymbol("rights"), sx.Int64(int64(mr.Rights))), ) } Index: shtml/const.go ================================================================== --- shtml/const.go +++ shtml/const.go @@ -11,73 +11,73 @@ // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package shtml -import "t73f.de/r/sx" +import "t73f.de/r/sxwebs/sxhtml" // Symbols for HTML header tags var ( - SymBody = sx.MakeSymbol("body") - SymHead = sx.MakeSymbol("head") - SymHTML = sx.MakeSymbol("html") - SymMeta = sx.MakeSymbol("meta") - SymScript = sx.MakeSymbol("script") - SymTitle = sx.MakeSymbol("title") + SymBody = sxhtml.MakeSymbol("body") + SymHead = sxhtml.MakeSymbol("head") + SymHTML = sxhtml.MakeSymbol("html") + SymMeta = sxhtml.MakeSymbol("meta") + SymScript = sxhtml.MakeSymbol("script") + SymTitle = sxhtml.MakeSymbol("title") ) // Symbols for HTML body tags var ( - SymA = sx.MakeSymbol("a") - SymASIDE = sx.MakeSymbol("aside") - symBLOCKQUOTE = sx.MakeSymbol("blockquote") - symBR = sx.MakeSymbol("br") - symCITE = sx.MakeSymbol("cite") - symCODE = sx.MakeSymbol("code") - symDD = sx.MakeSymbol("dd") - symDEL = sx.MakeSymbol("del") - SymDIV = sx.MakeSymbol("div") - symDL = sx.MakeSymbol("dl") - symDT = sx.MakeSymbol("dt") - symEM = sx.MakeSymbol("em") - SymEMBED = sx.MakeSymbol("embed") - SymFIGURE = sx.MakeSymbol("figure") - SymH1 = sx.MakeSymbol("h1") - SymH2 = sx.MakeSymbol("h2") - SymHR = sx.MakeSymbol("hr") - SymIMG = sx.MakeSymbol("img") - symINS = sx.MakeSymbol("ins") - symKBD = sx.MakeSymbol("kbd") - SymLI = sx.MakeSymbol("li") - symMARK = sx.MakeSymbol("mark") - SymOL = sx.MakeSymbol("ol") - SymP = sx.MakeSymbol("p") - symPRE = sx.MakeSymbol("pre") - symSAMP = sx.MakeSymbol("samp") - SymSPAN = sx.MakeSymbol("span") - SymSTRONG = sx.MakeSymbol("strong") - symSUB = sx.MakeSymbol("sub") - symSUP = sx.MakeSymbol("sup") - symTABLE = sx.MakeSymbol("table") - symTBODY = sx.MakeSymbol("tbody") - symTHEAD = sx.MakeSymbol("thead") - symTD = sx.MakeSymbol("td") - symTH = sx.MakeSymbol("th") - symTR = sx.MakeSymbol("tr") - SymUL = sx.MakeSymbol("ul") + SymA = sxhtml.MakeSymbol("a") + SymASIDE = sxhtml.MakeSymbol("aside") + symBLOCKQUOTE = sxhtml.MakeSymbol("blockquote") + symBR = sxhtml.MakeSymbol("br") + symCITE = sxhtml.MakeSymbol("cite") + symCODE = sxhtml.MakeSymbol("code") + symDD = sxhtml.MakeSymbol("dd") + symDEL = sxhtml.MakeSymbol("del") + SymDIV = sxhtml.MakeSymbol("div") + symDL = sxhtml.MakeSymbol("dl") + symDT = sxhtml.MakeSymbol("dt") + symEM = sxhtml.MakeSymbol("em") + SymEMBED = sxhtml.MakeSymbol("embed") + SymFIGURE = sxhtml.MakeSymbol("figure") + SymH1 = sxhtml.MakeSymbol("h1") + SymH2 = sxhtml.MakeSymbol("h2") + SymHR = sxhtml.MakeSymbol("hr") + SymIMG = sxhtml.MakeSymbol("img") + symINS = sxhtml.MakeSymbol("ins") + symKBD = sxhtml.MakeSymbol("kbd") + SymLI = sxhtml.MakeSymbol("li") + symMARK = sxhtml.MakeSymbol("mark") + SymOL = sxhtml.MakeSymbol("ol") + SymP = sxhtml.MakeSymbol("p") + symPRE = sxhtml.MakeSymbol("pre") + symSAMP = sxhtml.MakeSymbol("samp") + SymSPAN = sxhtml.MakeSymbol("span") + SymSTRONG = sxhtml.MakeSymbol("strong") + symSUB = sxhtml.MakeSymbol("sub") + symSUP = sxhtml.MakeSymbol("sup") + symTABLE = sxhtml.MakeSymbol("table") + symTBODY = sxhtml.MakeSymbol("tbody") + symTHEAD = sxhtml.MakeSymbol("thead") + symTD = sxhtml.MakeSymbol("td") + symTH = sxhtml.MakeSymbol("th") + symTR = sxhtml.MakeSymbol("tr") + SymUL = sxhtml.MakeSymbol("ul") ) // Symbols for HTML attribute keys var ( - SymAttrClass = sx.MakeSymbol("class") - SymAttrHref = sx.MakeSymbol("href") - SymAttrID = sx.MakeSymbol("id") - SymAttrLang = sx.MakeSymbol("lang") - SymAttrOpen = sx.MakeSymbol("open") - SymAttrRel = sx.MakeSymbol("rel") - SymAttrRole = sx.MakeSymbol("role") - SymAttrSrc = sx.MakeSymbol("src") - SymAttrTarget = sx.MakeSymbol("target") - SymAttrTitle = sx.MakeSymbol("title") - SymAttrType = sx.MakeSymbol("type") - SymAttrValue = sx.MakeSymbol("value") + SymAttrClass = sxhtml.MakeSymbol("class") + SymAttrHref = sxhtml.MakeSymbol("href") + SymAttrID = sxhtml.MakeSymbol("id") + SymAttrLang = sxhtml.MakeSymbol("lang") + SymAttrOpen = sxhtml.MakeSymbol("open") + SymAttrRel = sxhtml.MakeSymbol("rel") + SymAttrRole = sxhtml.MakeSymbol("role") + SymAttrSrc = sxhtml.MakeSymbol("src") + SymAttrTarget = sxhtml.MakeSymbol("target") + SymAttrTitle = sxhtml.MakeSymbol("title") + SymAttrType = sxhtml.MakeSymbol("type") + SymAttrValue = sxhtml.MakeSymbol("value") ) Index: shtml/shtml.go ================================================================== --- shtml/shtml.go +++ shtml/shtml.go @@ -67,17 +67,14 @@ plist := sx.Nil() keys := a.Keys() for i := len(keys) - 1; i >= 0; i-- { key := keys[i] if key != zsx.DefaultAttribute && isValidName(key) { - plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.MakeString(a[key]))) + plist = plist.Cons(sx.Cons(sxhtml.MakeSymbol(key), sx.MakeString(a[key]))) } } - if plist == nil { - return nil - } - return plist.Cons(sxhtml.SymAttr) + return plist } // Evaluate a metadata s-expression into a list of HTML s-expressions. func (ev *Evaluator) Evaluate(lst *sx.Pair, env *Environment) (*sx.Pair, error) { result := ev.Eval(lst, env) @@ -123,26 +120,24 @@ } var result sx.ListBuilder result.AddN( SymOL, - sx.Nil().Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-endnotes"))).Cons(sxhtml.SymAttr), + sx.Nil().Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-endnotes"))), ) for i, fni := range env.endnotes { noteNum := strconv.Itoa(i + 1) attrs := fni.attrs.Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-endnote"))). Cons(sx.Cons(SymAttrValue, sx.MakeString(noteNum))). Cons(sx.Cons(SymAttrID, sx.MakeString("fn:"+fni.noteID))). - Cons(sx.Cons(SymAttrRole, sx.MakeString("doc-endnote"))). - Cons(sxhtml.SymAttr) + Cons(sx.Cons(SymAttrRole, sx.MakeString("doc-endnote"))) backref := sx.Nil().Cons(sx.MakeString("\u21a9\ufe0e")). Cons(sx.Nil(). Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-endnote-backref"))). Cons(sx.Cons(SymAttrHref, sx.MakeString("#fnref:"+fni.noteID))). - Cons(sx.Cons(SymAttrRole, sx.MakeString("doc-backlink"))). - Cons(sxhtml.SymAttr)). + Cons(sx.Cons(SymAttrRole, sx.MakeString("doc-backlink")))). Cons(SymA) var li sx.ListBuilder li.AddN(SymLI, attrs) li.ExtendBang(fni.noteHx) @@ -236,14 +231,11 @@ } func (ev *Evaluator) bindMetadata() { ev.bind(sz.SymMeta, 0, ev.evalList) evalMetaString := func(args sx.Vector, env *Environment) sx.Object { - a := make(zsx.Attributes, 2). - Set("name", getSymbol(args[0], env).GetValue()). - Set("content", getString(args[1], env).GetValue()) - return ev.EvaluateMeta(a) + return ev.evalMetaString(args[0], getString(args[1], env).GetValue(), env) } ev.bind(sz.SymTypeCredential, 2, evalMetaString) ev.bind(sz.SymTypeEmpty, 2, evalMetaString) ev.bind(sz.SymTypeID, 2, evalMetaString) ev.bind(sz.SymTypeNumber, 2, evalMetaString) @@ -260,18 +252,28 @@ } s := sb.String() if len(s) > 0 { s = s[1:] } - a := make(zsx.Attributes, 2). - Set("name", getSymbol(args[0], env).GetValue()). - Set("content", s) - return ev.EvaluateMeta(a) + return ev.evalMetaString(args[0], s, env) } ev.bind(sz.SymTypeIDSet, 2, evalMetaSet) ev.bind(sz.SymTypeTagSet, 2, evalMetaSet) } + +func (ev *Evaluator) evalMetaString(nameObj sx.Object, content string, env *Environment) sx.Object { + if env.err == nil { + if nameSym, ok := sx.GetSymbol(nameObj); ok { + a := make(zsx.Attributes, 2). + Set("name", nameSym.GetValue()). + Set("content", content) + return ev.EvaluateMeta(a) + } + env.err = fmt.Errorf("%v/%T is not a symbol", nameObj, nameObj) + } + return sx.Nil() +} // EvaluateMeta returns HTML meta object for an attribute. func (ev *Evaluator) EvaluateMeta(a zsx.Attributes) *sx.Pair { return sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymMeta) } @@ -285,12 +287,17 @@ nLevel := getInt64(args[0], env) if nLevel <= 0 { env.err = fmt.Errorf("%v is a negative heading level", nLevel) return sx.Nil() } - level := strconv.FormatInt(nLevel+ev.headingOffset, 10) - headingSymbol := sx.MakeSymbol("h" + level) + hLevel := nLevel + ev.headingOffset + if hLevel > 6 { + env.err = fmt.Errorf("%v is a too large heading level", hLevel) + return sx.Nil() + } + sLevel := strconv.FormatInt(hLevel, 10) + headingSymbol := sxhtml.MakeSymbol("h" + sLevel) a := GetAttributes(args[1], env) env.pushAttributes(a) defer env.popAttributes() if fragment := getString(args[3], env).GetValue(); fragment != "" { @@ -324,12 +331,13 @@ var result sx.ListBuilder result.Add(symBLOCKQUOTE) if attrs := EvaluateAttributes(GetAttributes(args[0], env)); attrs != nil { result.Add(attrs) } + isCompact := isCompactList(args[1:]) for _, elem := range args[1:] { - if quote, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { + if quote, isPair := sx.GetPair(ev.Eval(makeCompactItem(isCompact, elem), env)); isPair { result.Add(quote.Cons(sxhtml.SymListSplice)) } } return result.List() }) @@ -360,20 +368,20 @@ } } return result.List() }) - ev.bind(zsx.SymTable, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymTable, 2, func(args sx.Vector, env *Environment) sx.Object { thead := sx.Nil() - if header := getList(args[0], env); !sx.IsNil(header) { + if header := getList(args[1], env); !sx.IsNil(header) { thead = sx.Nil().Cons(ev.evalTableRow(symTH, header, env)).Cons(symTHEAD) } var tbody sx.ListBuilder - if len(args) > 1 { + if len(args) > 2 { tbody.Add(symTBODY) - for _, row := range args[1:] { + for _, row := range args[2:] { tbody.Add(ev.evalTableRow(symTD, getList(row, env), env)) } } table := sx.Nil() @@ -430,13 +438,13 @@ content = sx.MakeString(visibleReplacer.Replace(content.GetValue())) } return evalVerbatim(a, content) }) ev.bind(zsx.SymVerbatimZettel, 0, nilFn) - ev.bind(zsx.SymBLOB, 4, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) - return evalBLOB(a, getList(args[1], env), getString(args[2], env), getString(args[3], env)) + return evalBLOB(a, ev.evalSlice(args[3:], env), getString(args[1], env), getString(args[2], env)) }) ev.bind(zsx.SymTransclude, 2, func(args sx.Vector, env *Environment) sx.Object { if refSym, refValue := GetReference(args[1], env); refSym != nil { if refSym.IsEqualSymbol(zsx.SymRefStateExternal) { a := GetAttributes(args[0], env).Set("src", refValue).AddClass("external") @@ -461,20 +469,50 @@ result.Add(sym) if attrs := EvaluateAttributes(GetAttributes(args[0], env)); attrs != nil { result.Add(attrs) } if len(args) > 1 { + isCompact := isCompactList(args[1:]) for _, elem := range args[1:] { - item := sx.Nil().Cons(SymLI) - if res, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { - item.ExtendBang(res) + var itemLb sx.ListBuilder + itemLb.Add(SymLI) + if res, isPair := sx.GetPair(ev.Eval(makeCompactItem(isCompact, elem), env)); isPair { + itemLb.ExtendBang(res) } - result.Add(item) + result.Add(itemLb.List()) } } return result.List() } +} +func isCompactList(elems sx.Vector) bool { + for _, elem := range elems { + item, isPair := sx.GetPair(elem) + if !isPair { + return false + } + if !zsx.SymBlock.IsEqual(item.Car()) { + return false + } + item = item.Tail() + if item.Tail() != nil { // more than two elements -> multiple paragraphs in item + return false + } + head := item.Head() + if !zsx.SymPara.IsEqual(head.Car()) { + return false + } + } + return true +} +func makeCompactItem(isCompact bool, elem sx.Object) sx.Object { + if isCompact { + if item, isPair := sx.GetPair(elem); isPair { + elem = item.Tail().Head().Tail().Cons(zsx.SymInline) + } + } + return elem } func (ev *Evaluator) evalDescriptionTerm(term *sx.Pair, env *Environment) *sx.Pair { var result sx.ListBuilder for obj := range term.Values() { @@ -642,14 +680,13 @@ noteID := ev.unique + noteNum env.endnotes = append(env.endnotes, endnoteInfo{ noteID: noteID, noteAST: args[1:], noteHx: nil, attrs: attrPlist}) hrefAttr := sx.Nil().Cons(sx.Cons(SymAttrRole, sx.MakeString("doc-noteref"))). Cons(sx.Cons(SymAttrHref, sx.MakeString("#fn:"+noteID))). - Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-noteref"))). - Cons(sxhtml.SymAttr) + Cons(sx.Cons(SymAttrClass, sx.MakeString("zs-noteref"))) href := sx.Nil().Cons(sx.MakeString(noteNum)).Cons(hrefAttr).Cons(SymA) - supAttr := sx.Nil().Cons(sx.Cons(SymAttrID, sx.MakeString("fnref:"+noteID))).Cons(sxhtml.SymAttr) + supAttr := sx.Nil().Cons(sx.Cons(SymAttrID, sx.MakeString("fnref:"+noteID))) return sx.Nil().Cons(href).Cons(supAttr).Cons(symSUP) }) ev.bind(zsx.SymFormatDelete, 1, ev.makeFormatFn(symDEL)) ev.bind(zsx.SymFormatEmph, 1, ev.makeFormatFn(symEM)) @@ -882,19 +919,10 @@ return result.Cons(SymSPAN) } return result.Cons(EvaluateAttributes(a)).Cons(SymA) } -func getSymbol(obj sx.Object, env *Environment) *sx.Symbol { - if env.err == nil { - if sym, ok := sx.GetSymbol(obj); ok { - return sym - } - env.err = fmt.Errorf("%v/%T is not a symbol", obj, obj) - } - return sx.MakeSymbol("???") -} func getString(val sx.Object, env *Environment) sx.String { if env.err == nil { if s, ok := sx.GetString(val); ok { return s } @@ -930,11 +958,11 @@ // GetReference returns the reference symbol and the reference value of a reference pair. func GetReference(val sx.Object, env *Environment) (*sx.Symbol, string) { if env.err == nil { if p := getList(val, env); env.err == nil { - sym, val := sz.GetReference(p) + sym, val := zsx.GetReference(p) if sym != nil { return sym, val } env.err = fmt.Errorf("%v/%T is not a reference", val, val) } Index: sz/ref.go ================================================================== --- sz/ref.go +++ sz/ref.go @@ -21,80 +21,59 @@ "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsx" ) -// MakeReference builds a reference node. -func MakeReference(sym *sx.Symbol, val string) *sx.Pair { - return sx.Cons(sym, sx.Cons(sx.MakeString(val), sx.Nil())) -} - -// GetReference returns the reference symbol and value. -func GetReference(ref *sx.Pair) (*sx.Symbol, string) { - if ref != nil { - if sym, isSymbol := sx.GetSymbol(ref.Car()); isSymbol { - val, isString := sx.GetString(ref.Cdr()) - if !isString { - val, isString = sx.GetString(ref.Tail().Car()) - } - if isString { - return sym, val.GetValue() - } - } - } - return nil, "" -} - // ScanReference scans a string and returns a reference. // // This function is very specific for Zettelstore. func ScanReference(s string) *sx.Pair { if len(s) == id.LengthZid { if _, err := id.Parse(s); err == nil { - return MakeReference(SymRefStateZettel, s) + return zsx.MakeReference(SymRefStateZettel, s) } if s == "00000000000000" { - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } } else if len(s) > id.LengthZid && s[id.LengthZid] == '#' { zidPart := s[:id.LengthZid] if _, err := id.Parse(zidPart); err == nil { if u, err2 := url.Parse(s); err2 != nil || u.String() != s { - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } - return MakeReference(SymRefStateZettel, s) + return zsx.MakeReference(SymRefStateZettel, s) } if zidPart == "00000000000000" { - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } } if strings.HasPrefix(s, api.QueryPrefix) { - return MakeReference(SymRefStateQuery, s[len(api.QueryPrefix):]) + return zsx.MakeReference(SymRefStateQuery, s[len(api.QueryPrefix):]) } if strings.HasPrefix(s, "//") { if u, err := url.Parse(s[1:]); err == nil { if u.Scheme == "" && u.Opaque == "" && u.Host == "" && u.User == nil { if u.String() == s[1:] { - return MakeReference(SymRefStateBased, s[1:]) + return zsx.MakeReference(SymRefStateBased, s[1:]) } - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } } } if s == "" { - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } u, err := url.Parse(s) if err != nil || u.String() != s { - return MakeReference(zsx.SymRefStateInvalid, s) + return zsx.MakeReference(zsx.SymRefStateInvalid, s) } sym := zsx.SymRefStateExternal if u.Scheme == "" && u.Opaque == "" && u.Host == "" && u.User == nil { if s[0] == '#' { sym = zsx.SymRefStateSelf } else { sym = zsx.SymRefStateHosted } } - return MakeReference(sym, s) + return zsx.MakeReference(sym, s) } Index: sz/zmk/block.go ================================================================== --- sz/zmk/block.go +++ sz/zmk/block.go @@ -669,11 +669,11 @@ if cp.lastRow == nil { if row.IsEmpty() { return nil } cp.lastRow = sx.Cons(row.List(), nil) - return cp.lastRow.Cons(nil).Cons(zsx.SymTable) + return cp.lastRow.Cons(nil).Cons(nil).Cons(zsx.SymTable) } cp.lastRow = cp.lastRow.AppendBang(row.List()) return nil } // inp.Ch must be '|' @@ -741,7 +741,7 @@ inp.Next() // consume last '}' attrs := parseBlockAttributes(inp) inp.SkipToEOL() refText := string(inp.Src[posA:posE]) ref := cp.scanReference(refText) - return zsx.MakeTransclusion(attrs, ref, sx.Nil()), true + return zsx.MakeTransclusion(attrs, ref, nil), true } Index: sz/zmk/post-processor.go ================================================================== --- sz/zmk/post-processor.go +++ sz/zmk/post-processor.go @@ -23,30 +23,30 @@ var symInVerse = sx.MakeSymbol("in-verse") var symNoBlock = sx.MakeSymbol("no-block") type postProcessor struct{} -func (pp *postProcessor) VisitBefore(lst *sx.Pair, env *sx.Pair) (sx.Object, bool) { +func (pp *postProcessor) VisitBefore(lst *sx.Pair, alst *sx.Pair) (sx.Object, bool) { if lst == nil { return nil, true } sym, isSym := sx.GetSymbol(lst.Car()) if !isSym { panic(lst) } if fn, found := symMap[sym]; found { - return fn(pp, lst, env), true + return fn(pp, lst, alst), true } return nil, false } func (pp *postProcessor) VisitAfter(lst *sx.Pair, _ *sx.Pair) sx.Object { return lst } -func (pp *postProcessor) visitPairList(lst *sx.Pair, env *sx.Pair) *sx.Pair { +func (pp *postProcessor) visitPairList(lst *sx.Pair, alst *sx.Pair) *sx.Pair { var pList sx.ListBuilder for node := range lst.Pairs() { - if elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), env)); isPair && elem != nil { + if elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), alst)); isPair && elem != nil { pList.Add(elem) } } return pList.List() } @@ -94,43 +94,43 @@ } } func ignoreProcess(*postProcessor, *sx.Pair, *sx.Pair) *sx.Pair { return nil } -func postProcessBlockList(pp *postProcessor, lst *sx.Pair, env *sx.Pair) *sx.Pair { - result := pp.visitPairList(lst.Tail(), env) +func postProcessBlockList(pp *postProcessor, lst *sx.Pair, alst *sx.Pair) *sx.Pair { + result := pp.visitPairList(lst.Tail(), alst) if result == nil { - if noBlockPair := env.Assoc(symNoBlock); noBlockPair == nil || sx.IsTrue(noBlockPair.Cdr()) { + if noBlockPair := alst.Assoc(symNoBlock); noBlockPair == nil || sx.IsTrue(noBlockPair.Cdr()) { return nil } } return result.Cons(lst.Car()) } -func postProcessInlineList(pp *postProcessor, lst *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessInlineList(pp *postProcessor, lst *sx.Pair, alst *sx.Pair) *sx.Pair { sym := lst.Car() - if rest := pp.visitInlines(lst.Tail(), env); rest != nil { + if rest := pp.visitInlines(lst.Tail(), alst); rest != nil { return rest.Cons(sym) } return nil } -func postProcessRegion(pp *postProcessor, rn *sx.Pair, env *sx.Pair) *sx.Pair { - return doPostProcessRegion(pp, rn, env, env) +func postProcessRegion(pp *postProcessor, rn *sx.Pair, alst *sx.Pair) *sx.Pair { + return doPostProcessRegion(pp, rn, alst, alst) } -func postProcessRegionVerse(pp *postProcessor, rn *sx.Pair, env *sx.Pair) *sx.Pair { - return doPostProcessRegion(pp, rn, env.Cons(sx.Cons(symInVerse, nil)), env) +func postProcessRegionVerse(pp *postProcessor, rn *sx.Pair, alst *sx.Pair) *sx.Pair { + return doPostProcessRegion(pp, rn, alst.Cons(sx.Cons(symInVerse, nil)), alst) } -func doPostProcessRegion(pp *postProcessor, rn *sx.Pair, envBlock, envInline *sx.Pair) *sx.Pair { +func doPostProcessRegion(pp *postProcessor, rn *sx.Pair, alstBlock, alstInline *sx.Pair) *sx.Pair { sym := rn.Car().(*sx.Symbol) next := rn.Tail() attrs := next.Car().(*sx.Pair) next = next.Tail() - blocks := pp.visitPairList(next.Head(), envBlock) - text := pp.visitInlines(next.Tail(), envInline) + blocks := pp.visitPairList(next.Head(), alstBlock) + text := pp.visitInlines(next.Tail(), alstInline) if blocks == nil && text == nil { return nil } return zsx.MakeRegion(sym, attrs, blocks, text) } @@ -140,37 +140,37 @@ return verb } return nil } -func postProcessHeading(pp *postProcessor, hn *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessHeading(pp *postProcessor, hn *sx.Pair, alst *sx.Pair) *sx.Pair { next := hn.Tail() level := next.Car().(sx.Int64) next = next.Tail() attrs := next.Car().(*sx.Pair) next = next.Tail() slug := next.Car().(sx.String) next = next.Tail() fragment := next.Car().(sx.String) - if text := pp.visitInlines(next.Tail(), env); text != nil { + if text := pp.visitInlines(next.Tail(), alst); text != nil { return zsx.MakeHeading(int(level), attrs, text, slug.GetValue(), fragment.GetValue()) } return nil } -func postProcessItemList(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessItemList(pp *postProcessor, ln *sx.Pair, alst *sx.Pair) *sx.Pair { attrs := ln.Tail().Head() - elems := pp.visitListElems(ln.Tail(), env) + elems := pp.visitListElems(ln.Tail(), alst) if elems == nil { return nil } return zsx.MakeList(ln.Car().(*sx.Symbol), attrs, elems) } -func postProcessQuoteList(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessQuoteList(pp *postProcessor, ln *sx.Pair, alst *sx.Pair) *sx.Pair { attrs := ln.Tail().Head() - elems := pp.visitListElems(ln.Tail(), env.Cons(sx.Cons(symNoBlock, nil))) + elems := pp.visitListElems(ln.Tail(), alst.Cons(sx.Cons(symNoBlock, nil))) // Collect multiple paragraph items into one item. var newElems sx.ListBuilder var newPara sx.ListBuilder @@ -204,78 +204,79 @@ } addtoParagraph() return zsx.MakeList(ln.Car().(*sx.Symbol), attrs, newElems.List()) } -func (pp *postProcessor) visitListElems(ln *sx.Pair, env *sx.Pair) *sx.Pair { +func (pp *postProcessor) visitListElems(ln *sx.Pair, alst *sx.Pair) *sx.Pair { var pList sx.ListBuilder for node := range ln.Tail().Pairs() { - if elem := zsx.Walk(pp, node.Head(), env); elem != nil { + if elem := zsx.Walk(pp, node.Head(), alst); elem != nil { pList.Add(elem) } } return pList.List() } -func postProcessDescription(pp *postProcessor, dl *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessDescription(pp *postProcessor, dl *sx.Pair, alst *sx.Pair) *sx.Pair { attrs := dl.Tail().Head() var dList sx.ListBuilder isTerm := false for node := range dl.Tail().Tail().Pairs() { isTerm = !isTerm if isTerm { - dList.Add(pp.visitInlines(node.Head(), env)) + dList.Add(pp.visitInlines(node.Head(), alst)) } else { - dList.Add(zsx.Walk(pp, node.Head(), env)) + dList.Add(zsx.Walk(pp, node.Head(), alst.Cons(sx.Cons(symNoBlock, nil)))) } } return dList.List().Cons(attrs).Cons(dl.Car()) } -func postProcessTable(pp *postProcessor, tbl *sx.Pair, env *sx.Pair) *sx.Pair { - sym := tbl.Car() - next := tbl.Tail() +func postProcessTable(pp *postProcessor, tbl *sx.Pair, alst *sx.Pair) *sx.Pair { + sym, next := tbl.Car(), tbl.Tail() + attrs := next.Head() + next = next.Tail() header := next.Head() if header != nil { // Already post-processed return tbl } - rows, width := pp.visitRows(next.Tail(), env) + rows, width := pp.visitRows(next.Tail(), alst) if rows == nil { // Header and row are nil -> no table return nil } header, rows, align := splitTableHeader(rows, width) alignRow(header, align) for node := range rows.Pairs() { alignRow(node.Head(), align) } - return rows.Cons(header).Cons(sym) + return rows.Cons(header).Cons(attrs).Cons(sym) } -func (pp *postProcessor) visitRows(rows *sx.Pair, env *sx.Pair) (*sx.Pair, int) { +func (pp *postProcessor) visitRows(rows *sx.Pair, alst *sx.Pair) (*sx.Pair, int) { maxWidth := 0 var pRows sx.ListBuilder for node := range rows.Pairs() { row := node.Head() - row, width := pp.visitCells(row, env) + row, width := pp.visitCells(row, alst) if maxWidth < width { maxWidth = width } pRows.Add(row) } return pRows.List(), maxWidth } -func (pp *postProcessor) visitCells(cells *sx.Pair, env *sx.Pair) (*sx.Pair, int) { +func (pp *postProcessor) visitCells(cells *sx.Pair, alst *sx.Pair) (*sx.Pair, int) { width := 0 var pCells sx.ListBuilder for node := range cells.Pairs() { cell := node.Head() rest := cell.Tail() attrs := rest.Head() - ins := pp.visitInlines(rest.Tail(), env) + ins := pp.visitInlines(rest.Tail(), alst) pCells.Add(zsx.MakeCell(attrs, ins)) width++ } return pCells.List(), width } @@ -396,20 +397,20 @@ default: return sx.MakeString(""), false } } -func (pp *postProcessor) visitInlines(lst *sx.Pair, env *sx.Pair) *sx.Pair { +func (pp *postProcessor) visitInlines(lst *sx.Pair, alst *sx.Pair) *sx.Pair { length := lst.Length() if length <= 0 { return nil } - inVerse := env.Assoc(symInVerse) != nil + inVerse := alst.Assoc(symInVerse) != nil vector := make([]*sx.Pair, 0, length) // 1st phase: process all childs, ignore ' ' / '\t' at start, and merge some elements for node := range lst.Pairs() { - elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), env)) + elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), alst)) if !isPair || elem == nil { continue } elemSym := elem.Car() elemTail := elem.Tail() @@ -525,64 +526,64 @@ } } return nil } -func postProcessSoft(_ *postProcessor, sn *sx.Pair, env *sx.Pair) *sx.Pair { - if env.Assoc(symInVerse) == nil { +func postProcessSoft(_ *postProcessor, sn *sx.Pair, alst *sx.Pair) *sx.Pair { + if alst.Assoc(symInVerse) == nil { return sn } return sx.Cons(zsx.SymHard, nil) } -func postProcessEndnote(pp *postProcessor, en *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessEndnote(pp *postProcessor, en *sx.Pair, alst *sx.Pair) *sx.Pair { next := en.Tail() attrs := next.Car().(*sx.Pair) - if text := pp.visitInlines(next.Tail(), env); text != nil { + if text := pp.visitInlines(next.Tail(), alst); text != nil { return zsx.MakeEndnote(attrs, text) } return zsx.MakeEndnote(attrs, sx.Nil()) } -func postProcessMark(pp *postProcessor, en *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessMark(pp *postProcessor, en *sx.Pair, alst *sx.Pair) *sx.Pair { next := en.Tail() mark := next.Car().(sx.String) next = next.Tail() slug := next.Car().(sx.String) next = next.Tail() fragment := next.Car().(sx.String) - text := pp.visitInlines(next.Tail(), env) + text := pp.visitInlines(next.Tail(), alst) return zsx.MakeMark(mark.GetValue(), slug.GetValue(), fragment.GetValue(), text) } -func postProcessInlines4(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessInlines4(pp *postProcessor, ln *sx.Pair, alst *sx.Pair) *sx.Pair { sym := ln.Car() next := ln.Tail() attrs := next.Car() next = next.Tail() val3 := next.Car() - text := pp.visitInlines(next.Tail(), env) + text := pp.visitInlines(next.Tail(), alst) return text.Cons(val3).Cons(attrs).Cons(sym) } -func postProcessEmbed(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessEmbed(pp *postProcessor, ln *sx.Pair, alst *sx.Pair) *sx.Pair { next := ln.Tail() attrs := next.Car().(*sx.Pair) next = next.Tail() ref := next.Car() next = next.Tail() syntax := next.Car().(sx.String) - text := pp.visitInlines(next.Tail(), env) + text := pp.visitInlines(next.Tail(), alst) return zsx.MakeEmbed(attrs, ref, syntax.GetValue(), text) } -func postProcessFormat(pp *postProcessor, fn *sx.Pair, env *sx.Pair) *sx.Pair { +func postProcessFormat(pp *postProcessor, fn *sx.Pair, alst *sx.Pair) *sx.Pair { symFormat := fn.Car().(*sx.Symbol) next := fn.Tail() // Attrs attrs := next.Car().(*sx.Pair) next = next.Tail() // Possible inlines if next == nil { return fn } - inlines := pp.visitInlines(next, env) + inlines := pp.visitInlines(next, alst) return zsx.MakeFormat(symFormat, attrs, inlines) } Index: sz/zmk/zmk.go ================================================================== --- sz/zmk/zmk.go +++ sz/zmk/zmk.go @@ -59,14 +59,11 @@ if cp.nestingLevel != 0 { panic("Nesting level was not decremented") } var pp postProcessor - if bs := pp.visitPairList(blkBuild.List(), nil); bs != nil { - return bs.Cons(zsx.SymBlock) - } - return nil + return pp.visitPairList(blkBuild.List(), nil).Cons(zsx.SymBlock) } func withQueryPrefix(src []byte) bool { return len(src) > len(api.QueryPrefix) && string(src[:len(api.QueryPrefix)]) == api.QueryPrefix } Index: sz/zmk/zmk_test.go ================================================================== --- sz/zmk/zmk_test.go +++ sz/zmk/zmk_test.go @@ -55,23 +55,34 @@ t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) parser.Initialize(inp) ast := parser.Parse() - zsx.Walk(astWalker{}, ast, nil) - got := ast.String() - if tc.want != got { - st.Errorf("\nwant=%q\n got=%q", tc.want, got) + if got := ast.String(); tc.want != got { + st.Errorf("none\nwant=%q\n got=%q", tc.want, got) + } + copyAST := zsx.Walk(astWalker{}, ast, nil) + if got := copyAST.String(); tc.want != got { + st.Errorf("copy\nwant=%q\n got=%q", tc.want, got) + } + zsx.WalkIt(astWalkerIt{}, ast, nil) + if got := ast.String(); tc.want != got { + st.Errorf("itit\nwant=%q\n got=%q", tc.want, got) } }) } } type astWalker struct{} -func (astWalker) VisitBefore(node *sx.Pair, env *sx.Pair) (sx.Object, bool) { return sx.Nil(), false } -func (astWalker) VisitAfter(node *sx.Pair, env *sx.Pair) sx.Object { return node } +func (astWalker) VisitBefore(*sx.Pair, *sx.Pair) (sx.Object, bool) { return sx.Nil(), false } +func (astWalker) VisitAfter(node *sx.Pair, _ *sx.Pair) sx.Object { return node } + +type astWalkerIt struct{} + +func (astWalkerIt) VisitBefore(*sx.Pair, *sx.Pair) bool { return false } +func (astWalkerIt) VisitAfter(*sx.Pair, *sx.Pair) {} func TestEdges(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"\"\"\"\n; \n0{{0}}{0}\n\"\"\"", "(BLOCK (REGION-VERSE () ((DESCRIPTION () ()) (PARA (TEXT \"0\") (EMBED ((\"0\" . \"\")) (HOSTED \"0\") \"\")))))"}, @@ -79,15 +90,15 @@ } func TestEOL(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"", "()"}, - {"\n", "()"}, - {"\r", "()"}, - {"\r\n", "()"}, - {"\n\n", "()"}, + {"", "(BLOCK)"}, + {"\n", "(BLOCK)"}, + {"\r", "(BLOCK)"}, + {"\r\n", "(BLOCK)"}, + {"\n\n", "(BLOCK)"}, }) } func TestText(t *testing.T) { t.Parallel() @@ -95,15 +106,15 @@ {"abcd", "(BLOCK (PARA (TEXT \"abcd\")))"}, {"ab cd", "(BLOCK (PARA (TEXT \"ab cd\")))"}, {"abcd ", "(BLOCK (PARA (TEXT \"abcd\")))"}, {" abcd", "(BLOCK (PARA (TEXT \"abcd\")))"}, {"\\", "(BLOCK (PARA (TEXT \"\\\\\")))"}, - {"\\\n", "()"}, + {"\\\n", "(BLOCK)"}, {"\\\ndef", "(BLOCK (PARA (HARD) (TEXT \"def\")))"}, - {"\\\r", "()"}, + {"\\\r", "(BLOCK)"}, {"\\\rdef", "(BLOCK (PARA (HARD) (TEXT \"def\")))"}, - {"\\\r\n", "()"}, + {"\\\r\n", "(BLOCK)"}, {"\\\r\ndef", "(BLOCK (PARA (HARD) (TEXT \"def\")))"}, {"\\a", "(BLOCK (PARA (TEXT \"a\")))"}, {"\\aa", "(BLOCK (PARA (TEXT \"aa\")))"}, {"a\\a", "(BLOCK (PARA (TEXT \"aa\")))"}, {"\\+", "(BLOCK (PARA (TEXT \"+\")))"}, @@ -113,33 +124,33 @@ } func TestSpace(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {" ", "()"}, - {"\t", "()"}, - {" ", "()"}, + {" ", "(BLOCK)"}, + {"\t", "(BLOCK)"}, + {" ", "(BLOCK)"}, }) } func TestSoftBreak(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"x\ny", "(BLOCK (PARA (TEXT \"x\") (SOFT) (TEXT \"y\")))"}, {"z\n", "(BLOCK (PARA (TEXT \"z\")))"}, - {" \n ", "()"}, - {" \n", "()"}, + {" \n ", "(BLOCK)"}, + {" \n", "(BLOCK)"}, }) } func TestHardBreak(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"x \ny", "(BLOCK (PARA (TEXT \"x\") (HARD) (TEXT \"y\")))"}, {"z \n", "(BLOCK (PARA (TEXT \"z\")))"}, - {" \n ", "()"}, - {" \n", "()"}, + {" \n ", "(BLOCK)"}, + {" \n", "(BLOCK)"}, }) } func TestLink(t *testing.T) { t.Parallel() @@ -493,20 +504,20 @@ } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"@@@\n@@@", "()"}, + {"@@@\n@@@", "(BLOCK)"}, {"@@@\nabc\n@@@", "(BLOCK (VERBATIM-ZETTEL () \"abc\"))"}, {"@@@@def\nabc\n@@@@", "(BLOCK (VERBATIM-ZETTEL ((\"\" . \"def\")) \"abc\"))"}, }) } func TestVerbatimCode(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"```\n```", "()"}, + {"```\n```", "(BLOCK)"}, {"```\nabc\n```", "(BLOCK (VERBATIM-CODE () \"abc\"))"}, {"```\nabc\n````", "(BLOCK (VERBATIM-CODE () \"abc\"))"}, {"````\nabc\n````", "(BLOCK (VERBATIM-CODE () \"abc\"))"}, {"````\nabc\n```\n````", "(BLOCK (VERBATIM-CODE () \"abc\\n```\"))"}, {"````go\nabc\n````", "(BLOCK (VERBATIM-CODE ((\"\" . \"go\")) \"abc\"))"}, @@ -514,11 +525,11 @@ } func TestVerbatimEval(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"~~~\n~~~", "()"}, + {"~~~\n~~~", "(BLOCK)"}, {"~~~\nabc\n~~~", "(BLOCK (VERBATIM-EVAL () \"abc\"))"}, {"~~~\nabc\n~~~~", "(BLOCK (VERBATIM-EVAL () \"abc\"))"}, {"~~~~\nabc\n~~~~", "(BLOCK (VERBATIM-EVAL () \"abc\"))"}, {"~~~~\nabc\n~~~\n~~~~", "(BLOCK (VERBATIM-EVAL () \"abc\\n~~~\"))"}, {"~~~~go\nabc\n~~~~", "(BLOCK (VERBATIM-EVAL ((\"\" . \"go\")) \"abc\"))"}, @@ -526,11 +537,11 @@ } func TestVerbatimMath(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"$$$\n$$$", "()"}, + {"$$$\n$$$", "(BLOCK)"}, {"$$$\nabc\n$$$", "(BLOCK (VERBATIM-MATH () \"abc\"))"}, {"$$$\nabc\n$$$$", "(BLOCK (VERBATIM-MATH () \"abc\"))"}, {"$$$$\nabc\n$$$$", "(BLOCK (VERBATIM-MATH () \"abc\"))"}, {"$$$$\nabc\n$$$\n$$$$", "(BLOCK (VERBATIM-MATH () \"abc\\n$$$\"))"}, {"$$$$go\nabc\n$$$$", "(BLOCK (VERBATIM-MATH ((\"\" . \"go\")) \"abc\"))"}, @@ -538,11 +549,11 @@ } func TestVerbatimComment(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"%%%\n%%%", "()"}, + {"%%%\n%%%", "(BLOCK)"}, {"%%%\nabc\n%%%", "(BLOCK (VERBATIM-COMMENT () \"abc\"))"}, {"%%%%go\nabc\n%%%%", "(BLOCK (VERBATIM-COMMENT ((\"\" . \"go\")) \"abc\"))"}, }) } @@ -555,11 +566,11 @@ } func TestSpanRegion(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {":::\n:::", "()"}, + {":::\n:::", "(BLOCK)"}, {":::\nabc\n:::", "(BLOCK (REGION-BLOCK () ((PARA (TEXT \"abc\")))))"}, {":::\nabc\n::::", "(BLOCK (REGION-BLOCK () ((PARA (TEXT \"abc\")))))"}, {"::::\nabc\n::::", "(BLOCK (REGION-BLOCK () ((PARA (TEXT \"abc\")))))"}, {"::::\nabc\n:::\ndef\n:::\n::::", "(BLOCK (REGION-BLOCK () ((PARA (TEXT \"abc\")) (REGION-BLOCK () ((PARA (TEXT \"def\")))))))"}, {":::{go}\n:::a", "(BLOCK (REGION-BLOCK ((\"go\" . \"\")) () (TEXT \"a\")))"}, @@ -568,11 +579,11 @@ } func TestQuoteRegion(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"<<<\n<<<", "()"}, + {"<<<\n<<<", "(BLOCK)"}, {"<<<\nabc\n<<<", "(BLOCK (REGION-QUOTE () ((PARA (TEXT \"abc\")))))"}, {"<<<\nabc\n<<<<", "(BLOCK (REGION-QUOTE () ((PARA (TEXT \"abc\")))))"}, {"<<<<\nabc\n<<<<", "(BLOCK (REGION-QUOTE () ((PARA (TEXT \"abc\")))))"}, {"<<<<\nabc\n<<<\ndef\n<<<\n<<<<", "(BLOCK (REGION-QUOTE () ((PARA (TEXT \"abc\")) (REGION-QUOTE () ((PARA (TEXT \"def\")))))))"}, {"<<\n|b|c", "(BLOCK (TABLE ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ())) ((CELL ((align . \"right\")) (TEXT \"b\")) (CELL () (TEXT \"c\")))))"}, - {"|=\n||", "(BLOCK (TABLE ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ((align . \"right\")) (TEXT \"b\"))) ((CELL ()) (CELL ((align . \"right\"))))))"}, + {"|", "(BLOCK)"}, + {"||", "(BLOCK (TABLE () () ((CELL ()))))"}, + {"| |", "(BLOCK (TABLE () () ((CELL ()))))"}, + {"|a", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")))))"}, + {"|a|", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")))))"}, + {"|a| ", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")) (CELL ()))))"}, + {"|a|b", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\")))))"}, + {"|a\n|b", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\"))) ((CELL () (TEXT \"b\")))))"}, + {"|a|b\n|c|d", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL () (TEXT \"d\")))))"}, + {"|%", "(BLOCK)"}, + {"|=a", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")))))"}, + {"|=a\n|b", "(BLOCK (TABLE () ((CELL () (TEXT \"a\"))) ((CELL () (TEXT \"b\")))))"}, + {"|a|b\n|%---\n|c|d", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL () (TEXT \"d\")))))"}, + {"|a|b\n|c", "(BLOCK (TABLE () () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL ()))))"}, + {"|=\n|b|c", "(BLOCK (TABLE () ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ())) ((CELL ((align . \"right\")) (TEXT \"b\")) (CELL () (TEXT \"c\")))))"}, + {"|=\n||", "(BLOCK (TABLE () ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ((align . \"right\")) (TEXT \"b\"))) ((CELL ()) (CELL ((align . \"right\"))))))"}, }) } func TestTransclude(t *testing.T) { t.Parallel() @@ -854,8 +866,8 @@ } func TestTemp(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"", "()"}, + {"", "(BLOCK)"}, }) }