sexp

package module
v0.0.0-...-d3d2f25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2013 License: MIT Imports: 7 Imported by: 11

README

A very simple S-expressions parser. Still in development. More documentation
will follow shortly.

API documentation:
http://go.pkgdoc.org/github.com/nsf/sexp

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Beautify

func Beautify(err error, getcont func(string) []byte, ctx *SourceContext, colors bool) string

Returns a prettified version of the `err`.

The arguments need explanation, if you get a parser error (returned by one of the Parse functions) or unmarshaling error (returned by one of the Node methods), it can be prettified given that you have access to the SourceContext used in parsing and the source data.

You need to provide a closure `getcont` which will return contents of the source file for a given filename argument. The reason for this complicated interface is because SourceContext supports multiple files and it's not necessarly clear where the error is.

Colors argument specified whether you want to use colors or not. It applies typical terminal escape sequnces to the resulting string in case if the argument is true.

It will prettify only ParseError or UnmarshalError errors, if something else is given it will return error.Error() output.

Example
package main

import (
	"fmt"
	"github.com/nsf/sexp"
	"strings"
)

func main() {
	const example_sexp = `
		(correct syntax)
		( ; oops, no enclosing ')' here
	`
	var ctx sexp.SourceContext
	f := ctx.AddFile("example.sexp", -1)
	_, err := sexp.Parse(strings.NewReader(example_sexp), f)
	if err != nil {
		// we know the contents of the only source file used, let's
		// just return it:
		getcont := func(string) []byte {
			return []byte(example_sexp)
		}
		fmt.Println(sexp.Beautify(err, getcont, &ctx, false))
	}
}
Output:

