https://github.com/hymkor/sxencode-go
Go library that encodes arbitrary Go values into Lisp-style S-expressions
https://github.com/hymkor/sxencode-go
encoder go golang lisp s-expression
Last synced: about 1 year ago
JSON representation
Go library that encodes arbitrary Go values into Lisp-style S-expressions
- Host: GitHub
- URL: https://github.com/hymkor/sxencode-go
- Owner: hymkor
- License: mit
- Created: 2025-07-13T13:57:44.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2025-07-14T06:17:09.000Z (about 1 year ago)
- Last Synced: 2025-07-14T09:21:07.986Z (about 1 year ago)
- Topics: encoder, go, golang, lisp, s-expression
- Language: Go
- Homepage:
- Size: 14.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
sxencode - Golang Encoder to S-Expression
=========================================
`sxencode` is a Go library that encodes arbitrary Go values into **Lisp-style S-expressions**.
It is useful for interfacing with Lisp systems, structured debugging, or human-readable serialization.
The output is readable by standard Lisp implementations including Common Lisp and ISLisp.
Example
-------
The following is a minimal Go program that encodes a Go struct and the encoder itself into S-expressions:
```example.go
package main
import (
"fmt"
"os"
"github.com/hymkor/sxencode-go"
)
func main() {
type Foo struct {
Bar string
Baz float64
Qux []int
Quux map[string]int
Quuux string
}
value := &Foo{
Bar: "hogehoge",
Baz: 0.1,
Qux: []int{1, 2, 3, 4},
Quux: map[string]int{"ahaha": 1, "ihihi": 2, "ufufu": 3},
Quuux: "a\"\\\n\tb",
}
enc := sxencode.NewEncoder(os.Stdout)
enc.Encode(value)
fmt.Println()
enc.Encode(enc)
fmt.Println()
}
```
## Output of `go run example.go`
The output of the above program is a pair of S-expressions representing the encoded data:
```make example|
go run example.go
((struct Foo)(Bar "hogehoge")(Baz 0.1)(Qux (1 2 3 4))(Quux (("ahaha" 1)("ihihi" 2)("ufufu" 3)))(Quuux "a\"\\
b"))
((struct Encoder)(ArrayHeader "")(ArrayIndex nil)(TypeNotFound ""))
```
### Output Format
* A struct is encoded as a list beginning with `(struct )`, followed by pairs of field name and value:
Example: `((struct Foo)(Bar "hogehoge") ...)`.
The field names and type name are emitted as symbols, making them easy to extract with `(assoc)` in Lisp.
* A map is encoded as a list of `(key value)` pairs.
If the keys are strings, note that in many Lisp dialects, `(assoc)` with a string key won't match unless `equal` is used.
For this reason, helper functions like `field` (shown later) may be necessary.
* A slice or array is encoded as a plain list of elements: `(1 2 3 4)`.
These conventions make it easy to parse the output in various Lisp systems while retaining structural information.
## Reading the output in SBCL
The following shows how the output can be parsed and validated using [SBCL (Steel Bank Common Lisp)][SBCL].
[SBCL]: https://www.sbcl.org/
```make test-sbcl |
go run example.go | sbcl --load "test-sbcl.lsp"
This is SBCL 2.5.6, an implementation of ANSI Common Lisp.
More information about SBCL is available at .
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
PASS: (test (FIELD 'STRUCT DATA) FOO)
PASS: (test (FIELD 'BAR DATA) "hogehoge")
PASS: (test (FIELD 'BAZ DATA) 0.1)
PASS: (test (FIELD 'QUX DATA) (1 2 3 4))
PASS: (test (FIELD "ahaha" M) 1)
PASS: (test (FIELD "ihihi" M) 2)
PASS: (test (FIELD "ufufu" M) 3)
PASS: (test (FIELD 'QUUUX DATA) "a\"\\
b")
PASS: (test (FIELD 'STRUCT DATA) ENCODER)
PASS: (test (FIELD 'ARRAYHEADER DATA) "")
PASS: (test (FIELD 'ARRAYINDEX DATA) NIL)
PASS: (test (FIELD 'TYPENOTFOUND DATA) "")
*
```
### Lisp files
These are the supporting Lisp files used for the SBCL test:
#### `test-sbcl.lsp`
```lisp
(defun standard-input () *standard-input*)
(defun standard-output () t)
(load "test.lsp")
```
#### `test.lsp`
```lisp
(defmacro test (source expect)
(let ((result (gensym)))
`(let ((,result ,source))
(if (equal ,result ,expect)
(format (standard-output) "PASS: (test ~S ~S)~%"
(quote ,source)
,expect)
(format (standard-output) "FAIL: (test ~S ~S)~% but ~S~%"
(quote ,source)
,expect
,result)
))))
(defun field (key m)
(and
m
(consp m)
(if (equal (car (car m)) key)
(car (cdr (car m)))
(field key (cdr m)))))
(let ((data (read (standard-input) nil nil)))
(test (field 'struct-name data) 'Foo)
(test (field 'name data) "hogehoge")
(test (field 'value data) 0.1)
(test (field 'array data) '(1 2 3 4))
(let ((m (field 'map data)))
(test (field "ahaha" m) 1)
(test (field "ihihi" m) 2)
(test (field "ufufu" m) 3)))
(let ((data (read (standard-input) nil nil)))
(test (field 'struct-name data) 'Encoder)
(test (field 'arrayheader data) "")
(test (field 'arrayindex data) nil)
(test (field 'typenotfound data) ""))
```
## Reading the output in OKI ISLisp
The same logic can be used in [OKI ISLisp](https://openlab.jp/islisp/), by loading the wrapper file `test-oki.lsp`:
```sh
go run example.go > sample.log
echo "(load \"test-oki.lsp\")" | islisp
```
Execution result:
```
> ISLisp Version 0.80 (1999/02/25)
>
ISLisp>PASS: (test (FIELD (QUOTE STRUCT-NAME) DATA) FOO)
PASS: (test (FIELD (QUOTE NAME) DATA) "hogehoge")
PASS: (test (FIELD (QUOTE VALUE) DATA) 0.1)
PASS: (test (FIELD (QUOTE ARRAY) DATA) (1 2 3 4))
PASS: (test (FIELD "ahaha" M) 1)
PASS: (test (FIELD "ihihi" M) 2)
PASS: (test (FIELD "ufufu" M) 3)
PASS: (test (FIELD (QUOTE STRUCT-NAME) DATA) ENCODER)
PASS: (test (FIELD (QUOTE ARRAYHEADER) DATA) "")
PASS: (test (FIELD (QUOTE ARRAYINDEX) DATA) NIL)
PASS: (test (FIELD (QUOTE TYPENOTFOUND) DATA) "")
T
ISLisp>
```
#### `test-oki.lsp`
```lisp
(with-open-input-file
(fd "sample.log")
(with-standard-input
fd
(load "test.lsp")))
```
## Also works with gmnlisp
The same test.lsp file can be run directly using [gmnlisp] — no modifications are needed, since [gmnlisp] is designed to be largely compatible with ISLisp and supports the same input/output conventions.
[gmnlisp]: https://github.com/hymkor/gmnlisp
```sh
go run example.go | gmnlisp test.lsp
```
Result:
```
PASS: (test (FIELD 'STRUCT-NAME DATA) FOO)
PASS: (test (FIELD 'NAME DATA) "hogehoge")
PASS: (test (FIELD 'VALUE DATA) 0.100000)
PASS: (test (FIELD 'ARRAY DATA) (1 2 3 4))
PASS: (test (FIELD "ahaha" M) 1)
PASS: (test (FIELD "ihihi" M) 2)
PASS: (test (FIELD "ufufu" M) 3)
PASS: (test (FIELD 'STRUCT-NAME DATA) ENCODER)
PASS: (test (FIELD 'ARRAYHEADER DATA) "")
PASS: (test (FIELD 'ARRAYINDEX DATA) NIL)
PASS: (test (FIELD 'TYPENOTFOUND DATA) "")
```
## Summary
* `sxencode` converts Go values into Lisp-compatible S-expressions
* Output is compatible with multiple Lisp systems: Common Lisp, ISLisp (OKI, gmnlisp)
* Includes reusable test logic in `test.lsp`
* Ideal for inter-language data exchange and human-readable serialization
Pull requests and feedback are welcome!