ell/ell/ell.go

1295 lines
29 KiB
Go

//
// Copyright (c) 2018, Přemysl Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Package ell implements a simple scripting language.
package ell
import (
"errors"
"fmt"
"io"
// standard library
"bytes"
"os"
"os/exec"
)
// --- Values ------------------------------------------------------------------
// VType denotes the type of a value.
type VType int
const (
// VTypeString denotes a string value.
VTypeString VType = iota
// VTypeList denotes a list value.
VTypeList
)
// V is a value in the ell language.
type V struct {
Type VType // the type of this value
Next *V // next value in sequence
Head *V // the head of a VTypeList
String []byte // the immutable contents of a VTypeString
}
// Clone clones a value without following the rest of its chain.
func (v *V) Clone() *V {
if v == nil {
return nil
}
return &V{
Type: v.Type,
Next: nil,
Head: v.Head.CloneSeq(),
// TODO: Consider actually storing the string as a string,
// so that the compiler/runtime assure its immutability.
String: v.String,
}
}
// CloneSeq clones a value including the rest of its chain.
func (v *V) CloneSeq() *V {
var head *V
for out := &head; v != nil; v = v.Next {
*out = v.Clone()
out = &(*out).Next
}
return head
}
// NewString creates a new value containing a string.
func NewString(string []byte) *V {
return &V{
Type: VTypeString,
String: string,
}
}
// NewList creates a new list value containing the given sequence.
func NewList(head *V) *V {
return &V{
Type: VTypeList,
Head: head,
}
}
// --- Lexer -------------------------------------------------------------------
type token int
const (
tAbort token = iota
tLParen
tRParen
tLBracket
tRBracket
tLBrace
tRBrace
tString
tNewline
tAt
)
func (t token) String() string {
switch t {
case tAbort:
return "end of input"
case tLParen:
return "left parenthesis"
case tRParen:
return "right parenthesis"
case tLBracket:
return "left bracket"
case tRBracket:
return "right bracket"
case tLBrace:
return "left brace"
case tRBrace:
return "right brace"
case tString:
return "string"
case tNewline:
return "newline"
case tAt:
return "at symbol"
}
panic("unknown token")
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type lexer struct {
p []byte // unread input
line, column int // current line and column
buf []byte // parsed string value
}
func newLexer(p []byte) *lexer {
return &lexer{p: p}
}
func (lex *lexer) advance() byte {
ch := lex.p[0]
lex.p = lex.p[1:]
if ch == '\n' {
lex.column = 0
lex.line++
} else {
lex.column++
}
return ch
}
var lexerHexAlphabet = "0123456789abcdef"
// fromHex converts a nibble from hexadecimal. Avoiding dependencies.
func lexerFromHex(ch byte) int {
if ch >= 'A' && ch <= 'Z' {
ch += 32
}
for i := 0; i < len(lexerHexAlphabet); i++ {
if lexerHexAlphabet[i] == ch {
return i
}
}
return -1
}
func (lex *lexer) hexaEscape() bool {
if len(lex.p) < 2 {
return false
}
h := lexerFromHex(lex.advance())
if h < 0 {
return false
}
l := lexerFromHex(lex.advance())
if l < 0 {
return false
}
lex.buf = append(lex.buf, byte(h<<4|l))
return true
}
const (
lexerStringQuote = '\''
lexerEscape = '\\'
lexerComment = '#'
)
func lexerIsWhitespace(ch byte) bool {
return ch == 0 || ch == ' ' || ch == '\t' || ch == '\r'
}
var lexerEscapes = map[byte]byte{
lexerStringQuote: lexerStringQuote,
lexerEscape: lexerEscape,
'a': '\a',
'b': '\b',
'n': '\n',
'r': '\r',
't': '\t',
}
func (lex *lexer) escapeSequence() error {
if len(lex.p) == 0 {
return errors.New("premature end of escape sequence")
}
ch := lex.advance()
if ch == 'x' {
if lex.hexaEscape() {
return nil
}
return errors.New("invalid hexadecimal escape")
}
ch, ok := lexerEscapes[ch]
if !ok {
return errors.New("unknown escape sequence")
}
lex.buf = append(lex.buf, ch)
return nil
}
func (lex *lexer) string() error {
for len(lex.p) > 0 {
ch := lex.advance()
if ch == lexerStringQuote {
return nil
}
if ch != lexerEscape {
lex.buf = append(lex.buf, ch)
} else if err := lex.escapeSequence(); err != nil {
return err
}
}
return errors.New("premature end of string")
}
var lexerTokens = map[byte]token{
'(': tLParen,
')': tRParen,
'[': tLBracket,
']': tRBracket,
'{': tLBrace,
'}': tRBrace,
';': tNewline,
'\n': tNewline,
'@': tAt,
lexerStringQuote: tString,
}
func (lex *lexer) next() (token, error) {
for len(lex.p) > 0 && lexerIsWhitespace(lex.p[0]) {
lex.advance()
}
if len(lex.p) == 0 {
return tAbort, nil
}
lex.buf = nil
ch := lex.advance()
if ch == lexerComment {
for len(lex.p) > 0 {
if ch := lex.advance(); ch == '\n' {
return tNewline, nil
}
}
return tAbort, nil
}
token, ok := lexerTokens[ch]
if !ok {
lex.buf = append(lex.buf, ch)
for len(lex.p) > 0 && !lexerIsWhitespace(lex.p[0]) &&
lexerTokens[lex.p[0]] == 0 /* ugly but short */ {
lex.buf = append(lex.buf, lex.advance())
}
return tString, nil
}
if token == tString {
if err := lex.string(); err != nil {
return tAbort, err
}
}
return token, nil
}
func (lex *lexer) errorf(format string, a ...interface{}) error {
return fmt.Errorf("at or before line %d, column %d: %s",
lex.line+1, lex.column+1, fmt.Sprintf(format, a...))
}
// --- Printing ----------------------------------------------------------------
func printStringNeedsQuoting(s *V) bool {
for i := 0; i < len(s.String); i++ {
ch := s.String[i]
if lexerIsWhitespace(ch) || lexerTokens[ch] != 0 ||
ch == lexerEscape || ch < 32 {
return true
}
}
return len(s.String) == 0
}
func printString(w io.Writer, s *V) bool {
if s.Type != VTypeString {
return false
}
if !printStringNeedsQuoting(s) {
_, _ = w.Write(s.String)
return true
}
_, _ = w.Write([]byte{lexerStringQuote})
for i := 0; i < len(s.String); i++ {
ch := s.String[i]
if ch < 32 {
_, _ = fmt.Fprintf(w, "\\x%02x", ch)
} else if ch == lexerEscape || ch == lexerStringQuote {
_, _ = fmt.Fprintf(w, "\\%c", ch)
} else {
_, _ = w.Write([]byte{ch})
}
}
_, _ = w.Write([]byte{lexerStringQuote})
return true
}
func printBlock(w io.Writer, list *V) bool {
if list.Head == nil || string(list.Head.String) != "block" {
return false
}
list = list.Head.Next
for line := list; line != nil; line = line.Next {
if line.Type != VTypeList {
return false
}
}
_, _ = w.Write([]byte{'{'})
for line := list; line != nil; line = line.Next {
_, _ = w.Write([]byte{' '})
PrintSeq(w, line.Head)
if line.Next != nil {
_, _ = w.Write([]byte{';'})
} else {
_, _ = w.Write([]byte{' '})
}
}
_, _ = w.Write([]byte{'}'})
return true
}
func printSet(w io.Writer, list *V) bool {
if list.Head == nil || string(list.Head.String) != "set" ||
list.Head.Next == nil || list.Head.Next.Next != nil {
return false
}
_, _ = w.Write([]byte{'@'})
PrintSeq(w, list.Head.Next)
return true
}
func printList(w io.Writer, list *V) bool {
if list.Head == nil || string(list.Head.String) != "list" {
return false
}
_, _ = w.Write([]byte{'['})
PrintSeq(w, list.Head.Next)
_, _ = w.Write([]byte{']'})
return true
}
// PrintV serializes a value to the given writer, ignoring I/O errors.
func PrintV(w io.Writer, v *V) {
if printString(w, v) ||
printBlock(w, v) ||
printSet(w, v) ||
printList(w, v) {
return
}
_, _ = w.Write([]byte{'('})
PrintSeq(w, v.Head)
_, _ = w.Write([]byte{')'})
}
// PrintSeq serializes a sequence of values to the given writer.
func PrintSeq(w io.Writer, v *V) {
for ; v != nil; v = v.Next {
PrintV(w, v)
if v.Next != nil {
_, _ = w.Write([]byte{' '})
}
}
}
// --- Parsing -----------------------------------------------------------------
// Parser is a context for parsing.
type Parser struct {
lexer *lexer // tokenizer
token token // current token in the lexer
replaceToken bool // replace the token
}
// NewParser returns a new parser for the give byte slice.
func NewParser(script []byte) *Parser {
// As reading in tokens may cause exceptions, we wait for the first peek
// to replace the initial ELLT_ABORT.
return &Parser{
lexer: newLexer(script),
replaceToken: true,
}
}
func (p *Parser) peek() token {
if p.replaceToken {
token, err := p.lexer.next()
if err != nil {
panic(p.lexer.errorf("%s", err))
}
p.token = token
p.replaceToken = false
}
return p.token
}
func (p *Parser) accept(token token) bool {
p.replaceToken = p.peek() == token
return p.replaceToken
}
func (p *Parser) expect(token token) {
if !p.accept(token) {
panic(p.lexer.errorf("unexpected `%s', expected `%s'", p.token, token))
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func (p *Parser) skipNL() {
for p.accept(tNewline) {
}
}
func parsePrefixList(seq *V, name string) *V {
prefix := NewString([]byte(name))
prefix.Next = seq
return NewList(prefix)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func (p *Parser) parseV() *V {
var result *V
tail := &result
p.skipNL()
switch {
case p.accept(tString):
return NewString(p.lexer.buf)
case p.accept(tAt):
result = p.parseV()
return parsePrefixList(result, "set")
case p.accept(tLParen):
for !p.accept(tRParen) {
*tail = p.parseV()
tail = &(*tail).Next
p.skipNL()
}
return NewList(result)
case p.accept(tLBracket):
for !p.accept(tRBracket) {
*tail = p.parseV()
tail = &(*tail).Next
p.skipNL()
}
return parsePrefixList(result, "list")
case p.accept(tLBrace):
for {
*tail = p.parseLine()
if *tail == nil {
break
}
tail = &(*tail).Next
}
p.expect(tRBrace)
return parsePrefixList(result, "block")
}
panic(p.lexer.errorf("unexpected `%s', expected a value", p.token))
}
func (p *Parser) parseLine() *V {
var result *V
tail := &result
for p.peek() != tRBrace && p.peek() != tAbort {
if !p.accept(tNewline) {
*tail = p.parseV()
tail = &(*tail).Next
} else if result != nil {
return NewList(result)
}
}
if result != nil {
return NewList(result)
}
return nil
}
// Run runs the parser and returns a value to be interpreted or an error.
func (p *Parser) Run() (result *V, err error) {
// "The convention in the Go libraries is that even when a package
// uses panic internally, its external API still presents explicit
// error return values." We're good.
defer func() {
if r := recover(); r != nil {
result, err = nil, r.(error)
}
}()
tail := &result
for {
*tail = p.parseLine()
if *tail == nil {
break
}
tail = &(*tail).Next
}
p.expect(tAbort)
return result, nil
}
// --- Runtime -----------------------------------------------------------------
// Handler is a Go handler for an Ell function.
type Handler func(*Ell, *V, **V) bool
// Ell is an interpreter context.
type Ell struct {
Globals *V // list of global variables
scopes *V // dynamic scopes from the newest
Native map[string]Handler // maps strings to Go functions
Error string // error information
}
// New returns a new interpreter context ready for program execution.
func New() *Ell {
return &Ell{
Native: make(map[string]Handler),
}
}
func scopeFind(scope **V, name string) **V {
for ; *scope != nil; scope = &(*scope).Next {
if string((*scope).Head.String) == name {
return scope
}
}
return nil
}
func scopePrepend(scope **V, name string, v *V) {
key := NewString([]byte(name))
pair := NewList(key)
key.Next = v
pair.Next = *scope
*scope = pair
}
// Get retrieves a value by name from the scope or from global variables.
func (ell *Ell) Get(name string) *V {
var place **V
for scope := ell.scopes; scope != nil; scope = scope.Next {
if place = scopeFind(&scope.Head, name); place != nil {
return (*place).Head.Next
}
}
if place = scopeFind(&ell.Globals, name); place != nil {
return (*place).Head.Next
}
return nil
}
// Set sets a value by name in the scope or in global variables.
func (ell *Ell) Set(name string, v *V) {
var place **V
for scope := ell.scopes; scope != nil; scope = scope.Next {
if place = scopeFind(&scope.Head, name); place != nil {
(*place).Head.Next = v
return
}
}
// Variables only get deleted by "arg" or from the global scope.
if place = scopeFind(&ell.Globals, name); place != nil {
*place = (*place).Next
}
scopePrepend(&ell.Globals, name, v)
}
// NativeFind returns the handler for a native function or nil.
func (ell *Ell) NativeFind(name string) Handler {
return ell.Native[name]
}
// NativeRegister registers a native Go function handler.
func (ell *Ell) NativeRegister(name string, handler Handler) {
ell.Native[name] = handler
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Errorf sets an error message in the interpreter context and returns false.
func (ell *Ell) Errorf(format string, args ...interface{}) bool {
ell.Error = fmt.Sprintf(format, args...)
return false
}
func (ell *Ell) canModifyError() bool {
// Errors starting with an underscore are exceptions and would not work
// with stack traces generated this way.
return ell.Error == "" || ell.Error[0] != '_'
}
func (ell *Ell) evalArgs(args *V, result **V) bool {
var res *V
out := &res
i := 0
for ; args != nil; args = args.Next {
var evaluated *V
// Arguments should not evaporate, default to a nil value.
if !ell.evalStatement(args, &evaluated) {
goto error
}
if evaluated == nil {
evaluated = NewList(nil)
}
evaluated.Next = nil
*out = evaluated
out = &(*out).Next
i++
}
*result = res
return true
error:
// Once the code flows like this, at least make some use of it.
if ell.canModifyError() {
ell.Errorf("(argument %d) -> %s", i, ell.Error)
}
return false
}
func (ell *Ell) evalNative(name string, args *V, result **V) bool {
fn := ell.NativeFind(name)
if fn == nil {
return ell.Errorf("unknown function")
}
var arguments *V
if !ell.evalArgs(args, &arguments) {
return false
}
return fn(ell, arguments, result)
}
func (ell *Ell) evalResolved(body *V, args *V, result **V) bool {
// Resolving names recursively could be pretty fatal, let's not do that.
if body.Type == VTypeString {
*result = body.Clone()
return true
}
var arguments *V
return ell.evalArgs(args, &arguments) &&
ell.EvalBlock(body.Head, arguments, result)
}
func (ell *Ell) evalValue(body *V, result **V) bool {
args := body.Next
if body.Type == VTypeString {
name := string(body.String)
if name == "block" {
if args != nil {
*result = NewList(args.CloneSeq())
}
return true
}
if body := ell.Get(name); body != nil {
return ell.evalResolved(body, args, result)
}
return ell.evalNative(name, args, result)
}
// When someone tries to call a block directly, we must evaluate it;
// e.g. something like `{ choose [@f1 @f2 @f3] } arg1 arg2 arg3`.
var evaluated *V
if !ell.evalStatement(body, &evaluated) {
return false
}
// It might a bit confusing that this doesn't evaluate arguments
// but neither does "block" and there's nothing to do here.
if evaluated == nil {
return true
}
return ell.evalResolved(evaluated, args, result)
}
func (ell *Ell) evalStatement(statement *V, result **V) bool {
if statement.Type == VTypeString {
*result = statement.Clone()
return true
}
// Executing a nil value results in no value. It's not very different from
// calling a block that returns no value--it's for our callers to resolve.
if statement.Head == nil || ell.evalValue(statement.Head, result) {
return true
}
*result = nil
name := "(block)"
if statement.Head.Type == VTypeString {
name = string(statement.Head.String)
}
if ell.canModifyError() {
ell.Errorf("%s -> %s", name, ell.Error)
}
return false
}
func argsToScope(args *V, scope **V) {
args = NewList(args)
scopePrepend(scope, "args", args)
i := 0
for args = args.Head; args != nil; args = args.Next {
i++
scopePrepend(scope, fmt.Sprintf("%d", i), args.Clone())
}
*scope = NewList(*scope)
}
// EvalBlock executes a block and returns whatever the last statement returned,
// eats args.
func (ell *Ell) EvalBlock(body *V, args *V, result **V) bool {
var scope *V
argsToScope(args, &scope)
scope.Next = ell.scopes
ell.scopes = scope
ok := true
for ; body != nil; body = body.Next {
*result = nil
if ok = ell.evalStatement(body, result); !ok {
break
}
}
ell.scopes = scope.Next
return ok
}
// --- Standard library --------------------------------------------------------
// EvalAny evaluates any value.
func EvalAny(ell *Ell, body *V, arg *V, result **V) bool {
if body.Type == VTypeString {
*result = body.Clone()
return true
}
return ell.EvalBlock(body.Head, arg.Clone(), result)
}
// NewNumber creates a new string value containing a number.
func NewNumber(n float64) *V {
s := fmt.Sprintf("%f", n)
i := len(s)
for i > 0 && s[i-1] == '0' {
i--
}
if s[i-1] == '.' {
i--
}
return NewString([]byte(s[:i]))
}
// Truthy decides whether any value is logically true.
func Truthy(v *V) bool {
return v != nil && (v.Head != nil || len(v.String) > 0)
}
// NewBoolean creates a new string value copying the boolean's truthiness.
func NewBoolean(b bool) *V {
if b {
return NewString([]byte("1"))
}
return NewString(nil)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func fnLocal(ell *Ell, args *V, result **V) bool {
names := args
if names == nil || names.Type != VTypeList {
return ell.Errorf("first argument must be a list")
}
// Duplicates or non-strings don't really matter to us, user's problem.
scope := &ell.scopes.Head
values := names.Next
for names = names.Head; names != nil; names = names.Next {
scopePrepend(scope, string(names.String), values.Clone())
if values != nil {
values = values.Next
}
}
return true
}
func fnSet(ell *Ell, args *V, result **V) bool {
name := args
if name == nil || name.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
v := name.Next
if v != nil {
*result = v.Clone()
ell.Set(string(name.String), v)
return true
}
// We return an empty list for a nil value.
if v = ell.Get(string(name.String)); v != nil {
*result = v.Clone()
} else {
*result = NewList(nil)
}
return true
}
func fnList(ell *Ell, args *V, result **V) bool {
*result = NewList(args.CloneSeq())
return true
}
func fnValues(ell *Ell, args *V, result **V) bool {
*result = args.CloneSeq()
return true
}
func fnIf(ell *Ell, args *V, result **V) bool {
var cond, body, keyword *V
for cond = args; ; cond = keyword.Next {
if cond == nil {
return ell.Errorf("missing condition")
}
if body = cond.Next; body == nil {
return ell.Errorf("missing body")
}
var res *V
if !EvalAny(ell, cond, nil, &res) {
return false
}
if Truthy(res) {
return EvalAny(ell, body, nil, result)
}
if keyword = body.Next; keyword == nil {
break
}
if keyword.Type != VTypeString {
return ell.Errorf("expected keyword, got list")
}
switch kw := string(keyword.String); kw {
case "else":
if body = keyword.Next; body == nil {
return ell.Errorf("missing body")
}
return EvalAny(ell, body, nil, result)
case "elif":
default:
return ell.Errorf("invalid keyword: %s", kw)
}
}
return true
}
func fnMap(ell *Ell, args *V, result **V) bool {
var body, values *V
if body = args; body == nil {
return ell.Errorf("first argument must be a function")
}
if values = body.Next; values == nil || values.Type != VTypeList {
return ell.Errorf("second argument must be a list")
}
var res *V
out := &res
for v := values.Head; v != nil; v = v.Next {
if !EvalAny(ell, body, v, out) {
return false
}
for *out != nil {
out = &(*out).Next
}
}
*result = NewList(res)
return true
}
func fnPrint(ell *Ell, args *V, result **V) bool {
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
PrintV(os.Stdout, args)
} else if _, err := os.Stdout.Write(args.String); err != nil {
return ell.Errorf("write failed: %s", err)
}
}
return true
}
func fnCat(ell *Ell, args *V, result **V) bool {
buf := bytes.NewBuffer(nil)
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
PrintV(buf, args)
} else {
buf.Write(args.String)
}
}
*result = NewString(buf.Bytes())
return true
}
func fnSystem(ell *Ell, args *V, result **V) bool {
var argv []string
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
argv = append(argv, string(args.String))
}
if len(argv) == 0 {
return ell.Errorf("command name required")
}
cmd := exec.Command(argv[0], argv[1:]...)
// Approximation of system(3) return value to match C ell at least a bit.
if err := cmd.Run(); err == nil {
*result = NewNumber(0)
} else if _, ok := err.(*exec.Error); ok {
return ell.Errorf("%s", err)
} else {
*result = NewNumber(1)
}
return true
}
func fnParse(ell *Ell, args *V, result **V) bool {
body := args
if body == nil || body.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
res, err := NewParser(body.String).Run()
if err != nil {
return ell.Errorf("%s", err)
}
*result = NewList(res)
return true
}
func fnTry(ell *Ell, args *V, result **V) bool {
var body, handler *V
if body = args; body == nil {
return ell.Errorf("first argument must be a function")
}
if handler = body.Next; handler == nil {
return ell.Errorf("second argument must be a function")
}
if EvalAny(ell, body, nil, result) {
return true
}
msg := NewString([]byte(ell.Error))
ell.Error = ""
*result = nil
return EvalAny(ell, handler, msg, result)
}
func fnThrow(ell *Ell, args *V, result **V) bool {
message := args
if message == nil || message.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
return ell.Errorf("%s", message.String)
}
func fnPlus(ell *Ell, args *V, result **V) bool {
res := 0.
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
var arg float64
if n, _ := fmt.Sscan(string(args.String), &arg); n < 1 {
return ell.Errorf("invalid number: %s", args.String)
}
res += arg
}
*result = NewNumber(res)
return true
}
func fnMinus(ell *Ell, args *V, result **V) bool {
if args == nil || args.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
var res float64
if n, _ := fmt.Sscan(string(args.String), &res); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
if args = args.Next; args == nil {
res = -res
}
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
var arg float64
if n, _ := fmt.Sscan(string(args.String), &arg); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
res -= arg
}
*result = NewNumber(res)
return true
}
func fnMultiply(ell *Ell, args *V, result **V) bool {
res := 1.
for ; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
var arg float64
if n, _ := fmt.Sscan(string(args.String), &arg); n < 1 {
return ell.Errorf("invalid number: %s", args.String)
}
res *= arg
}
*result = NewNumber(res)
return true
}
func fnDivide(ell *Ell, args *V, result **V) bool {
if args == nil || args.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
var res float64
if n, _ := fmt.Sscan(string(args.String), &res); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
for args = args.Next; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
var arg float64
if n, _ := fmt.Sscan(string(args.String), &arg); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
res /= arg
}
*result = NewNumber(res)
return true
}
func fnNot(ell *Ell, args *V, result **V) bool {
if args == nil {
return ell.Errorf("missing argument")
}
*result = NewBoolean(!Truthy(args))
return true
}
func fnAnd(ell *Ell, args *V, result **V) bool {
if args == nil {
*result = NewBoolean(true)
return true
}
for ; args != nil; args = args.Next {
*result = nil
if !EvalAny(ell, args, nil, result) {
return false
}
if !Truthy(*result) {
*result = NewBoolean(false)
return true
}
}
return true
}
func fnOr(ell *Ell, args *V, result **V) bool {
for ; args != nil; args = args.Next {
if !EvalAny(ell, args, nil, result) {
return false
}
if Truthy(*result) {
return true
}
*result = nil
}
*result = NewBoolean(false)
return true
}
func fnEq(ell *Ell, args *V, result **V) bool {
etalon := args
if etalon == nil || etalon.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
res := true
for args = etalon.Next; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
if res = string(etalon.String) == string(args.String); !res {
break
}
}
*result = NewBoolean(res)
return true
}
func fnLt(ell *Ell, args *V, result **V) bool {
etalon := args
if etalon == nil || etalon.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
res := true
for args = etalon.Next; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
if res = string(etalon.String) < string(args.String); !res {
break
}
etalon = args
}
*result = NewBoolean(res)
return true
}
func fnEquals(ell *Ell, args *V, result **V) bool {
etalon := args
if etalon == nil || etalon.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
var first, second float64
if n, _ := fmt.Sscan(string(etalon.String), &first); n < 1 {
return ell.Errorf("invalid number: %f", etalon.String)
}
res := true
for args = etalon.Next; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
if n, _ := fmt.Sscan(string(args.String), &second); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
if res = first == second; !res {
break
}
first = second
}
*result = NewBoolean(res)
return true
}
func fnLess(ell *Ell, args *V, result **V) bool {
etalon := args
if etalon == nil || etalon.Type != VTypeString {
return ell.Errorf("first argument must be string")
}
var first, second float64
if n, _ := fmt.Sscan(string(etalon.String), &first); n < 1 {
return ell.Errorf("invalid number: %f", etalon.String)
}
res := true
for args = etalon.Next; args != nil; args = args.Next {
if args.Type != VTypeString {
return ell.Errorf("arguments must be strings")
}
if n, _ := fmt.Sscan(string(args.String), &second); n < 1 {
return ell.Errorf("invalid number: %f", args.String)
}
if res = first < second; !res {
break
}
first = second
}
*result = NewBoolean(res)
return true
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var stdNative = map[string]Handler{
"local": fnLocal,
"set": fnSet,
"list": fnList,
"values": fnValues,
"if": fnIf,
"map": fnMap,
"print": fnPrint,
"..": fnCat,
"system": fnSystem,
"parse": fnParse,
"try": fnTry,
"throw": fnThrow,
"+": fnPlus,
"-": fnMinus,
"*": fnMultiply,
"/": fnDivide,
"not": fnNot,
"and": fnAnd,
"or": fnOr,
"eq?": fnEq,
"lt?": fnLt,
"=": fnEquals,
"<": fnLess,
}
var stdComposed = `
set unless { if (not (@1)) @2 }
set filter { local [_body _list] @1 @2;
map { if (@_body @1) { @1 } } @_list }
set for { local [_list _body] @1 @2;
try { map { @_body @1 } @_list } { if (ne? @1 _break) { throw @1 } } }
set break { throw _break }
# TODO: we should be able to apply them to all arguments
set ne? { not (eq? @1 @2) }; set le? { ge? @2 @1 }
set ge? { not (lt? @1 @2) }; set gt? { lt? @2 @1 }
set <> { not (= @1 @2) }; set <= { >= @2 @1 }
set >= { not (< @1 @2) }; set > { < @2 @1 }`
// StdInitialize initializes the ell standard library.
func StdInitialize(ell *Ell) bool {
for name, handler := range stdNative {
ell.NativeRegister(name, handler)
}
p := NewParser([]byte(stdComposed))
program, err := p.Run()
if err != nil {
return false
}
var result *V
return ell.EvalBlock(program, nil, &result)
}