ADDED .github/dependabot.yml Index: .github/dependabot.yml ================================================================== --- /dev/null +++ .github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + rebase-strategy: "disabled" Index: api/api.go ================================================================== --- api/api.go +++ api/api.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package api contains common definitions used for client and server. package api @@ -23,11 +20,11 @@ // IsValid returns true, if the idenfifier contains 14 digits. func (zid ZettelID) IsValid() bool { if len(zid) != 14 { return false } - for i := range 14 { + for i := 0; i < 14; i++ { ch := zid[i] if ch < '0' || '9' < ch { return false } } Index: api/const.go ================================================================== --- api/const.go +++ api/const.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package api import "fmt" @@ -87,10 +84,11 @@ MetaString = "String" MetaTagSet = "TagSet" MetaTimestamp = "Timestamp" MetaURL = "URL" MetaWord = "Word" + MetaWordSet = "WordSet" MetaZettelmarkup = "Zettelmarkup" ) // Predefined general Metadata keys const ( @@ -269,11 +267,10 @@ const ( BackwardDirective = "BACKWARD" ContextDirective = "CONTEXT" CostDirective = "COST" ForwardDirective = "FORWARD" - FullDirective = "FULL" IdentDirective = "IDENT" ItemsDirective = "ITEMS" MaxDirective = "MAX" LimitDirective = "LIMIT" OffsetDirective = "OFFSET" @@ -285,20 +282,10 @@ ReverseDirective = "REVERSE" UnlinkedDirective = "UNLINKED" ActionSeparator = "|" - AtomAction = "ATOM" - KeysAction = "KEYS" - MinAction = "MIN" - MaxAction = "MAX" - NumberedAction = "NUMBERED" - RedirectAction = "REDIRECT" - ReIndexAction = "REINDEX" - RSSAction = "RSS" - TitleAction = "TITLE" - ExistOperator = "?" ExistNotOperator = "!?" SearchOperatorNot = "!" SearchOperatorEqual = "=" @@ -314,8 +301,5 @@ SearchOperatorLess = "<" SearchOperatorNotLess = "!<" SearchOperatorGreater = ">" SearchOperatorNotGreater = "!>" ) - -// QueryPrefix is the prefix that denotes a query expression within a reference. -const QueryPrefix = "query:" Index: api/urlbuilder.go ================================================================== --- api/urlbuilder.go +++ api/urlbuilder.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package api import ( Index: attrs/attrs.go ================================================================== --- attrs/attrs.go +++ attrs/attrs.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package attrs stores attributes of zettel parts. package attrs Index: attrs/attrs_test.go ================================================================== --- attrs/attrs_test.go +++ attrs/attrs_test.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package attrs_test import ( Index: client/client.go ================================================================== --- client/client.go +++ client/client.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package client provides a client for accessing the Zettelstore via its API. package client @@ -27,11 +24,10 @@ "strings" "time" "zettelstore.de/client.fossil/api" "zettelstore.de/client.fossil/sexp" - "zettelstore.de/client.fossil/sz" "zettelstore.de/sx.fossil" "zettelstore.de/sx.fossil/sxreader" ) // Client contains all data to execute requests. @@ -127,12 +123,11 @@ Message: resp.Status[4:], Body: body, } } -// NewURLBuilder creates a new URL builder for the client with the given key. -func (c *Client) NewURLBuilder(key byte) *api.URLBuilder { +func (c *Client) newURLBuilder(key byte) *api.URLBuilder { return api.NewURLBuilder(c.base, key) } func (*Client) newRequest(ctx context.Context, method string, ub *api.URLBuilder, body io.Reader) (*http.Request, error) { return http.NewRequestWithContext(ctx, method, ub.String(), body) } @@ -192,16 +187,16 @@ } vals, err := sexp.ParseList(obj, "ssi") if err != nil { return err } - token := string(vals[1].(sx.String)) + token := vals[1].(sx.String).String() if len(token) < 4 { return fmt.Errorf("no valid token found: %q", token) } c.token = token - c.tokenType = string(vals[0].(sx.String)) + c.tokenType = vals[0].(sx.String).String() c.expires = time.Now().Add(time.Duration(vals[2].(sx.Int64)*9/10) * time.Second) return nil } func (c *Client) updateToken(ctx context.Context) error { @@ -215,30 +210,30 @@ } // Authenticate sets a new token by sending user name and password. func (c *Client) Authenticate(ctx context.Context) error { authData := url.Values{"username": {c.username}, "password": {c.password}} - req, err := c.newRequest(ctx, http.MethodPost, c.NewURLBuilder('a'), strings.NewReader(authData.Encode())) + req, err := c.newRequest(ctx, http.MethodPost, c.newURLBuilder('a'), strings.NewReader(authData.Encode())) if err != nil { return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return c.executeAuthRequest(req) } // RefreshToken updates the access token func (c *Client) RefreshToken(ctx context.Context) error { - req, err := c.newRequest(ctx, http.MethodPut, c.NewURLBuilder('a'), nil) + req, err := c.newRequest(ctx, http.MethodPut, c.newURLBuilder('a'), nil) if err != nil { return err } return c.executeAuthRequest(req) } // CreateZettel creates a new zettel and returns its URL. func (c *Client) CreateZettel(ctx context.Context, data []byte) (api.ZettelID, error) { - ub := c.NewURLBuilder('z') + ub := c.newURLBuilder('z') resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, bytes.NewBuffer(data), nil) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() @@ -259,11 +254,11 @@ func (c *Client) CreateZettelData(ctx context.Context, data api.ZettelData) (api.ZettelID, error) { var buf bytes.Buffer if _, err := sx.Print(&buf, sexp.EncodeZettel(data)); err != nil { return api.InvalidZID, err } - ub := c.NewURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) + ub := c.newURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, &buf, nil) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() @@ -280,11 +275,11 @@ var bsLF = []byte{'\n'} // QueryZettel returns a list of all Zettel. func (c *Client) QueryZettel(ctx context.Context, query string) ([][]byte, error) { - ub := c.NewURLBuilder('z').AppendQuery(query) + ub := c.newURLBuilder('z').AppendQuery(query) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return nil, err } defer resp.Body.Close() @@ -306,11 +301,11 @@ return lines, nil } // QueryZettelData returns a list of zettel metadata. func (c *Client) QueryZettelData(ctx context.Context, query string) (string, string, []api.ZidMetaRights, error) { - ub := c.NewURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData).AppendQuery(query) + ub := c.newURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData).AppendQuery(query) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return "", "", nil, err } defer resp.Body.Close() @@ -337,11 +332,11 @@ hVals, err := sexp.ParseList(vals[2], "ys") if err != nil { return "", "", nil, err } metaList, err := parseMetaList(vals[3].(*sx.Pair)) - return sz.GoValue(qVals[1]), sz.GoValue(hVals[1]), metaList, err + return qVals[1].String(), hVals[1].String(), metaList, err } func parseMetaList(metaPair *sx.Pair) ([]api.ZidMetaRights, error) { if metaPair == nil { return nil, fmt.Errorf("no zettel list") @@ -411,11 +406,11 @@ return zid, nil } // QueryAggregate returns a aggregate as a result of a query. // It is most often used in a query with an action, where the action is either -// a metadata key of type Word or of type TagSet. +// a metadata key of type Word, WordSet, or TagSet. func (c *Client) QueryAggregate(ctx context.Context, query string) (api.Aggregate, error) { lines, err := c.QueryZettel(ctx, query) if err != nil { return nil, err } @@ -452,11 +447,11 @@ func (c *Client) fetchTagOrRoleZettel(ctx context.Context, key, val string) (api.ZettelID, error) { if c.client.CheckRedirect == nil { panic("client does not allow to track redirect") } - ub := c.NewURLBuilder('z').AppendKVQuery(key, val) + ub := c.newURLBuilder('z').AppendKVQuery(key, val) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() @@ -479,11 +474,11 @@ } } // GetZettel returns a zettel as a string. func (c *Client) GetZettel(ctx context.Context, zid api.ZettelID, part string) ([]byte, error) { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) if part != "" && part != api.PartContent { ub.AppendKVQuery(api.QueryKeyPart, part) } resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { @@ -501,11 +496,11 @@ return data, err } // GetZettelData returns a zettel as a struct of its parts. func (c *Client) GetZettelData(ctx context.Context, zid api.ZettelID) (api.ZettelData, error) { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) ub.AppendKVQuery(api.QueryKeyPart, api.PartZettel) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err == nil { defer resp.Body.Close() @@ -530,11 +525,11 @@ func (c *Client) GetEvaluatedZettel(ctx context.Context, zid api.ZettelID, enc api.EncodingEnum) ([]byte, error) { return c.getZettelString(ctx, zid, enc, false) } func (c *Client) getZettelString(ctx context.Context, zid api.ZettelID, enc api.EncodingEnum, parseOnly bool) ([]byte, error) { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, enc.String()) ub.AppendKVQuery(api.QueryKeyPart, api.PartContent) if parseOnly { ub.AppendKVQuery(api.QueryKeyParseOnly, "") } @@ -551,21 +546,21 @@ } return io.ReadAll(resp.Body) } // GetParsedSz returns an parsed zettel as a Sexpr-decoded data structure. -func (c *Client) GetParsedSz(ctx context.Context, zid api.ZettelID, part string) (sx.Object, error) { - return c.getSz(ctx, zid, part, true) +func (c *Client) GetParsedSz(ctx context.Context, zid api.ZettelID, part string, sf sx.SymbolFactory) (sx.Object, error) { + return c.getSz(ctx, zid, part, true, sf) } // GetEvaluatedSz returns an evaluated zettel as a Sexpr-decoded data structure. -func (c *Client) GetEvaluatedSz(ctx context.Context, zid api.ZettelID, part string) (sx.Object, error) { - return c.getSz(ctx, zid, part, false) +func (c *Client) GetEvaluatedSz(ctx context.Context, zid api.ZettelID, part string, sf sx.SymbolFactory) (sx.Object, error) { + return c.getSz(ctx, zid, part, false, sf) } -func (c *Client) getSz(ctx context.Context, zid api.ZettelID, part string, parseOnly bool) (sx.Object, error) { - ub := c.NewURLBuilder('z').SetZid(zid) +func (c *Client) getSz(ctx context.Context, zid api.ZettelID, part string, parseOnly bool, sf sx.SymbolFactory) (sx.Object, error) { + ub := c.newURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingSz) if part != "" { ub.AppendKVQuery(api.QueryKeyPart, part) } if parseOnly { @@ -577,16 +572,16 @@ } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, statusToError(resp) } - return sxreader.MakeReader(bufio.NewReaderSize(resp.Body, 8)).Read() + return sxreader.MakeReader(bufio.NewReaderSize(resp.Body, 8), sxreader.WithSymbolFactory(sf)).Read() } // GetMetaData returns the metadata of a zettel. func (c *Client) GetMetaData(ctx context.Context, zid api.ZettelID) (api.MetaRights, error) { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) ub.AppendKVQuery(api.QueryKeyPart, api.PartMeta) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return api.MetaRights{}, err @@ -624,11 +619,11 @@ }, nil } // UpdateZettel updates an existing zettel. func (c *Client) UpdateZettel(ctx context.Context, zid api.ZettelID, data []byte) error { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, bytes.NewBuffer(data), nil) if err != nil { return err } defer resp.Body.Close() @@ -642,11 +637,11 @@ func (c *Client) UpdateZettelData(ctx context.Context, zid api.ZettelID, data api.ZettelData) error { var buf bytes.Buffer if _, err := sx.Print(&buf, sexp.EncodeZettel(data)); err != nil { return err } - ub := c.NewURLBuilder('z').SetZid(zid).AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) + ub := c.newURLBuilder('z').SetZid(zid).AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, &buf, nil) if err != nil { return err } defer resp.Body.Close() @@ -656,13 +651,13 @@ return nil } // RenameZettel renames a zettel. func (c *Client) RenameZettel(ctx context.Context, oldZid, newZid api.ZettelID) error { - ub := c.NewURLBuilder('z').SetZid(oldZid) + ub := c.newURLBuilder('z').SetZid(oldZid) h := http.Header{ - api.HeaderDestination: {c.NewURLBuilder('z').SetZid(newZid).String()}, + api.HeaderDestination: {c.newURLBuilder('z').SetZid(newZid).String()}, } resp, err := c.buildAndExecuteRequest(ctx, api.MethodMove, ub, nil, h) if err != nil { return err } @@ -673,11 +668,11 @@ return nil } // DeleteZettel deletes a zettel with the given identifier. func (c *Client) DeleteZettel(ctx context.Context, zid api.ZettelID) error { - ub := c.NewURLBuilder('z').SetZid(zid) + ub := c.newURLBuilder('z').SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodDelete, ub, nil, nil) if err != nil { return err } defer resp.Body.Close() @@ -687,11 +682,11 @@ return nil } // ExecuteCommand will execute a given command at the Zettelstore. func (c *Client) ExecuteCommand(ctx context.Context, command api.Command) error { - ub := c.NewURLBuilder('x').AppendKVQuery(api.QueryKeyCommand, string(command)) + ub := c.newURLBuilder('x').AppendKVQuery(api.QueryKeyCommand, string(command)) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, nil, nil) if err != nil { return err } defer resp.Body.Close() @@ -701,11 +696,11 @@ return nil } // GetVersionInfo returns version information.. func (c *Client) GetVersionInfo(ctx context.Context) (VersionInfo, error) { - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, c.NewURLBuilder('x'), nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, c.newURLBuilder('x'), nil, nil) if err != nil { return VersionInfo{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { @@ -717,12 +712,12 @@ if vals, errVals := sexp.ParseList(obj, "iiiss"); errVals == nil { return VersionInfo{ Major: int(vals[0].(sx.Int64)), Minor: int(vals[1].(sx.Int64)), Patch: int(vals[2].(sx.Int64)), - Info: string(vals[3].(sx.String)), - Hash: string(vals[4].(sx.String)), + Info: vals[3].(sx.String).String(), + Hash: vals[4].(sx.String).String(), }, nil } } return VersionInfo{}, err } @@ -733,23 +728,5 @@ Minor int Patch int Info string Hash string } - -// Get executes a GET request to the given URL and returns the read data. -func (c *Client) Get(ctx context.Context, ub *api.URLBuilder) ([]byte, error) { - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) - if err != nil { - return nil, err - } - defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: - case http.StatusNoContent: - return nil, nil - default: - return nil, statusToError(resp) - } - data, err := io.ReadAll(resp.Body) - return data, err -} Index: client/client_test.go ================================================================== --- client/client_test.go +++ client/client_test.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package client_test import ( @@ -20,10 +17,12 @@ "net/url" "testing" "zettelstore.de/client.fossil/api" "zettelstore.de/client.fossil/client" + "zettelstore.de/client.fossil/sz" + "zettelstore.de/sx.fossil" ) func TestZettelList(t *testing.T) { c := getClient() _, err := c.QueryZettel(context.Background(), "") @@ -46,11 +45,14 @@ } } func TestGetSzZettel(t *testing.T) { c := getClient() - value, err := c.GetEvaluatedSz(context.Background(), api.ZidDefaultHome, api.PartContent) + sf := sx.MakeMappedFactory(1024) + var zetSyms sz.ZettelSymbols + zetSyms.InitializeZettelSymbols(sf) + value, err := c.GetEvaluatedSz(context.Background(), api.ZidDefaultHome, api.PartContent, sf) if err != nil { t.Error(err) return } if value.IsNil() { Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -1,5 +1,5 @@ module zettelstore.de/client.fossil -go 1.22 +go 1.21 -require zettelstore.de/sx.fossil v0.0.0-20240304124557-67e0a1799d1d +require zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207 Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,2 +1,2 @@ -zettelstore.de/sx.fossil v0.0.0-20240304124557-67e0a1799d1d h1:Gl5ZmdNV5wJsNMIQYjAd/sWLq2ng4NP+eglWU7lQP+I= -zettelstore.de/sx.fossil v0.0.0-20240304124557-67e0a1799d1d/go.mod h1:/iGHxFXoo6GSV04PUkwaLuFrrCa5LMorxD73iLMAruI= +zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207 h1:8ch54z0w53bps6a00NDofEqo3AJ1l7ITXyC3XyLmlY4= +zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207/go.mod h1:Uw3OLM1ufOM4Xe0G51mvkTDUv2okd+HyDBMx+0ZG7ME= DELETED input/entity.go Index: input/entity.go ================================================================== --- input/entity.go +++ /dev/null @@ -1,162 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2022-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2022-present Detlef Stern -//----------------------------------------------------------------------------- - -package input - -import ( - "html" - "unicode" -) - -// ScanEntity scans either a named or a numbered entity and returns it as a string. -// -// For numbered entities (like { or ģ) html.UnescapeString returns -// sometimes other values as expected, if the number is not well-formed. This -// may happen because of some strange HTML parsing rules. But these do not -// apply to Zettelmarkup. Therefore, I parse the number here in the code. -func (inp *Input) ScanEntity() (res string, success bool) { - if inp.Ch != '&' { - return "", false - } - pos := inp.Pos - inp.Next() - if inp.Ch == '#' { - inp.Next() - if inp.Ch == 'x' || inp.Ch == 'X' { - return inp.scanEntityBase16() - } - return inp.scanEntityBase10() - } - return inp.scanEntityNamed(pos) -} - -func (inp *Input) scanEntityBase16() (string, bool) { - inp.Next() - if inp.Ch == ';' { - return "", false - } - code := 0 - for { - switch ch := inp.Ch; ch { - case ';': - inp.Next() - if r := rune(code); isValidEntity(r) { - return string(r), true - } - return "", false - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - code = 16*code + int(ch-'0') - case 'a', 'b', 'c', 'd', 'e', 'f': - code = 16*code + int(ch-'a'+10) - case 'A', 'B', 'C', 'D', 'E', 'F': - code = 16*code + int(ch-'A'+10) - default: - return "", false - } - if code > unicode.MaxRune { - return "", false - } - inp.Next() - } -} - -func (inp *Input) scanEntityBase10() (string, bool) { - // Base 10 code - if inp.Ch == ';' { - return "", false - } - code := 0 - for { - switch ch := inp.Ch; ch { - case ';': - inp.Next() - if r := rune(code); isValidEntity(r) { - return string(r), true - } - return "", false - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - code = 10*code + int(ch-'0') - default: - return "", false - } - if code > unicode.MaxRune { - return "", false - } - inp.Next() - } -} -func (inp *Input) scanEntityNamed(pos int) (string, bool) { - for { - switch inp.Ch { - case EOS, '\n', '\r', '&': - return "", false - case ';': - inp.Next() - es := string(inp.Src[pos:inp.Pos]) - ues := html.UnescapeString(es) - if es == ues { - return "", false - } - return ues, true - default: - inp.Next() - } - } -} - -// isValidEntity checks if the given code is valid for an entity. -// -// According to https://html.spec.whatwg.org/multipage/syntax.html#character-references -// ""The numeric character reference forms described above are allowed to reference any code point -// excluding U+000D CR, noncharacters, and controls other than ASCII whitespace."" -func isValidEntity(r rune) bool { - // No C0 control and no "code point in the range U+007F DELETE to U+009F APPLICATION PROGRAM COMMAND, inclusive." - if r < ' ' || ('\u007f' <= r && r <= '\u009f') { - return false - } - - // If below any noncharacter code point, return true - // - // See: https://infra.spec.whatwg.org/#noncharacter - if r < '\ufdd0' { - return true - } - - // First range of noncharacter code points: "(...) in the range U+FDD0 to U+FDEF, inclusive" - if r <= '\ufdef' { - return false - } - - // Other noncharacter code points: - switch r { - case '\uFFFE', '\uFFFF', - '\U0001FFFE', '\U0001FFFF', - '\U0002FFFE', '\U0002FFFF', - '\U0003FFFE', '\U0003FFFF', - '\U0004FFFE', '\U0004FFFF', - '\U0005FFFE', '\U0005FFFF', - '\U0006FFFE', '\U0006FFFF', - '\U0007FFFE', '\U0007FFFF', - '\U0008FFFE', '\U0008FFFF', - '\U0009FFFE', '\U0009FFFF', - '\U000AFFFE', '\U000AFFFF', - '\U000BFFFE', '\U000BFFFF', - '\U000CFFFE', '\U000CFFFF', - '\U000DFFFE', '\U000DFFFF', - '\U000EFFFE', '\U000EFFFF', - '\U000FFFFE', '\U000FFFFF', - '\U0010FFFE', '\U0010FFFF': - return false - } - return true -} DELETED input/entity_test.go Index: input/entity_test.go ================================================================== --- input/entity_test.go +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -package input_test - -import ( - "testing" - - "zettelstore.de/client.fossil/input" -) - -func TestScanEntity(t *testing.T) { - t.Parallel() - var testcases = []struct { - text string - exp string - }{ - {"", ""}, - {"a", ""}, - {"&", "&"}, - {"!", "!"}, - {"3", "3"}, - {""", "\""}, - } - for id, tc := range testcases { - inp := input.NewInput([]byte(tc.text)) - got, ok := inp.ScanEntity() - if !ok { - if tc.exp != "" { - t.Errorf("ID=%d, text=%q: expected error, but got %q", id, tc.text, got) - } - if inp.Pos != 0 { - t.Errorf("ID=%d, text=%q: input position advances to %d", id, tc.text, inp.Pos) - } - continue - } - if tc.exp != got { - t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) - } - } -} - -func TestScanIllegalEntity(t *testing.T) { - t.Parallel() - testcases := []string{"", "a", "& Input →", " ", ""} - for i, tc := range testcases { - inp := input.NewInput([]byte(tc)) - got, ok := inp.ScanEntity() - if ok { - t.Errorf("%d: scanning %q was unexpected successful, got %q", i, tc, got) - continue - } - } -} DELETED input/input.go Index: input/input.go ================================================================== --- input/input.go +++ /dev/null @@ -1,156 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package input provides an abstraction for data to be read. -package input - -import "unicode/utf8" - -// Input is an abstract input source -type Input struct { - // Read-only, will never change - Src []byte // The source string - - // Read-only, will change - Ch rune // current character - Pos int // character position in src - readPos int // reading position (position after current character) -} - -// NewInput creates a new input source. -func NewInput(src []byte) *Input { - inp := &Input{Src: src} - inp.Next() - return inp -} - -// EOS = End of source -const EOS = rune(-1) - -// Next reads the next rune into inp.Ch and returns it too. -func (inp *Input) Next() rune { - if inp.readPos >= len(inp.Src) { - inp.Pos = len(inp.Src) - inp.Ch = EOS - return EOS - } - inp.Pos = inp.readPos - r, w := rune(inp.Src[inp.readPos]), 1 - if r >= utf8.RuneSelf { - r, w = utf8.DecodeRune(inp.Src[inp.readPos:]) - } - inp.readPos += w - inp.Ch = r - return r -} - -// Peek returns the rune following the most recently read rune without -// advancing. If end-of-source was already found peek returns EOS. -func (inp *Input) Peek() rune { - return inp.PeekN(0) -} - -// PeekN returns the n-th rune after the most recently read rune without -// advancing. If end-of-source was already found peek returns EOS. -func (inp *Input) PeekN(n int) rune { - pos := inp.readPos + n - if pos < len(inp.Src) { - r := rune(inp.Src[pos]) - if r >= utf8.RuneSelf { - r, _ = utf8.DecodeRune(inp.Src[pos:]) - } - if r == '\t' { - return ' ' - } - return r - } - return EOS -} - -// Accept checks if the given string is a prefix of the text to be parsed. -// If successful, advance position and current character. -// String must only contain bytes < 128. -// If not successful, everything remains as it is. -func (inp *Input) Accept(s string) bool { - pos := inp.Pos - remaining := len(inp.Src) - pos - if s == "" || len(s) > remaining { - return false - } - // According to internal documentation of bytes.Equal, the string() will not allocate any memory. - if readPos := pos + len(s); s == string(inp.Src[pos:readPos]) { - inp.readPos = readPos - inp.Next() - return true - } - return false -} - -// IsEOLEOS returns true if char is either EOS or EOL. -func IsEOLEOS(ch rune) bool { - switch ch { - case EOS, '\n', '\r': - return true - } - return false -} - -// EatEOL transforms both "\r" and "\r\n" into "\n". -func (inp *Input) EatEOL() { - switch inp.Ch { - case '\r': - if inp.Peek() == '\n' { - inp.Next() - } - inp.Ch = '\n' - inp.Next() - case '\n': - inp.Next() - } -} - -// SetPos allows to reset the read position. -func (inp *Input) SetPos(pos int) { - if inp.Pos != pos { - inp.readPos = pos - inp.Next() - } -} - -// SkipToEOL reads until the next end-of-line. -func (inp *Input) SkipToEOL() { - for { - switch inp.Ch { - case EOS, '\n', '\r': - return - } - inp.Next() - } -} - -// ScanLineContent reads the reaining input stream and interprets it as lines of text. -func (inp *Input) ScanLineContent() []byte { - result := make([]byte, 0, len(inp.Src)-inp.Pos+1) - for { - inp.EatEOL() - posL := inp.Pos - if inp.Ch == EOS { - return result - } - inp.SkipToEOL() - if len(result) > 0 { - result = append(result, '\n') - } - result = append(result, inp.Src[posL:inp.Pos]...) - } -} DELETED input/input_test.go Index: input/input_test.go ================================================================== --- input/input_test.go +++ /dev/null @@ -1,68 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package input_test provides some unit-tests for reading data. -package input_test - -import ( - "testing" - - "zettelstore.de/client.fossil/input" -) - -func TestEatEOL(t *testing.T) { - t.Parallel() - inp := input.NewInput(nil) - inp.EatEOL() - if inp.Ch != input.EOS { - t.Errorf("No EOS found: %q", inp.Ch) - } - if inp.Pos != 0 { - t.Errorf("Pos != 0: %d", inp.Pos) - } - - inp = input.NewInput([]byte("ABC")) - if inp.Ch != 'A' { - t.Errorf("First ch != 'A', got %q", inp.Ch) - } - inp.EatEOL() - if inp.Ch != 'A' { - t.Errorf("First ch != 'A', got %q", inp.Ch) - } -} - -func TestAccept(t *testing.T) { - t.Parallel() - testcases := []struct { - accept string - src string - acc bool - exp rune - }{ - {"", "", false, input.EOS}, - {"AB", "abc", false, 'a'}, - {"AB", "ABC", true, 'C'}, - {"AB", "AB", true, input.EOS}, - {"AB", "A", false, 'A'}, - } - for i, tc := range testcases { - inp := input.NewInput([]byte(tc.src)) - acc := inp.Accept(tc.accept) - if acc != tc.acc { - t.Errorf("%d: %q.Accept(%q) == %v, but got %v", i, tc.src, tc.accept, tc.acc, acc) - } - if got := inp.Ch; tc.exp != got { - t.Errorf("%d: %q.Accept(%q) should result in run %v, but got %v", i, tc.src, tc.accept, tc.exp, got) - } - } -} DELETED input/runes.go Index: input/runes.go ================================================================== --- input/runes.go +++ /dev/null @@ -1,27 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -package input - -import "unicode" - -// IsSpace returns true if rune is a whitespace. -func IsSpace(ch rune) bool { - switch ch { - case ' ', '\t': - return true - case '\n', '\r', EOS: - return false - } - return unicode.IsSpace(ch) -} Index: maps/maps.go ================================================================== --- maps/maps.go +++ maps/maps.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package maps import "sort" Index: maps/maps_test.go ================================================================== --- maps/maps_test.go +++ maps/maps_test.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package maps_test import ( Index: sexp/sexp.go ================================================================== --- sexp/sexp.go +++ sexp/sexp.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package sexp contains helper function to work with s-expression in an alien // environment. package sexp @@ -24,16 +21,17 @@ "zettelstore.de/sx.fossil" ) // EncodeZettel transforms zettel data into a sx object. func EncodeZettel(zettel api.ZettelData) sx.Object { + sf := sx.MakeMappedFactory(1024) return sx.MakeList( - sx.MakeSymbol("zettel"), - meta2sz(zettel.Meta), - sx.MakeList(sx.MakeSymbol("rights"), sx.Int64(int64(zettel.Rights))), - sx.MakeList(sx.MakeSymbol("encoding"), sx.String(zettel.Encoding)), - sx.MakeList(sx.MakeSymbol("content"), sx.String(zettel.Content)), + sf.MustMake("zettel"), + meta2sz(zettel.Meta, sf), + sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(zettel.Rights))), + sx.MakeList(sf.MustMake("encoding"), sx.String(zettel.Encoding)), + sx.MakeList(sf.MustMake("content"), sx.String(zettel.Content)), ) } func ParseZettel(obj sx.Object) (api.ZettelData, error) { vals, err := ParseList(obj, "ypppp") @@ -71,37 +69,38 @@ } return api.ZettelData{ Meta: meta, Rights: rights, - Encoding: string(encVals[1].(sx.String)), - Content: string(contentVals[1].(sx.String)), + Encoding: encVals[1].(sx.String).String(), + Content: contentVals[1].(sx.String).String(), }, nil } // EncodeMetaRights translates metadata/rights into a sx object. func EncodeMetaRights(mr api.MetaRights) *sx.Pair { + sf := sx.MakeMappedFactory(1024) return sx.MakeList( - sx.SymbolList, - meta2sz(mr.Meta), - sx.MakeList(sx.MakeSymbol("rights"), sx.Int64(int64(mr.Rights))), + sf.MustMake("list"), + meta2sz(mr.Meta, sf), + sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(mr.Rights))), ) } -func meta2sz(m api.ZettelMeta) sx.Object { - var result sx.ListBuilder - result.Add(sx.MakeSymbol("meta")) +func meta2sz(m api.ZettelMeta, sf sx.SymbolFactory) sx.Object { + result := sx.Nil().Cons(sf.MustMake("meta")) + curr := result keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { - val := sx.MakeList(sx.MakeSymbol(k), sx.String(m[k])) - result.Add(val) + val := sx.MakeList(sf.MustMake(k), sx.String(m[k])) + curr = curr.AppendBang(val) } - return result.List() + return result } // ParseMeta translates the given list to metadata. func ParseMeta(pair *sx.Pair) (api.ZettelMeta, error) { if err := CheckSymbol(pair.Car(), "meta"); err != nil { @@ -111,11 +110,11 @@ for node := pair.Tail(); node != nil; node = node.Tail() { mVals, err := ParseList(node.Car(), "ys") if err != nil { return nil, err } - res[(mVals[0].(*sx.Symbol)).GetValue()] = string(mVals[1].(sx.String)) + res[mVals[0].(*sx.Symbol).Name()] = mVals[1].(sx.String).String() } return res, nil } // ParseRights returns the rights values of the given object. @@ -133,11 +132,11 @@ } return api.ZettelRights(i64), nil } // ParseList parses the given object as a proper list, based on a type specification. -func ParseList(obj sx.Object, spec string) (sx.Vector, error) { +func ParseList(obj sx.Object, spec string) ([]sx.Object, error) { pair, isPair := sx.GetPair(obj) if !isPair { return nil, fmt.Errorf("not a list: %T/%v", obj, obj) } if pair == nil { @@ -145,11 +144,11 @@ return nil, nil } return nil, ErrElementsMissing } - result := make(sx.Vector, 0, len(spec)) + result := make([]sx.Object, 0, len(spec)) node, i := pair, 0 for ; node != nil; i++ { if i >= len(spec) { return nil, ErrNoSpec } @@ -195,10 +194,10 @@ func CheckSymbol(obj sx.Object, name string) error { sym, isSymbol := sx.GetSymbol(obj) if !isSymbol { return fmt.Errorf("object %v/%T is not a symbol", obj, obj) } - if got := sym.GetValue(); got != name { + if got := sym.Name(); got != name { return fmt.Errorf("symbol %q expected, but got: %q", name, got) } return nil } Index: sexp/sexp_test.go ================================================================== --- sexp/sexp_test.go +++ sexp/sexp_test.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package sexp_test import ( DELETED shtml/const.go Index: shtml/const.go ================================================================== --- shtml/const.go +++ /dev/null @@ -1,83 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2024-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2024-present Detlef Stern -//----------------------------------------------------------------------------- - -package shtml - -import "zettelstore.de/sx.fossil" - -// 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") -) - -// 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") - symTR = sx.MakeSymbol("tr") - SymUL = sx.MakeSymbol("ul") -) - -// Symbols for HTML attribute keys -var ( - symAttrAlt = sx.MakeSymbol("alt") - 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") -) Index: shtml/shtml.go ================================================================== --- shtml/shtml.go +++ shtml/shtml.go @@ -4,13 +4,10 @@ // This file is part of zettelstore-client. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package shtml transforms a s-expr encoded zettel AST into a s-expr representation of HTML. package shtml @@ -29,37 +26,70 @@ ) // Evaluator will transform a s-expression that encodes the zettel AST into an s-expression // that represents HTML. type Evaluator struct { + sf sx.SymbolFactory headingOffset int64 unique string noLinks bool // true iff output must not include links + + symAttr *sx.Symbol + symList *sx.Symbol + symNoEscape *sx.Symbol + symClass *sx.Symbol + symMeta *sx.Symbol + symP *sx.Symbol + symLI *sx.Symbol + symA *sx.Symbol + symHREF *sx.Symbol + symSpan *sx.Symbol fns map[string]EvalFn minArgs map[string]int } // NewEvaluator creates a new Evaluator object. -func NewEvaluator(headingOffset int) *Evaluator { +func NewEvaluator(headingOffset int, sf sx.SymbolFactory) *Evaluator { + if sf == nil { + sf = sx.MakeMappedFactory(128) + } ev := &Evaluator{ + sf: sf, headingOffset: int64(headingOffset), + symAttr: sf.MustMake(sxhtml.NameSymAttr), + symList: sf.MustMake(sxhtml.NameSymList), + symNoEscape: sf.MustMake(sxhtml.NameSymNoEscape), + symClass: sf.MustMake("class"), + symMeta: sf.MustMake("meta"), + symP: sf.MustMake("p"), + symLI: sf.MustMake("li"), + symA: sf.MustMake("a"), + symHREF: sf.MustMake("href"), + symSpan: sf.MustMake("span"), fns: make(map[string]EvalFn, 128), minArgs: make(map[string]int, 128), } + ev.bindCommon() ev.bindMetadata() ev.bindBlocks() ev.bindInlines() return ev } // SetUnique sets a prefix to make several HTML ids unique. func (tr *Evaluator) SetUnique(s string) { tr.unique = s } +// SymbolFactory returns the symbol factory of this evaluator. +func (ev *Evaluator) SymbolFactory() sx.SymbolFactory { return ev.sf } + // IsValidName returns true, if name is a valid symbol name. -func (tr *Evaluator) IsValidName(s string) bool { return s != "" } +func (tr *Evaluator) IsValidName(s string) bool { return tr.sf.IsValidName(s) } + +// Make a new HTML symbol. +func (tr *Evaluator) Make(s string) *sx.Symbol { return tr.sf.MustMake(s) } // EvaluateAttrbute transforms the given attributes into a HTML s-expression. func (tr *Evaluator) EvaluateAttrbute(a attrs.Attributes) *sx.Pair { if len(a) == 0 { return nil @@ -67,91 +97,84 @@ plist := sx.Nil() keys := a.Keys() for i := len(keys) - 1; i >= 0; i-- { key := keys[i] if key != attrs.DefaultAttribute && tr.IsValidName(key) { - plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.String(a[key]))) + plist = plist.Cons(sx.Cons(tr.Make(key), sx.String(a[key]))) } } if plist == nil { return nil } - return plist.Cons(sxhtml.SymAttr) + return plist.Cons(tr.symAttr) } // 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) + result := ev.eval(lst, env) if err := env.err; err != nil { return nil, err } pair, isPair := sx.GetPair(result) if !isPair { return nil, fmt.Errorf("evaluation does not result in a pair, but %T/%v", result, result) } for i := 0; i < len(env.endnotes); i++ { - // May extend tr.endnotes -> do not use for i := range len(...)!!! + // May extend tr.endnotes if env.endnotes[i].noteHx != nil { continue } - noteHx, _ := ev.EvaluateList(env.endnotes[i].noteAST, env) + objHx := ev.eval(env.endnotes[i].noteAST, env) + if env.err != nil { + break + } + noteHx, isHx := sx.GetPair(objHx) + if !isHx { + return nil, fmt.Errorf("endnote evaluation does not result in pair, but %T/%v", objHx, objHx) + } env.endnotes[i].noteHx = noteHx } return pair, nil } -// EvaluateList will evaluate all list elements separately and returns them as a sx.Pair list -func (ev *Evaluator) EvaluateList(lst sx.Vector, env *Environment) (*sx.Pair, error) { - var result sx.ListBuilder - for _, elem := range lst { - p := ev.Eval(elem, env) - result.Add(p) - } - if err := env.err; err != nil { - return nil, err - } - return result.List(), nil -} - // Endnotes returns a SHTML object with all collected endnotes. func (ev *Evaluator) Endnotes(env *Environment) *sx.Pair { if env.err != nil || len(env.endnotes) == 0 { return nil } - var result sx.ListBuilder - result.Add(SymOL) - result.Add(sx.Nil().Cons(sx.Cons(SymAttrClass, sx.String("zs-endnotes"))).Cons(sxhtml.SymAttr)) + result := sx.Nil().Cons(ev.Make("ol")) + symValue, symId, symRole := ev.Make("value"), ev.Make("id"), ev.Make("role") + + currResult := result.AppendBang(sx.Nil().Cons(sx.Cons(ev.symClass, sx.String("zs-endnotes"))).Cons(ev.symAttr)) for i, fni := range env.endnotes { noteNum := strconv.Itoa(i + 1) - attrs := fni.attrs.Cons(sx.Cons(SymAttrClass, sx.String("zs-endnote"))). - Cons(sx.Cons(SymAttrValue, sx.String(noteNum))). - Cons(sx.Cons(SymAttrId, sx.String("fn:"+fni.noteID))). - Cons(sx.Cons(SymAttrRole, sx.String("doc-endnote"))). - Cons(sxhtml.SymAttr) + attrs := fni.attrs.Cons(sx.Cons(ev.symClass, sx.String("zs-endnote"))). + Cons(sx.Cons(symValue, sx.String(noteNum))). + Cons(sx.Cons(symId, sx.String("fn:"+fni.noteID))). + Cons(sx.Cons(symRole, sx.String("doc-endnote"))). + Cons(ev.symAttr) backref := sx.Nil().Cons(sx.String("\u21a9\ufe0e")). Cons(sx.Nil(). - Cons(sx.Cons(SymAttrClass, sx.String("zs-endnote-backref"))). - Cons(sx.Cons(SymAttrHref, sx.String("#fnref:"+fni.noteID))). - Cons(sx.Cons(SymAttrRole, sx.String("doc-backlink"))). - Cons(sxhtml.SymAttr)). - Cons(SymA) - - var li sx.ListBuilder - li.Add(SymLI) - li.Add(attrs) - li.ExtendBang(fni.noteHx) - li.Add(sx.String(" ")) - li.Add(backref) - result.Add(li.List()) - } - return result.List() + Cons(sx.Cons(ev.symClass, sx.String("zs-endnote-backref"))). + Cons(sx.Cons(ev.symHREF, sx.String("#fnref:"+fni.noteID))). + Cons(sx.Cons(symRole, sx.String("doc-backlink"))). + Cons(ev.symAttr)). + Cons(ev.symA) + + li := sx.Nil().Cons(ev.symLI) + li.AppendBang(attrs). + ExtendBang(fni.noteHx). + AppendBang(sx.String(" ")).AppendBang(backref) + currResult = currResult.AppendBang(li) + } + return result } // Environment where sz objects are evaluated to shtml objects type Environment struct { err error @@ -159,11 +182,11 @@ endnotes []endnoteInfo quoteNesting uint } type endnoteInfo struct { noteID string // link id - noteAST sx.Vector // Endnote as list of AST inline elements + noteAST sx.Object // Endnote as AST attrs *sx.Pair // attrs a-list noteHx *sx.Pair // Endnote as SxHTML } // MakeEnvironment builds a new evaluation environment. @@ -206,243 +229,250 @@ func (env *Environment) getLanguage() string { return env.langStack[len(env.langStack)-1] } // EvalFn is a function to be called for evaluation. -type EvalFn func(sx.Vector, *Environment) sx.Object +type EvalFn func([]sx.Object, *Environment) sx.Object -func (ev *Evaluator) bind(sym *sx.Symbol, minArgs int, fn EvalFn) { - symVal := sym.GetValue() - ev.fns[symVal] = fn +func (ev *Evaluator) bind(name string, minArgs int, fn EvalFn) { + ev.fns[name] = fn if minArgs > 0 { - ev.minArgs[symVal] = minArgs + ev.minArgs[name] = minArgs } } // ResolveBinding returns the function bound to the given name. -func (ev *Evaluator) ResolveBinding(sym *sx.Symbol) EvalFn { - if fn, found := ev.fns[sym.GetValue()]; found { +func (ev *Evaluator) ResolveBinding(name string) EvalFn { + if fn, found := ev.fns[name]; found { return fn } return nil } // Rebind overwrites a binding, but leaves the minimum number of arguments intact. -func (ev *Evaluator) Rebind(sym *sx.Symbol, fn EvalFn) { - symVal := sym.GetValue() - if _, found := ev.fns[symVal]; !found { - panic(sym) +func (ev *Evaluator) Rebind(name string, fn EvalFn) { + if _, found := ev.fns[name]; !found { + panic(name) } - ev.fns[symVal] = fn + ev.fns[name] = fn +} + +func (ev *Evaluator) bindCommon() { + ev.bind(sx.ListName, 0, ev.evalList) + ev.bind("quote", 1, func(args []sx.Object, _ *Environment) sx.Object { return args[0] }) } func (ev *Evaluator) bindMetadata() { - ev.bind(sz.SymMeta, 0, ev.evalList) - evalMetaString := func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymMeta, 0, ev.evalList) + evalMetaString := func(args []sx.Object, env *Environment) sx.Object { a := make(attrs.Attributes, 2). - Set("name", ev.getSymbol(args[0], env).GetValue()). - Set("content", string(getString(args[1], env))) + Set("name", ev.getSymbol(ev.eval(args[0], env), env).Name()). + Set("content", getString(args[1], env).String()) return ev.EvaluateMeta(a) } - ev.bind(sz.SymTypeCredential, 2, evalMetaString) - ev.bind(sz.SymTypeEmpty, 2, evalMetaString) - ev.bind(sz.SymTypeID, 2, evalMetaString) - ev.bind(sz.SymTypeNumber, 2, evalMetaString) - ev.bind(sz.SymTypeString, 2, evalMetaString) - ev.bind(sz.SymTypeTimestamp, 2, evalMetaString) - ev.bind(sz.SymTypeURL, 2, evalMetaString) - ev.bind(sz.SymTypeWord, 2, evalMetaString) - - evalMetaSet := func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymTypeCredential, 2, evalMetaString) + ev.bind(sz.NameSymTypeEmpty, 2, evalMetaString) + ev.bind(sz.NameSymTypeID, 2, evalMetaString) + ev.bind(sz.NameSymTypeNumber, 2, evalMetaString) + ev.bind(sz.NameSymTypeString, 2, evalMetaString) + ev.bind(sz.NameSymTypeTimestamp, 2, evalMetaString) + ev.bind(sz.NameSymTypeURL, 2, evalMetaString) + ev.bind(sz.NameSymTypeWord, 2, evalMetaString) + + evalMetaSet := func(args []sx.Object, env *Environment) sx.Object { var sb strings.Builder - for elem := getList(args[1], env); elem != nil; elem = elem.Tail() { + lst := ev.eval(args[1], env) + for elem := getList(lst, env); elem != nil; elem = elem.Tail() { sb.WriteByte(' ') - sb.WriteString(string(getString(elem.Car(), env))) + sb.WriteString(getString(elem.Car(), env).String()) } s := sb.String() if len(s) > 0 { s = s[1:] } a := make(attrs.Attributes, 2). - Set("name", ev.getSymbol(args[0], env).GetValue()). + Set("name", ev.getSymbol(ev.eval(args[0], env), env).Name()). Set("content", s) return ev.EvaluateMeta(a) } - ev.bind(sz.SymTypeIDSet, 2, evalMetaSet) - ev.bind(sz.SymTypeTagSet, 2, evalMetaSet) - ev.bind(sz.SymTypeZettelmarkup, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymTypeIDSet, 2, evalMetaSet) + ev.bind(sz.NameSymTypeTagSet, 2, evalMetaSet) + ev.bind(sz.NameSymTypeWordSet, 2, evalMetaSet) + ev.bind(sz.NameSymTypeZettelmarkup, 2, func(args []sx.Object, env *Environment) sx.Object { a := make(attrs.Attributes, 2). - Set("name", ev.getSymbol(args[0], env).GetValue()). + Set("name", ev.getSymbol(ev.eval(args[0], env), env).String()). Set("content", text.EvaluateInlineString(getList(args[1], env))) return ev.EvaluateMeta(a) }) } // EvaluateMeta returns HTML meta object for an attribute. func (ev *Evaluator) EvaluateMeta(a attrs.Attributes) *sx.Pair { - return sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(SymMeta) + return sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(ev.symMeta) } func (ev *Evaluator) bindBlocks() { - ev.bind(sz.SymBlock, 0, ev.evalList) - ev.bind(sz.SymPara, 0, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalSlice(args, env).Cons(SymP) + ev.bind(sz.NameSymBlock, 0, ev.evalList) + ev.bind(sz.NameSymPara, 0, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalSlice(args, env).Cons(ev.symP) }) - ev.bind(sz.SymHeading, 5, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymHeading, 5, func(args []sx.Object, env *Environment) sx.Object { nLevel := getInt64(args[0], env) if nLevel <= 0 { env.err = fmt.Errorf("%v is a negative level", nLevel) return sx.Nil() } level := strconv.FormatInt(nLevel+ev.headingOffset, 10) - headingSymbol := sx.MakeSymbol("h" + level) - a := ev.GetAttributes(args[1], env) + a := ev.getAttributes(args[1], env) env.pushAttributes(a) defer env.popAttributes() - if fragment := string(getString(args[3], env)); fragment != "" { + if fragment := getString(args[3], env).String(); fragment != "" { a = a.Set("id", ev.unique+fragment) } - if result, _ := ev.EvaluateList(args[4:], env); result != nil { + if result, isPair := sx.GetPair(ev.eval(args[4], env)); isPair && result != nil { if len(a) > 0 { result = result.Cons(ev.EvaluateAttrbute(a)) } - return result.Cons(headingSymbol) + return result.Cons(ev.Make("h" + level)) } - return sx.MakeList(headingSymbol, sx.String("")) + return sx.MakeList(ev.Make("h"+level), sx.String("")) }) - ev.bind(sz.SymThematic, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymThematic, 0, func(args []sx.Object, env *Environment) sx.Object { result := sx.Nil() if len(args) > 0 { - if attrList := getList(args[0], env); attrList != nil { + if attrList := getList(ev.eval(args[0], env), env); attrList != nil { result = result.Cons(ev.EvaluateAttrbute(sz.GetAttributes(attrList))) } } - return result.Cons(SymHR) + return result.Cons(ev.Make("hr")) }) - ev.bind(sz.SymListOrdered, 0, ev.makeListFn(SymOL)) - ev.bind(sz.SymListUnordered, 0, ev.makeListFn(SymUL)) - ev.bind(sz.SymDescription, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymListOrdered, 0, ev.makeListFn("ol")) + ev.bind(sz.NameSymListUnordered, 0, ev.makeListFn("ul")) + ev.bind(sz.NameSymDescription, 0, func(args []sx.Object, env *Environment) sx.Object { if len(args) == 0 { return sx.Nil() } - var items sx.ListBuilder - items.Add(symDL) + items := sx.Nil().Cons(ev.Make("dl")) + curItem := items for pos := 0; pos < len(args); pos++ { - term := ev.evalDescriptionTerm(getList(args[pos], env), env) - items.Add(term.Cons(symDT)) + term := getList(ev.eval(args[pos], env), env) + curItem = curItem.AppendBang(term.Cons(ev.Make("dt"))) pos++ if pos >= len(args) { break } - ddBlock := getList(ev.Eval(args[pos], env), env) + ddBlock := getList(ev.eval(args[pos], env), env) if ddBlock == nil { continue } for ddlst := ddBlock; ddlst != nil; ddlst = ddlst.Tail() { dditem := getList(ddlst.Car(), env) - items.Add(dditem.Cons(symDD)) + curItem = curItem.AppendBang(dditem.Cons(ev.Make("dd"))) } } - return items.List() + return items }) - ev.bind(sz.SymListQuote, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymListQuote, 0, func(args []sx.Object, env *Environment) sx.Object { if args == nil { return sx.Nil() } - var result sx.ListBuilder - result.Add(symBLOCKQUOTE) + result := sx.Nil().Cons(ev.Make("blockquote")) + currResult := result for _, elem := range args { - if quote, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { - result.Add(quote.Cons(sxhtml.SymListSplice)) + if quote, isPair := sx.GetPair(ev.eval(elem, env)); isPair { + currResult = currResult.AppendBang(quote.Cons(ev.symP)) } } - return result.List() + return result }) - ev.bind(sz.SymTable, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymTable, 1, func(args []sx.Object, env *Environment) sx.Object { thead := sx.Nil() - if header := getList(args[0], env); !sx.IsNil(header) { - thead = sx.Nil().Cons(ev.evalTableRow(header, env)).Cons(symTHEAD) + if header := getList(ev.eval(args[0], env), env); !sx.IsNil(header) { + thead = sx.Nil().Cons(ev.evalTableRow(header)).Cons(ev.Make("thead")) } - var tbody sx.ListBuilder + tbody := sx.Nil() if len(args) > 1 { - tbody.Add(symTBODY) + tbody = sx.Nil().Cons(ev.Make("tbody")) + curBody := tbody for _, row := range args[1:] { - tbody.Add(ev.evalTableRow(getList(row, env), env)) + curBody = curBody.AppendBang(ev.evalTableRow(getList(ev.eval(row, env), env))) } } table := sx.Nil() - if !tbody.IsEmpty() { - table = table.Cons(tbody.List()) + if tbody != nil { + table = table.Cons(tbody) } if thead != nil { table = table.Cons(thead) } if table == nil { return sx.Nil() } - return table.Cons(symTABLE) + return table.Cons(ev.Make("table")) }) - ev.bind(sz.SymCell, 0, ev.makeCellFn("")) - ev.bind(sz.SymCellCenter, 0, ev.makeCellFn("center")) - ev.bind(sz.SymCellLeft, 0, ev.makeCellFn("left")) - ev.bind(sz.SymCellRight, 0, ev.makeCellFn("right")) - - ev.bind(sz.SymRegionBlock, 2, ev.makeRegionFn(SymDIV, true)) - ev.bind(sz.SymRegionQuote, 2, ev.makeRegionFn(symBLOCKQUOTE, false)) - ev.bind(sz.SymRegionVerse, 2, ev.makeRegionFn(SymDIV, false)) - - ev.bind(sz.SymVerbatimComment, 1, func(args sx.Vector, env *Environment) sx.Object { - if ev.GetAttributes(args[0], env).HasDefault() { + ev.bind(sz.NameSymCell, 0, ev.makeCellFn("")) + ev.bind(sz.NameSymCellCenter, 0, ev.makeCellFn("center")) + ev.bind(sz.NameSymCellLeft, 0, ev.makeCellFn("left")) + ev.bind(sz.NameSymCellRight, 0, ev.makeCellFn("right")) + + symDiv := ev.Make("div") + ev.bind(sz.NameSymRegionBlock, 2, ev.makeRegionFn(symDiv, true)) + ev.bind(sz.NameSymRegionQuote, 2, ev.makeRegionFn(ev.Make("blockquote"), false)) + ev.bind(sz.NameSymRegionVerse, 2, ev.makeRegionFn(symDiv, false)) + + ev.bind(sz.NameSymVerbatimComment, 1, func(args []sx.Object, env *Environment) sx.Object { + if ev.getAttributes(args[0], env).HasDefault() { if len(args) > 1 { if s := getString(args[1], env); s != "" { - return sx.Nil().Cons(s).Cons(sxhtml.SymBlockComment) + t := sx.String(s.String()) + return sx.Nil().Cons(t).Cons(ev.Make(sxhtml.NameSymBlockComment)) } } } return nil }) - ev.bind(sz.SymVerbatimEval, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalVerbatim(ev.GetAttributes(args[0], env).AddClass("zs-eval"), getString(args[1], env)) - }) - ev.bind(sz.SymVerbatimHTML, 2, ev.evalHTML) - ev.bind(sz.SymVerbatimMath, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalVerbatim(ev.GetAttributes(args[0], env).AddClass("zs-math"), getString(args[1], env)) - }) - ev.bind(sz.SymVerbatimProg, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymVerbatimEval, 2, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalVerbatim(ev.getAttributes(args[0], env).AddClass("zs-eval"), getString(args[1], env)) + }) + ev.bind(sz.NameSymVerbatimHTML, 2, ev.evalHTML) + ev.bind(sz.NameSymVerbatimMath, 2, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalVerbatim(ev.getAttributes(args[0], env).AddClass("zs-math"), getString(args[1], env)) + }) + ev.bind(sz.NameSymVerbatimProg, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) content := getString(args[1], env) if a.HasDefault() { - content = sx.String(visibleReplacer.Replace(string(content))) + content = sx.String(visibleReplacer.Replace(content.String())) } return ev.evalVerbatim(a, content) }) - ev.bind(sz.SymVerbatimZettel, 0, nilFn) - ev.bind(sz.SymBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymVerbatimZettel, 0, nilFn) + ev.bind(sz.NameSymBLOB, 3, func(args []sx.Object, env *Environment) sx.Object { return ev.evalBLOB(getList(args[0], env), getString(args[1], env), getString(args[2], env)) }) - ev.bind(sz.SymTransclude, 2, func(args sx.Vector, env *Environment) sx.Object { - ref, isPair := sx.GetPair(args[1]) + ev.bind(sz.NameSymTransclude, 2, func(args []sx.Object, env *Environment) sx.Object { + ref, isPair := sx.GetPair(ev.eval(args[1], env)) if !isPair { return sx.Nil() } refKind := ref.Car() if sx.IsNil(refKind) { return sx.Nil() } if refValue := getString(ref.Tail().Car(), env); refValue != "" { - if refSym, isRefSym := sx.GetSymbol(refKind); isRefSym && refSym.IsEqual(sz.SymRefStateExternal) { - a := ev.GetAttributes(args[0], env).Set("src", string(refValue)).AddClass("external") - return sx.Nil().Cons(sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(SymIMG)).Cons(SymP) + if refSym, isRefSym := sx.GetSymbol(refKind); isRefSym && refSym.Name() == sz.NameSymRefStateExternal { + a := ev.getAttributes(args[0], env).Set("src", refValue.String()).AddClass("external") + return sx.Nil().Cons(sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(ev.Make("img"))).Cons(ev.symP) } return sx.MakeList( - sxhtml.SymInlineComment, + ev.Make(sxhtml.NameSymInlineComment), sx.String("transclude"), refKind, sx.String("->"), refValue, ) @@ -449,197 +479,189 @@ } return ev.evalSlice(args, env) }) } -func (ev *Evaluator) makeListFn(sym *sx.Symbol) EvalFn { - return func(args sx.Vector, env *Environment) sx.Object { - var result sx.ListBuilder - result.Add(sym) +func (ev *Evaluator) makeListFn(tag string) EvalFn { + sym := ev.Make(tag) + return func(args []sx.Object, env *Environment) sx.Object { + result := sx.Nil().Cons(sym) + last := result for _, elem := range args { - item := sx.Nil().Cons(SymLI) - if res, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { + item := sx.Nil().Cons(ev.symLI) + if res, isPair := sx.GetPair(ev.eval(elem, env)); isPair { item.ExtendBang(res) } - result.Add(item) + last = last.AppendBang(item) } - return result.List() + return result } } -func (ev *Evaluator) evalDescriptionTerm(term *sx.Pair, env *Environment) *sx.Pair { - var result sx.ListBuilder - for node := term; node != nil; node = node.Tail() { - elem := ev.Eval(node.Car(), env) - result.Add(elem) - } - return result.List() -} - -func (ev *Evaluator) evalTableRow(pairs *sx.Pair, env *Environment) *sx.Pair { +func (ev *Evaluator) evalTableRow(pairs *sx.Pair) *sx.Pair { + row := sx.Nil().Cons(ev.Make("tr")) if pairs == nil { return nil } - var row sx.ListBuilder - row.Add(symTR) + curRow := row for pair := pairs; pair != nil; pair = pair.Tail() { - row.Add(ev.Eval(pair.Car(), env)) + curRow = curRow.AppendBang(pair.Car()) } - return row.List() + return row } func (ev *Evaluator) makeCellFn(align string) EvalFn { - return func(args sx.Vector, env *Environment) sx.Object { + return func(args []sx.Object, env *Environment) sx.Object { tdata := ev.evalSlice(args, env) if align != "" { tdata = tdata.Cons(ev.EvaluateAttrbute(attrs.Attributes{"class": align})) } - return tdata.Cons(symTD) + return tdata.Cons(ev.Make("td")) } } func (ev *Evaluator) makeRegionFn(sym *sx.Symbol, genericToClass bool) EvalFn { - return func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + return func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if genericToClass { if val, found := a.Get(""); found { a = a.Remove("").AddClass(val) } } - var result sx.ListBuilder - result.Add(sym) + result := sx.Nil() if len(a) > 0 { - result.Add(ev.EvaluateAttrbute(a)) + result = result.Cons(ev.EvaluateAttrbute(a)) } - if region, isPair := sx.GetPair(args[1]); isPair { - if evalRegion := ev.EvalPairList(region, env); evalRegion != nil { - result.ExtendBang(evalRegion) - } + result = result.Cons(sym) + currResult := result.LastPair() + if region, isPair := sx.GetPair(ev.eval(args[1], env)); isPair { + currResult = currResult.ExtendBang(region) } if len(args) > 2 { - if cite, _ := ev.EvaluateList(args[2:], env); cite != nil { - result.Add(cite.Cons(symCITE)) + if cite, isPair := sx.GetPair(ev.eval(args[2], env)); isPair && cite != nil { + currResult.AppendBang(cite.Cons(ev.Make("cite"))) } } - return result.List() + return result } } func (ev *Evaluator) evalVerbatim(a attrs.Attributes, s sx.String) sx.Object { a = setProgLang(a) code := sx.Nil().Cons(s) if al := ev.EvaluateAttrbute(a); al != nil { code = code.Cons(al) } - code = code.Cons(symCODE) - return sx.Nil().Cons(code).Cons(symPRE) + code = code.Cons(ev.Make("code")) + return sx.Nil().Cons(code).Cons(ev.Make("pre")) } func (ev *Evaluator) bindInlines() { - ev.bind(sz.SymInline, 0, ev.evalList) - ev.bind(sz.SymText, 1, func(args sx.Vector, env *Environment) sx.Object { return getString(args[0], env) }) - ev.bind(sz.SymSpace, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymInline, 0, ev.evalList) + ev.bind(sz.NameSymText, 1, func(args []sx.Object, env *Environment) sx.Object { return getString(args[0], env) }) + ev.bind(sz.NameSymSpace, 0, func(args []sx.Object, env *Environment) sx.Object { if len(args) == 0 { return sx.String(" ") } return getString(args[0], env) }) - ev.bind(sz.SymSoft, 0, func(sx.Vector, *Environment) sx.Object { return sx.String(" ") }) - ev.bind(sz.SymHard, 0, func(sx.Vector, *Environment) sx.Object { return sx.Nil().Cons(symBR) }) + ev.bind(sz.NameSymSoft, 0, func([]sx.Object, *Environment) sx.Object { return sx.String(" ") }) + symBR := ev.Make("br") + ev.bind(sz.NameSymHard, 0, func([]sx.Object, *Environment) sx.Object { return sx.Nil().Cons(symBR) }) - ev.bind(sz.SymLinkInvalid, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymLinkInvalid, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() var inline *sx.Pair if len(args) > 2 { inline = ev.evalSlice(args[2:], env) } if inline == nil { - inline = sx.Nil().Cons(ev.Eval(args[1], env)) + inline = sx.Nil().Cons(ev.eval(args[1], env)) } - return inline.Cons(SymSPAN) + return inline.Cons(ev.symSpan) }) - evalHREF := func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + evalHREF := func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) - return ev.evalLink(a.Set("href", string(refValue)), refValue, args[2:], env) + return ev.evalLink(a.Set("href", refValue.String()), refValue, args[2:], env) } - ev.bind(sz.SymLinkZettel, 2, evalHREF) - ev.bind(sz.SymLinkSelf, 2, evalHREF) - ev.bind(sz.SymLinkFound, 2, evalHREF) - ev.bind(sz.SymLinkBroken, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymLinkZettel, 2, evalHREF) + ev.bind(sz.NameSymLinkSelf, 2, evalHREF) + ev.bind(sz.NameSymLinkFound, 2, evalHREF) + ev.bind(sz.NameSymLinkBroken, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) return ev.evalLink(a.AddClass("broken"), refValue, args[2:], env) }) - ev.bind(sz.SymLinkHosted, 2, evalHREF) - ev.bind(sz.SymLinkBased, 2, evalHREF) - ev.bind(sz.SymLinkQuery, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - query := "?" + api.QueryKeyQuery + "=" + url.QueryEscape(string(refValue)) - return ev.evalLink(a.Set("href", query), refValue, args[2:], env) - }) - ev.bind(sz.SymLinkExternal, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - return ev.evalLink(a.Set("href", string(refValue)).AddClass("external"), refValue, args[2:], env) - }) - - ev.bind(sz.SymEmbed, 3, func(args sx.Vector, env *Environment) sx.Object { - ref := getList(args[1], env) - syntax := getString(args[2], env) - if syntax == api.ValueSyntaxSVG { - embedAttr := sx.MakeList( - sxhtml.SymAttr, - sx.Cons(SymAttrType, sx.String("image/svg+xml")), - sx.Cons(SymAttrSrc, sx.String("/"+string(getString(ref.Tail(), env))+".svg")), - ) - return sx.MakeList( - SymFIGURE, - sx.MakeList( - SymEMBED, + ev.bind(sz.NameSymLinkHosted, 2, evalHREF) + ev.bind(sz.NameSymLinkBased, 2, evalHREF) + ev.bind(sz.NameSymLinkQuery, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) + env.pushAttributes(a) + defer env.popAttributes() + refValue := getString(args[1], env) + query := "?" + api.QueryKeyQuery + "=" + url.QueryEscape(refValue.String()) + return ev.evalLink(a.Set("href", query), refValue, args[2:], env) + }) + ev.bind(sz.NameSymLinkExternal, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) + env.pushAttributes(a) + defer env.popAttributes() + refValue := getString(args[1], env) + return ev.evalLink(a.Set("href", refValue.String()).AddClass("external"), refValue, args[2:], env) + }) + + ev.bind(sz.NameSymEmbed, 3, func(args []sx.Object, env *Environment) sx.Object { + ref := getList(ev.eval(args[1], env), env) + syntax := getString(args[2], env) + if syntax == api.ValueSyntaxSVG { + embedAttr := sx.MakeList( + ev.symAttr, + sx.Cons(ev.Make("type"), sx.String("image/svg+xml")), + sx.Cons(ev.Make("src"), sx.String("/"+getString(ref.Tail(), env).String()+".svg")), + ) + return sx.MakeList( + ev.Make("figure"), + sx.MakeList( + ev.Make("embed"), embedAttr, ), ) } - a := ev.GetAttributes(args[0], env) - a = a.Set("src", string(getString(ref.Tail().Car(), env))) + a := ev.getAttributes(args[0], env) + a = a.Set("src", getString(ref.Tail().Car(), env).String()) if len(args) > 3 { var sb strings.Builder flattenText(&sb, sx.MakeList(args[3:]...)) if d := sb.String(); d != "" { a = a.Set("alt", d) } } - return sx.MakeList(SymIMG, ev.EvaluateAttrbute(a)) + return sx.MakeList(ev.Make("img"), ev.EvaluateAttrbute(a)) }) - ev.bind(sz.SymEmbedBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { - a, syntax, data := ev.GetAttributes(args[0], env), getString(args[1], env), getString(args[2], env) + ev.bind(sz.NameSymEmbedBLOB, 3, func(args []sx.Object, env *Environment) sx.Object { + a, syntax, data := ev.getAttributes(args[0], env), getString(args[1], env), getString(args[2], env) summary, hasSummary := a.Get(api.KeySummary) if !hasSummary { summary = "" } return ev.evalBLOB( - sx.MakeList(sxhtml.SymListSplice, sx.String(summary)), + sx.MakeList(ev.Make(sx.ListName), sx.String(summary)), syntax, data, ) }) - ev.bind(sz.SymCite, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymCite, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() result := sx.Nil() if key := getString(args[1], env); key != "" { if len(args) > 2 { @@ -651,87 +673,95 @@ result = result.Cons(ev.EvaluateAttrbute(a)) } if result == nil { return nil } - return result.Cons(SymSPAN) + return result.Cons(ev.symSpan) }) - ev.bind(sz.SymMark, 3, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(sz.NameSymMark, 3, func(args []sx.Object, env *Environment) sx.Object { result := ev.evalSlice(args[3:], env) if !ev.noLinks { if fragment := getString(args[2], env); fragment != "" { - a := attrs.Attributes{"id": string(fragment) + ev.unique} - return result.Cons(ev.EvaluateAttrbute(a)).Cons(SymA) + a := attrs.Attributes{"id": fragment.String() + ev.unique} + return result.Cons(ev.EvaluateAttrbute(a)).Cons(ev.symA) } } - return result.Cons(SymSPAN) + return result.Cons(ev.symSpan) }) - ev.bind(sz.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymEndnote, 1, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() attrPlist := sx.Nil() if len(a) > 0 { if attrs := ev.EvaluateAttrbute(a); attrs != nil { attrPlist = attrs.Tail() } } + text, isPair := sx.GetPair(args[1]) + if !isPair { + return sx.Nil() + } noteNum := strconv.Itoa(len(env.endnotes) + 1) 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.String("doc-noteref"))). - Cons(sx.Cons(SymAttrHref, sx.String("#fn:"+noteID))). - Cons(sx.Cons(SymAttrClass, sx.String("zs-noteref"))). - Cons(sxhtml.SymAttr) - href := sx.Nil().Cons(sx.String(noteNum)).Cons(hrefAttr).Cons(SymA) - supAttr := sx.Nil().Cons(sx.Cons(SymAttrId, sx.String("fnref:"+noteID))).Cons(sxhtml.SymAttr) - return sx.Nil().Cons(href).Cons(supAttr).Cons(symSUP) + noteID: noteID, noteAST: ev.eval(text, env), noteHx: nil, attrs: attrPlist}) + hrefAttr := sx.Nil().Cons(sx.Cons(ev.Make("role"), sx.String("doc-noteref"))). + Cons(sx.Cons(ev.symHREF, sx.String("#fn:"+noteID))). + Cons(sx.Cons(ev.symClass, sx.String("zs-noteref"))). + Cons(ev.symAttr) + href := sx.Nil().Cons(sx.String(noteNum)).Cons(hrefAttr).Cons(ev.symA) + supAttr := sx.Nil().Cons(sx.Cons(ev.Make("id"), sx.String("fnref:"+noteID))).Cons(ev.symAttr) + return sx.Nil().Cons(href).Cons(supAttr).Cons(ev.Make("sup")) }) - ev.bind(sz.SymFormatDelete, 1, ev.makeFormatFn(symDEL)) - ev.bind(sz.SymFormatEmph, 1, ev.makeFormatFn(symEM)) - ev.bind(sz.SymFormatInsert, 1, ev.makeFormatFn(symINS)) - ev.bind(sz.SymFormatMark, 1, ev.makeFormatFn(symMARK)) - ev.bind(sz.SymFormatQuote, 1, ev.evalQuote) - ev.bind(sz.SymFormatSpan, 1, ev.makeFormatFn(SymSPAN)) - ev.bind(sz.SymFormatStrong, 1, ev.makeFormatFn(SymSTRONG)) - ev.bind(sz.SymFormatSub, 1, ev.makeFormatFn(symSUB)) - ev.bind(sz.SymFormatSuper, 1, ev.makeFormatFn(symSUP)) - - ev.bind(sz.SymLiteralComment, 1, func(args sx.Vector, env *Environment) sx.Object { - if ev.GetAttributes(args[0], env).HasDefault() { + ev.bind(sz.NameSymFormatDelete, 1, ev.makeFormatFn("del")) + ev.bind(sz.NameSymFormatEmph, 1, ev.makeFormatFn("em")) + ev.bind(sz.NameSymFormatInsert, 1, ev.makeFormatFn("ins")) + ev.bind(sz.NameSymFormatMark, 1, ev.makeFormatFn("mark")) + ev.bind(sz.NameSymFormatQuote, 1, ev.evalQuote) + ev.bind(sz.NameSymFormatSpan, 1, ev.makeFormatFn("span")) + ev.bind(sz.NameSymFormatStrong, 1, ev.makeFormatFn("strong")) + ev.bind(sz.NameSymFormatSub, 1, ev.makeFormatFn("sub")) + ev.bind(sz.NameSymFormatSuper, 1, ev.makeFormatFn("sup")) + + ev.bind(sz.NameSymLiteralComment, 1, func(args []sx.Object, env *Environment) sx.Object { + if ev.getAttributes(args[0], env).HasDefault() { if len(args) > 1 { - if s := getString(ev.Eval(args[1], env), env); s != "" { - return sx.Nil().Cons(s).Cons(sxhtml.SymInlineComment) + if s := getString(ev.eval(args[1], env), env); s != "" { + return sx.Nil().Cons(s).Cons(ev.Make(sxhtml.NameSymInlineComment)) } } } return sx.Nil() }) - ev.bind(sz.SymLiteralHTML, 2, ev.evalHTML) - ev.bind(sz.SymLiteralInput, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symKBD, env) - }) - ev.bind(sz.SymLiteralMath, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env).AddClass("zs-math") - return ev.evalLiteral(args, a, symCODE, env) - }) - ev.bind(sz.SymLiteralOutput, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symSAMP, env) - }) - ev.bind(sz.SymLiteralProg, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symCODE, env) - }) - - ev.bind(sz.SymLiteralZettel, 0, nilFn) -} - -func (ev *Evaluator) makeFormatFn(sym *sx.Symbol) EvalFn { - return func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + ev.bind(sz.NameSymLiteralHTML, 2, ev.evalHTML) + kbdSym := ev.Make("kbd") + ev.bind(sz.NameSymLiteralInput, 2, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalLiteral(args, nil, kbdSym, env) + }) + codeSym := ev.Make("code") + ev.bind(sz.NameSymLiteralMath, 2, func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env).AddClass("zs-math") + return ev.evalLiteral(args, a, codeSym, env) + }) + sampSym := ev.Make("samp") + ev.bind(sz.NameSymLiteralOutput, 2, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalLiteral(args, nil, sampSym, env) + }) + ev.bind(sz.NameSymLiteralProg, 2, func(args []sx.Object, env *Environment) sx.Object { + return ev.evalLiteral(args, nil, codeSym, env) + }) + + ev.bind(sz.NameSymLiteralZettel, 0, nilFn) +} + +func (ev *Evaluator) makeFormatFn(tag string) EvalFn { + sym := ev.Make(tag) + return func(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if val, hasClass := a.Get(""); hasClass { a = a.Remove("").AddClass(val) } @@ -774,12 +804,12 @@ return data.primLeft, data.primRight } return data.secLeft, data.secRight } -func (ev *Evaluator) evalQuote(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) +func (ev *Evaluator) evalQuote(args []sx.Object, env *Environment) sx.Object { + a := ev.getAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if val, hasClass := a.Get(""); hasClass { a = a.Remove("").AddClass(val) @@ -791,35 +821,35 @@ res := ev.evalSlice(args[1:], env) env.quoteNesting-- lastPair := res.LastPair() if lastPair.IsNil() { - res = sx.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.String(leftQ), sx.String(rightQ)), sx.Nil()) + res = sx.Cons(sx.MakeList(ev.symNoEscape, sx.String(leftQ), sx.String(rightQ)), sx.Nil()) } else { if quotes.nbsp { - lastPair.AppendBang(sx.MakeList(sxhtml.SymNoEscape, sx.String(" "), sx.String(rightQ))) - res = res.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.String(leftQ), sx.String(" "))) + lastPair.AppendBang(sx.MakeList(ev.symNoEscape, sx.String(" "), sx.String(rightQ))) + res = res.Cons(sx.MakeList(ev.symNoEscape, sx.String(leftQ), sx.String(" "))) } else { - lastPair.AppendBang(sx.MakeList(sxhtml.SymNoEscape, sx.String(rightQ))) - res = res.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.String(leftQ))) + lastPair.AppendBang(sx.MakeList(ev.symNoEscape, sx.String(rightQ))) + res = res.Cons(sx.MakeList(ev.symNoEscape, sx.String(leftQ))) } } if len(a) > 0 { res = res.Cons(ev.EvaluateAttrbute(a)) - return res.Cons(SymSPAN) + return res.Cons(ev.symSpan) } - return res.Cons(sxhtml.SymListSplice) + return res.Cons(ev.symList) } var visibleReplacer = strings.NewReplacer(" ", "\u2423") -func (ev *Evaluator) evalLiteral(args sx.Vector, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object { +func (ev *Evaluator) evalLiteral(args []sx.Object, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object { if a == nil { - a = ev.GetAttributes(args[0], env) + a = ev.getAttributes(args[0], env) } a = setProgLang(a) - literal := string(getString(args[1], env)) + literal := getString(args[1], env).String() if a.HasDefault() { a = a.RemoveDefault() literal = visibleReplacer.Replace(literal) } res := sx.Nil().Cons(sx.String(literal)) @@ -833,13 +863,13 @@ a = a.AddClass("language-" + val).Remove("") } return a } -func (ev *Evaluator) evalHTML(args sx.Vector, env *Environment) sx.Object { - if s := getString(ev.Eval(args[1], env), env); s != "" && IsSafe(string(s)) { - return sx.Nil().Cons(s).Cons(sxhtml.SymNoEscape) +func (ev *Evaluator) evalHTML(args []sx.Object, env *Environment) sx.Object { + if s := getString(ev.eval(args[1], env), env); s != "" && IsSafe(s.String()) { + return sx.Nil().Cons(s).Cons(ev.symNoEscape) } return nil } func (ev *Evaluator) evalBLOB(description *sx.Pair, syntax, data sx.String) sx.Object { @@ -848,45 +878,44 @@ } switch syntax { case "": return sx.Nil() case api.ValueSyntaxSVG: - return sx.Nil().Cons(sx.Nil().Cons(data).Cons(sxhtml.SymNoEscape)).Cons(SymP) + return sx.Nil().Cons(sx.Nil().Cons(data).Cons(ev.symNoEscape)).Cons(ev.symP) default: - imgAttr := sx.Nil().Cons(sx.Cons(SymAttrSrc, sx.String("data:image/"+string(syntax)+";base64,"+string(data)))) + imgAttr := sx.Nil().Cons(sx.Cons(ev.Make("src"), sx.String("data:image/"+syntax.String()+";base64,"+data.String()))) var sb strings.Builder flattenText(&sb, description) if d := sb.String(); d != "" { - imgAttr = imgAttr.Cons(sx.Cons(symAttrAlt, sx.String(d))) + imgAttr = imgAttr.Cons(sx.Cons(ev.Make("alt"), sx.String(d))) } - return sx.Nil().Cons(sx.Nil().Cons(imgAttr.Cons(sxhtml.SymAttr)).Cons(SymIMG)).Cons(SymP) + return sx.Nil().Cons(sx.Nil().Cons(imgAttr.Cons(ev.symAttr)).Cons(ev.Make("img"))).Cons(ev.symP) } } func flattenText(sb *strings.Builder, lst *sx.Pair) { for elem := lst; elem != nil; elem = elem.Tail() { switch obj := elem.Car().(type) { case sx.String: - sb.WriteString(string(obj)) + sb.WriteString(obj.String()) case *sx.Symbol: - if obj.IsEqual(sz.SymSpace) { + if obj.Name() == sz.NameSymSpace { sb.WriteByte(' ') break } case *sx.Pair: flattenText(sb, obj) } } } -func (ev *Evaluator) evalList(args sx.Vector, env *Environment) sx.Object { +func (ev *Evaluator) evalList(args []sx.Object, env *Environment) sx.Object { return ev.evalSlice(args, env) } -func nilFn(sx.Vector, *Environment) sx.Object { return sx.Nil() } +func nilFn([]sx.Object, *Environment) sx.Object { return sx.Nil() } -// Eval evaluates an object in an environment. -func (ev *Evaluator) Eval(obj sx.Object, env *Environment) sx.Object { +func (ev *Evaluator) eval(obj sx.Object, env *Environment) sx.Object { if env.err != nil { return sx.Nil() } if sx.IsNil(obj) { return obj @@ -898,28 +927,28 @@ sym, found := sx.GetSymbol(lst.Car()) if !found { env.err = fmt.Errorf("symbol expected, but got %T/%v", lst.Car(), lst.Car()) return sx.Nil() } - symVal := sym.GetValue() - fn, found := ev.fns[symVal] + name := sym.Name() + fn, found := ev.fns[name] if !found { - env.err = fmt.Errorf("symbol %q not bound", sym) + env.err = fmt.Errorf("symbol %q not bound", name) return sx.Nil() } - var args sx.Vector + var args []sx.Object for cdr := lst.Cdr(); !sx.IsNil(cdr); { pair, isPair := sx.GetPair(cdr) if !isPair { break } args = append(args, pair.Car()) cdr = pair.Cdr() } - if minArgs, hasMinArgs := ev.minArgs[symVal]; hasMinArgs { + if minArgs, hasMinArgs := ev.minArgs[name]; hasMinArgs { if minArgs > len(args) { - env.err = fmt.Errorf("%v needs at least %d arguments, but got only %d", sym, minArgs, len(args)) + env.err = fmt.Errorf("%v needs at least %d arguments, but got only %d", name, minArgs, len(args)) return sx.Nil() } } result := fn(args, env) if env.err != nil { @@ -926,54 +955,42 @@ return sx.Nil() } return result } -func (ev *Evaluator) evalSlice(args sx.Vector, env *Environment) *sx.Pair { - var result sx.ListBuilder - for _, arg := range args { - elem := ev.Eval(arg, env) - result.Add(elem) - } - if env.err == nil { - return result.List() - } - return nil -} - -// EvaluatePairList evaluates a list of lists. -func (ev *Evaluator) EvalPairList(pair *sx.Pair, env *Environment) *sx.Pair { - var result sx.ListBuilder - for node := pair; node != nil; node = node.Tail() { - elem := ev.Eval(node.Car(), env) - result.Add(elem) - } - if env.err == nil { - return result.List() - } - return nil -} - -func (ev *Evaluator) evalLink(a attrs.Attributes, refValue sx.String, inline sx.Vector, env *Environment) sx.Object { +func (ev *Evaluator) evalSlice(args []sx.Object, env *Environment) *sx.Pair { + result := sx.Cons(sx.Nil(), sx.Nil()) + curr := result + for _, arg := range args { + elem := ev.eval(arg, env) + if env.err != nil { + return nil + } + curr = curr.AppendBang(elem) + } + return result.Tail() +} + +func (ev *Evaluator) evalLink(a attrs.Attributes, refValue sx.String, inline []sx.Object, env *Environment) sx.Object { result := ev.evalSlice(inline, env) if len(inline) == 0 { result = sx.Nil().Cons(refValue) } if ev.noLinks { - return result.Cons(SymSPAN) + return result.Cons(ev.symSpan) } - return result.Cons(ev.EvaluateAttrbute(a)).Cons(SymA) + return result.Cons(ev.EvaluateAttrbute(a)).Cons(ev.symA) } -func (ev *Evaluator) getSymbol(obj sx.Object, env *Environment) *sx.Symbol { +func (ev *Evaluator) getSymbol(val sx.Object, env *Environment) *sx.Symbol { if env.err == nil { - if sym, ok := sx.GetSymbol(obj); ok { + if sym, ok := sx.GetSymbol(val); ok { return sym } - env.err = fmt.Errorf("%v/%T is not a symbol", obj, obj) + env.err = fmt.Errorf("%v/%T is not a symbol", val, val) } - return sx.MakeSymbol("???") + return ev.Make("???") } func getString(val sx.Object, env *Environment) sx.String { if env.err != nil { return "" } @@ -1000,15 +1017,12 @@ return int64(num.(sx.Int64)) } env.err = fmt.Errorf("%v/%T is not a number", val, val) return -1017 } - -// GetAttributes evaluates the given arg in the given environment and returns -// the contained attributes. -func (ev *Evaluator) GetAttributes(arg sx.Object, env *Environment) attrs.Attributes { - return sz.GetAttributes(getList(arg, env)) +func (ev *Evaluator) getAttributes(arg sx.Object, env *Environment) attrs.Attributes { + return sz.GetAttributes(getList(ev.eval(arg, env), env)) } var unsafeSnippets = []string{ "Change Log - -