example.sexp:3:3: error: missing matching sequence delimiter ')'
		( ; oops, no enclosing ')' here
		↑

func DontPanic

func DontPanic(f func() error) (err error)

Types

type Helper

type Helper struct {
	// contains filtered or unexported fields
}

A simple helper structure inspired by the simplejson-go API. Use Help function to actually acquire it from the given *Node.

func Help

func Help(node *Node) Helper

func (Helper) Bool

func (h Helper) Bool() (bool, error)

func (Helper) Child

func (h Helper) Child(n int) Helper

func (Helper) Float64

func (h Helper) Float64() (float64, error)

func (Helper) Int

func (h Helper) Int() (int, error)

func (Helper) IsList

func (h Helper) IsList() bool

func (Helper) IsScalar

func (h Helper) IsScalar() bool

func (Helper) IsValid

func (h Helper) IsValid() bool

func (Helper) MustBool

func (h Helper) MustBool() bool

func (Helper) MustFloat64

func (h Helper) MustFloat64() float64

func (Helper) MustInt

func (h Helper) MustInt() int

func (Helper) MustNode

func (h Helper) MustNode() *Node

func (Helper) MustString

func (h Helper) MustString() string

func (Helper) Next

func (h Helper) Next() Helper

func (Helper) Node

func (h Helper) Node() (*Node, error)

func (Helper) String

func (h Helper) String() (string, error)

type Node

type Node struct {
	Location SourceLoc
	Value    string
	Children *Node
	Next     *Node
}

The main and only AST structure. All fields are self explanatory, however the way they are being formed needs explanation.

A list node has empty value and non-nil children pointer, which is a nil-terminated list of children nodes.

A scalar node has nil children pointer.

Take a look at this example:

((1 2) 3 4)

will yield:

Node{Children:
  Node{Children:
    Node{Value: "1", Next:
    Node{Value: "2"}}, Next:
  Node{Value: "3", Next:
  Node{Value: "4"}}}}

func Parse

func Parse(r io.RuneReader, f *SourceFile) (*Node, error)

Parses S-expressions from a given io.RuneReader.

Returned node is a virtual list node with all the S-expressions read from the stream as children. In case of a syntax error, the returned error is not nil.

It's worth explaining where do you get *SourceFile from. The typical way to create it is:

var ctx SourceContext
f := ctx.AddFile(filename, length)

And you'll be able to use ctx later for decoding source location information. It's ok to provide -1 as length if it's unknown. In that case though you won't be able to add more files to the given SourceContext until the file with unknown length is finalized, which happens when parsing is finished.

Also f is optional, nil is a perfectly valid argument for it, in that case it will create a temporary context and add an unnamed file to it. Less setup work is required, but you lose the ability to decode error source code locations.

func ParseOne

func ParseOne(r io.RuneScanner, f *SourceFile) (*Node, error)

Parses a single S-expression node from a stream.

Returns just one node, be it a value or a list, doesn't touch the rest of the data. In case of a syntax error, the returned error is not nil.

Note that unlike Parse it requires io.RuneScanner. It's a technical requirement, because in some cases s-expressions syntax delimiter is not part of the s-expression value, like in a very simple example: "x y". "x" here will be returned as a value Node, but " y" should remain untouched, however without reading the space character we can't tell if this is the end of "x" or not. Hence the requirement of being able to unread one rune.

It's unclear what to do about error reporting for S-expressions read from the stream. The usual idea of lines and columns doesn't apply here. Hence if you do want to report errors gracefully some hacks will be necessary to do so.

NOTE: Maybe ParseOne will be changed in future to better serve the need of good error reporting.

func (*Node) IsList

func (n *Node) IsList() bool

Returns true if the node is a list (has children).

func (*Node) IsScalar

func (n *Node) IsScalar() bool

Return true if the node is a scalar (has no children).

func (*Node) IterKeyValues

func (n *Node) IterKeyValues(f func(k, v *Node) error) error

Walk over children nodes, assuming they are key/value pairs. It returns error if the iterable node is not a list or if any of its children is not a key/value pair.

func (*Node) Nth

func (n *Node) Nth(num int) (*Node, error)

Returns Nth child node. If node is not a list, it will return an error.

func (*Node) NumChildren

func (n *Node) NumChildren() int

Returns the number of children nodes. Has O(N) complexity.

func (*Node) String

func (n *Node) String() string

func (*Node) Unmarshal

func (n *Node) Unmarshal(vals ...interface{}) (err error)

Unmarshals the node and its siblings to pointer values.

The function expects pointers to values with arbitrary types. If one of the arguments is not a pointer it will panic.

It supports unmarshaling to the following types:

  • all number types: int{8,16,32,64}, uint{8,16,32,64}, float{32,64}
  • bool
  • string
  • arrays and slices of all supported types
  • empty interfaces{}
  • maps
  • structs
  • pointers to any of the supported types (only one level of indirection)
  • any type which implements Unmarshaler

Here's some details on unmarshaling semantics:

(u)ints: unmarshaled using strconv.ParseInt/strconv.ParseUint with base 10
         only
floats:  unmarshaled using strconv.ParseFloat
bool:    works strictly on two values "true" or "false"
string:  unmarshaled as is (keep in mind that lexer supports escape sequences)
arrays:  uses up to len(array) elements, if there is a smaller amount of
         elements, the rest is zeroed
slices:  uses all elements appending them to the slice, however if the slice
         was bigger than the amount of elements, it will reslice it to the
         appropriate length
iface:   only empty interfaces are supported, it will unmarshal AST to
         a []interface{} or a string
map:     when unmarshaling to the map, assumes this AST form:
         `((key value) (key value) (key value))`, doesn't clear the map
         before appending all the key value pairs
struct:  uses the same AST form as the map, where `key` means `field`,
         supports `sexp` tags (see description below), will try to match
         name specified in the tag, the field name and the field name
         ignoring the case in that order

Struct tags have the form: "name,opt,opt". Special tag "-" means "skip me". Supported options:

siblings: will use sibling nodes instead of children for unmarshaling
          to an array or a slice.

Important note: If the type implements Unmarshaler interface, it will use it instead of applying default unmarshaling strategies described above.

Example
package main

import (
	"fmt"
	"github.com/nsf/sexp"
	"strings"
)

func main() {
	const example_sexp = `
		(position 5   10    4.7)
		(target   10  -2.4  30.3)
	`

	var example struct {
		Pos [3]float32 `sexp:"position,siblings"`
		Tgt [3]float32 `sexp:"target,siblings"`
	}

	ast, err := sexp.Parse(strings.NewReader(example_sexp), nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = ast.Unmarshal(&example)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(example.Pos)
	fmt.Println(example.Tgt)

}
Output:

[5 10 4.7]
[10 -2.4 30.3]

func (*Node) UnmarshalChildren

func (n *Node) UnmarshalChildren(vals ...interface{}) (err error)

Unmarshals all children nodes of the node to pointer values. Applies the same logic as Unmarshal. See description of the (*Node).Unmarshal method for more details.

type ParseError

type ParseError struct {
	Location SourceLoc
	// contains filtered or unexported fields
}

This error structure is Parse* functions family specific, it returns information about errors encountered during parsing. Location can be decoded using the context you passed in as an argument. If the context was nil, then the location is simply a byte offset from the beginning of the input stream.

func (*ParseError) Error

func (e *ParseError) Error() string

Satisfy the built-in error interface. Returns the error message (without source location).

type SourceContext

type SourceContext struct {
	// contains filtered or unexported fields
}

Source context holds information needed to decompress source locations. It supports multiple files with knowns and unknowns lengths. Although having a file with unknown length prevents you from adding more files until it's been finalized.

func (*SourceContext) AddFile

func (s *SourceContext) AddFile(filename string, length int) *SourceFile

Adds a new file to the context, use -1 as length if the length is unknown, but keep in mind that having a file with unknown length prevents further AddFile calls, they will panic. In order to continue adding files to the context, the last file with unknown length must be finalized. Method doesn't read anything, all the arguments are purely informative.

func (*SourceContext) Decode

func (s *SourceContext) Decode(loc SourceLoc) SourceLocEx

Decodes an encoded source location.

type SourceFile

type SourceFile struct {
	// contains filtered or unexported fields
}

Represents one file within source context, usually a parser will require you to pass source file before parsing. Parser should use SourceFile.Encode method to encode source location information, method takes byte offset from the beginning of the file as an argument.

func (*SourceFile) AddLine

func (f *SourceFile) AddLine(offset int)

Adds a new line with a given offset, keep in mind that the first line is added automatically by SourceContext.AddFile. A parser typically calls that method each time it encounters a newline character.

func (*SourceFile) Encode

func (f *SourceFile) Encode(offset int) SourceLoc

Encodes an offset from the beginning of the file as a source location.

func (*SourceFile) Finalize

func (f *SourceFile) Finalize(len int)

If the length of the file is unknown at the beginning, the file must be finalized at some point using this method. Otherwise no new files can be added to the source context.

type SourceLoc

type SourceLoc uint32

Compressed SourceLocEx, can be decoded using an appropriate SourceContext.

type SourceLocEx

type SourceLocEx struct {
	Filename   string
	Line       int // starting from 1
	LineOffset int // offset to the beginning of the line (in bytes)
	Offset     int // offset to the location (in bytes)
}

Complete source location information. Line number starts from 1, it is a traditional choice. The column is not specified, but you can find it by counting runes between LineOffset and Offset within the source file this location belongs to.

type UnmarshalError

type UnmarshalError struct {
	Type reflect.Type
	Node *Node
	// contains filtered or unexported fields
}

func NewUnmarshalError

func NewUnmarshalError(n *Node, t reflect.Type, format string, args ...interface{}) *UnmarshalError

func (*UnmarshalError) Error

func (e *UnmarshalError) Error() string

type Unmarshaler

type Unmarshaler interface {
	UnmarshalSexp(n *Node) error
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL