texted is a scriptable, headless text editor intended to be used for
making automated edits to files on disk.
A texted program is a series of instructions.
No means of abstraction are provided by texted,
these are to be implemented at a higher level, by the application using texted.
Much research has been done in this field already, so texted's editing language
is based on emacs.
Consider this file example.js:
function doIt() {
console.log("doing the thing")
}We can turn it into this:
function helloWorld() {
console.log("hello, world")
}Through the following texted script:
search-forward "doIt"
set-mark
search-forward "("
replace-region "helloWorld"
search-forward "doing the thing"
replace-match "hello, world"This same script can also be encoded as an S-expression:
(search-forward "doIt")
(set-mark)
(search-forward "(")
(replace-region "helloWorld")
(search-forward "doing the thing")
(replace-match "hello, world")In fact, the regular texted script parser just uses a specialized reader:
- Leading whitespace is stripped
- If the next character is
(, read a regular S-expression list, ignoring whitespace between elements. - Otherwise build a list reading invoking the reader repeatedly until
a single
\nis encountered.
The canonical format for encoding texted programs as JSON is this:
- sexp-lists are JSON arrays,
- sexp-numbers are JSON numbers,
- sexp-strings are JSON strings,
- sexp-symbols are JSON strings appearing as the first element of a list. Symbols are not allowed anywhere else.
According to these rules, the program above can be encoded as JSON like this:
["search-forward", "doIt"]
["set-mark"]
["search-forward", "("]
["replace-region", "helloWorld"]
["search-forward", "doing the thing"]
["replace-match", "hello, world"]The fundamental building block of texted is the buffer.
A buffer is a buffer of utf8-encoded text
unless otherwise specified by the buffer's encoding.
A buffer has a point (the current cursor position), starting at 1,
indicating the position between two characters.
The mark is "the other position" in a buffer,
which together with the point forms the region.
To obtain a buffer, call texted.NewBuffer("initial contents")
buf.String()returns the contents of the buffer as a stringbuf.Region()returns the contents of the regionbuf.PointMin()returns the minimum value of the point (usually 1).buf.PointMax()returns the maximum value of the point.buf.Mark()returns the position of the mark.buf.Encoding()returns"utf8"val, err := buf.Do(script)executes script, returning the value of the last expression.
package texted
type ValueKind interface {
// a unique name identifying this kind
KindName() string
}
type Value interface {
Kind() ValueKind
}
// Example of a generic function operating on values and kinds
func IsA(value Value, kind Kind) bool {
return value.Kind().KindName() == kind.KindName()
}
type SymbolKind struct {}
func (kind *SymbolKind) KindName() string { return "symbol" }
var TheSymbolKind = &SymbolKind{}
type Symbol struct {
Name string
}
func (sym *Symbol) Kind() ValueKind {
return TheSymbolKind
}
// same for numbers
// same for lists (backed by slices)
// same for stringsThe evaluation environment consists of:
- a set of available functions,
- a buffer to operate on,
- a read-only source buffer,
- the program that is currently executed,
- the instruction pointer in this program
All instructions are executed sequentially and execution halts when the first error is returned by an instruction.
edlisp values follow regular Lisp-1 semantics:
- the first element of a list must be a symbol, referring to a function to execute,
- no special forms are supported,
- strings evaluate to themselves,
- numbers evaluate to themselves.
On the Go level:
package edlisp
type BuiltinFn = func(Value) (Value, error)
func Eval(program Value, env *Environment) (Value, error)