Changes for Version 0.18.0 (pending)

- -

Changes for Version 0.17.0 (2024-03-04)

- * Generic GET method for HTTP client. - * Adapt to sz changes; see manual for current syntax. - * Got some packages from Zettelstore, for general use. +

Changes for Version 0.17.0 (pending)

Changes for Version 0.16.0 (2023-11-30)

* Refactor shtml transformator to support evaluating the language tree of a zettel AST. Its API has been changes, since evaluation is now top-down, Index: www/index.wiki ================================================================== --- www/index.wiki +++ www/index.wiki @@ -1,16 +1,16 @@ Home This repository contains Go client software to access [https://zettelstore.de|Zettelstore] via its API. -

Latest Release: 0.17.0 (2024-03-04)

- * [./changes.wiki#0_17|Change summary] - * [/timeline?p=v0.17.0&bt=v0.16.0&y=ci|Check-ins for version 0.17.0], - [/vdiff?to=v0.17.0&from=v0.16.0|content diff] - * [/timeline?df=v0.17.0&y=ci|Check-ins derived from the 0.17.0 release], - [/vdiff?from=v0.17.0&to=trunk|content diff] +

Latest Release: 0.16.0 (2023-11-30)

+ * [./changes.wiki#0_16|Change summary] + * [/timeline?p=v0.16.0&bt=v0.15.0&y=ci|Check-ins for version 0.16.0], + [/vdiff?to=v0.16.0&from=v0.15.0|content diff] + * [/timeline?df=v0.16.0&y=ci|Check-ins derived from the 0.16.0 release], + [/vdiff?from=v0.16.0&to=trunk|content diff] * [/timeline?t=release|Timeline of all past releases]

Use instructions

If you want to import this library into your own [https://go.dev/|Go] software,