func builtinMath(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.NumberValue, runtime.NumberValue); err != nil { return nil, err } base := big.NewRat(0, 1) var callback func(*big.Rat, *big.Rat) *big.Rat switch context.Name { case "+": callback = base.Add case "-": callback = base.Sub case "*": callback = base.Mul case "/": if context.Args[1].Number.Cmp(base) == 0 { return nil, runtime.NewRuntimeError(context.Pos, "division by zero") } callback = base.Quo } return runtime.NewNumberValueFromRat(callback(context.Args[0].Number, context.Args[1].Number)), nil }
func listMap(context *runtime.MacroCallContext) (*runtime.Value, error) { list, err := context.Block.EvalNode(context.Nodes[0]) if err != nil { return nil, err } if list.Type != runtime.ListValue { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "expected a list") } ident := context.Nodes[1].(*parser.IdentifierNode).Token.Data callback := context.Nodes[2] mappedList := runtime.NewListValue() for _, item := range list.List { b := runtime.NewBlock([]parser.Node{callback}, runtime.NewScope(context.Block.Scope)) b.Scope.SetSymbolLocally(ident, runtime.NewSymbol(item)) result, err := b.Eval() if err != nil { return nil, err } mappedList.List = append(mappedList.List, result) } return mappedList, nil }
func stringsCharacterCheck(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.StringValue); err != nil { return nil, err } s := context.Args[0].Str if len(s) != 1 { return nil, runtime.NewRuntimeError(context.Pos, "expected string that has 1 character, got %d character(s)", len(s)) } var callback func(rune) bool switch context.Name { case "is-digit": callback = unicode.IsDigit case "is-letter": callback = unicode.IsLetter case "is-lower": callback = unicode.IsLower case "is-upper": callback = unicode.IsUpper } return runtime.BooleanValueFor(callback(rune(s[0]))), nil }
func builtinFor(context *runtime.MacroCallContext) (*runtime.Value, error) { l, err := context.Block.EvalNode(context.Nodes[0]) if err != nil { return nil, err } if l.Type != runtime.ListValue { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "expected a list to iterate over") } var args []string for _, nameNode := range context.Nodes[1].(*parser.ListNode).Nodes { ident, isIdent := nameNode.(*parser.IdentifierNode) if isIdent { args = append(args, ident.Token.Data) } else { return nil, runtime.NewRuntimeError(nameNode.Pos(), "expected an identifier") } } if len(args) > 2 { return nil, runtime.NewRuntimeError(context.Nodes[1].Pos(), "too many arguments provided") } callbackBlock := runtime.NewBlock([]parser.Node{context.Nodes[2]}, runtime.NewScope(context.Block.Scope)) for i, item := range l.List { if len(args) >= 1 { callbackBlock.Scope.SetSymbol(args[0], runtime.NewSymbol(item)) } if len(args) == 2 { callbackBlock.Scope.SetSymbol(args[1], runtime.NewSymbol(runtime.NewNumberValueFromInt64(int64(i)))) } _, err := callbackBlock.Eval() if err != nil { return nil, err } } return runtime.Nil, nil }
func builtinCall(context *runtime.FunctionCallContext) (*runtime.Value, error) { if len(context.Args) < 1 || context.Args[0].Type != runtime.FunctionValue { return nil, runtime.NewRuntimeError(context.Pos, "expected a function") } function := context.Args[0].Function return function.Call(context.Block, context.Args[1:], context.Pos) }
func builtinDefmacro(context *runtime.MacroCallContext) (*runtime.Value, error) { name := context.Nodes[0].(*parser.IdentifierNode).Token.Data if name == "_" { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "disallowed macro name") } if context.Block.Scope.GetSymbol(name) != nil && context.Block.Scope.GetSymbol(name).Const { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "%s is a constant and cannot be modified", name) } argNodes := context.Nodes[1].(*parser.ListNode) var args []string callback := context.Nodes[2].(*parser.ListNode) if len(callback.Nodes) == 0 { return nil, runtime.NewRuntimeError(callback.Pos(), "empty macro body") } for _, argNode := range argNodes.Nodes { ident, ok := argNode.(*parser.IdentifierNode) if !ok { return nil, runtime.NewRuntimeError(argNode.Pos(), "expected an identifier") } args = append(args, ident.Token.Data) } macro := runtime.NewMacro(func(handlerContext *runtime.MacroCallContext) (*runtime.Value, error) { block := runtime.NewBlock([]parser.Node{callback}, runtime.NewScope(handlerContext.Block.Scope)) for i, arg := range args { block.Scope.SetSymbolLocally(arg, runtime.NewSymbol(runtime.NewQuotedValue(handlerContext.Nodes[i]))) } return block.Eval() }, false) context.Block.Scope.SetMacro(name, macro) return runtime.Nil, nil }
func builtinExport(context *runtime.MacroCallContext) (*runtime.Value, error) { for _, node := range context.Nodes { ident, isIdent := node.(*parser.IdentifierNode) if !isIdent { return nil, runtime.NewRuntimeError(node.Pos(), "expected an identifier") } else { name := ident.Token.Data if !context.Block.Scope.HasSymbol(name) { return nil, runtime.NewRuntimeError(node.Pos(), "unknown symbol '%s'", name) } context.Block.Scope.GetSymbol(name).Exported = true } } return runtime.Nil, nil }
func listReduce(context *runtime.MacroCallContext) (*runtime.Value, error) { list, err := context.Block.EvalNode(context.Nodes[0]) if err != nil { return nil, err } if list.Type != runtime.ListValue { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "expected a list") } if len(list.List) == 0 { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "empty list") } identLeft := context.Nodes[1].(*parser.IdentifierNode).Token.Data identRight := context.Nodes[2].(*parser.IdentifierNode).Token.Data callback := context.Nodes[3] reduced := list.List[0] for _, item := range list.List[1:] { b := runtime.NewBlock([]parser.Node{callback}, runtime.NewScope(context.Block.Scope)) b.Scope.SetSymbolLocally(identLeft, runtime.NewSymbol(reduced)) b.Scope.SetSymbolLocally(identRight, runtime.NewSymbol(item)) result, err := b.Eval() if err != nil { return nil, err } reduced = result } return reduced, nil }
func stringsFormat(context *runtime.FunctionCallContext) (*runtime.Value, error) { if len(context.Args) < 1 { return nil, runtime.NewRuntimeError(context.Pos, "missing format specifier") } if context.Args[0].Type != runtime.StringValue { return nil, runtime.NewRuntimeError(context.Pos, "format specifier should be a string") } format := context.Args[0].Str args := strings.Count(format, "~") if len(context.Args)-1 != args { return nil, runtime.NewRuntimeError(context.Pos, "format specifier expected %d arguments, got %d", args, len(context.Args)-1) } for _, item := range context.Args[1:] { format = strings.Replace(format, "~", item.String(), 1) } return runtime.NewStringValue(format), nil }
func builtinDef(context *runtime.MacroCallContext) (*runtime.Value, error) { name := context.Nodes[0].(*parser.IdentifierNode).Token.Data value, err := context.Block.EvalNode(context.Nodes[1]) if name == "_" { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "disallowed symbol name") } if err != nil { return nil, err } if context.Block.Scope.GetSymbol(name) != nil && context.Block.Scope.GetSymbol(name).Const { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "%s is a constant and cannot be modified", name) } sym := runtime.NewSymbol(value) sym.Const = context.Name == "defconst" context.Block.Scope.SetSymbol(name, sym) return value, nil }
func builtinFun(context *runtime.MacroCallContext) (*runtime.Value, error) { argNodes := context.Nodes[0].(*parser.ListNode) var args []string callback := context.Nodes[1].(*parser.ListNode) if len(callback.Nodes) == 0 { return nil, runtime.NewRuntimeError(callback.Pos(), "empty function body") } for _, argNode := range argNodes.Nodes { ident, ok := argNode.(*parser.IdentifierNode) if !ok { return nil, runtime.NewRuntimeError(argNode.Pos(), "expected an identifier") } args = append(args, ident.Token.Data) } function := runtime.NewLambdaFunction([]parser.Node{callback}, args) return runtime.NewFunctionValue(function), nil }
func listGet(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.ListValue, runtime.NumberValue); err != nil { return nil, err } size := int64(len(context.Args[0].List)) index := context.Args[1].NumberToInt64() if index < 0 || index > size-1 { return nil, runtime.NewRuntimeError(context.Pos, "index %d out of bounds (list size is %d)", index, size) } return context.Args[0].List[index], nil }
func builtinDefun(context *runtime.MacroCallContext) (*runtime.Value, error) { name := context.Nodes[0].(*parser.IdentifierNode).Token.Data if name == "_" { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "disallowed function name") } if context.Block.Scope.GetSymbol(name) != nil && context.Block.Scope.GetSymbol(name).Const { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "%s is a constant and cannot be modified", name) } argNodes := context.Nodes[1].(*parser.ListNode) var args []string callback := context.Nodes[2].(*parser.ListNode) if len(callback.Nodes) == 0 { return nil, runtime.NewRuntimeError(callback.Pos(), "empty function body") } for _, argNode := range argNodes.Nodes { ident, ok := argNode.(*parser.IdentifierNode) if !ok { return nil, runtime.NewRuntimeError(argNode.Pos(), "expected an identifier") } args = append(args, ident.Token.Data) } function := runtime.NewDeclaredFunction([]parser.Node{callback}, name, args) functionValue := runtime.NewFunctionValue(function) context.Block.Scope.SetSymbol(name, runtime.NewSymbol(functionValue)) return functionValue, nil }
func listDropLeft(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.ListValue); err != nil { return nil, err } l := context.Args[0] if len(l.List) == 0 { return nil, runtime.NewRuntimeError(context.Pos, "empty list") } l.List = l.List[1:] return context.Args[0], nil }
func builtinIfel(context *runtime.MacroCallContext) (*runtime.Value, error) { conditionNode := context.Nodes[0] condition, err := context.Block.EvalNode(conditionNode) if err != nil { return nil, err } if condition.Type != runtime.BooleanValue { return nil, runtime.NewRuntimeError(conditionNode.Pos(), "expected a boolean") } if condition.Boolean == true { return context.Block.EvalNode(context.Nodes[1]) } else { return context.Block.EvalNode(context.Nodes[2]) } }
func builtinQuoted2List(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.QuotedValue); err != nil { return nil, err } quoted := context.Args[0].Quoted if list, isList := quoted.(*parser.ListNode); isList { l := runtime.NewListValue() for _, listNode := range list.Nodes { l.List = append(l.List, runtime.NewQuotedValue(listNode)) } return l, nil } else { return nil, runtime.NewRuntimeError(context.Pos, "expected a quoted list, not %s", quoted.Name()) } }
func stringsRange(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.StringValue, runtime.NumberValue, runtime.NumberValue); err != nil { return nil, err } source := context.Args[0].Str start := context.Args[1].NumberToInt64() end := context.Args[2].NumberToInt64() sourceLen := int64(len(source)) if start < 0 || start > sourceLen || end < 0 || end > sourceLen { return nil, runtime.NewRuntimeError(context.Pos, "out of bounds (length is %d, trying to access %d:%d)", sourceLen, start, end) } result := source[start:end] return runtime.NewStringValue(result), nil }
func listSeq(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.NumberValue, runtime.NumberValue); err != nil { return nil, err } low := context.Args[0].NumberToInt64() high := context.Args[1].NumberToInt64() if low > high { return nil, runtime.NewRuntimeError(context.Pos, "invalid argument(s), low can't be higher than high (%d > %d)", low, high) } l := runtime.NewListValue() for i := low; i <= high; i++ { l.List = append(l.List, runtime.NewNumberValueFromInt64(i)) } return l, nil }
func builtinAssert(context *runtime.FunctionCallContext) (*runtime.Value, error) { optionalError := runtime.ValidateArguments(context, runtime.BooleanValue) if optionalError != nil { err := runtime.ValidateArguments(context, runtime.BooleanValue, runtime.StringValue) if err != nil { return nil, err } } if !context.Args[0].Boolean { message := "assertion failed" if len(context.Args) == 2 { message += ": " + context.Args[1].Str } return nil, runtime.NewRuntimeError(context.Pos, message) } return runtime.Nil, nil }
func listRemove(context *runtime.FunctionCallContext) (*runtime.Value, error) { if err := runtime.ValidateArguments(context, runtime.ListValue, runtime.NumberValue); err != nil { return nil, err } list := context.Args[0].List size := int64(len(list)) index := context.Args[1].NumberToInt64() if index < 0 || index > size-1 { return nil, runtime.NewRuntimeError(context.Pos, "index %d out of bounds (list size is %d)", index, size) } newList := runtime.NewListValue() for i, item := range list { if int64(i) != index { newList.List = append(newList.List, item) } } return newList, nil }
func listRange(context *runtime.FunctionCallContext) (*runtime.Value, error) { var begin, end int64 err := runtime.ValidateArguments(context, runtime.ListValue, runtime.NumberValue, runtime.NumberValue) if err != nil { optionalErr := runtime.ValidateArguments(context, runtime.ListValue, runtime.NumberValue) if optionalErr == nil { begin = context.Args[1].NumberToInt64() end = int64(len(context.Args[0].List)) - 1 } else { return nil, optionalErr } } else { begin = context.Args[1].NumberToInt64() end = context.Args[2].NumberToInt64() } length := int64(len(context.Args[0].List)) if begin < 0 || begin > length-1 || begin > end || end < 0 || end > length-1 { return nil, runtime.NewRuntimeError(context.Pos, "invalid bounds %d and %d (list length is %d)", begin, end, length) } newList := runtime.NewListValue() for _, item := range context.Args[0].List[begin : end+1] { newList.List = append(newList.List, item) } if len(newList.List) == 1 { return newList.List[0], nil } else { return newList, nil } }
func builtinWhile(context *runtime.MacroCallContext) (*runtime.Value, error) { recheck: callback, err := context.Block.EvalNode(context.Nodes[0]) if err != nil { return nil, err } if callback.Type != runtime.BooleanValue { return nil, runtime.NewRuntimeError(context.Nodes[0].Pos(), "expected a boolean") } if callback.Boolean { _, err := context.Block.EvalNode(context.Nodes[1]) if err != nil { return nil, err } goto recheck } return runtime.Nil, nil }
func builtinCase(context *runtime.MacroCallContext) (*runtime.Value, error) { if len(context.Nodes) < 1 { return nil, runtime.NewRuntimeError(context.Pos, "missing value to compare to") } matchNode := context.Nodes[0] match, err := context.Block.EvalNode(matchNode) if err != nil { return nil, err } if ((len(context.Nodes) - 1) % 2) != 0 { // -1 because we can't count for the match node too return nil, runtime.NewRuntimeError(context.Pos, "unbalanced case call") } var elems []caseElement var otherwise parser.Node // we begin at 1 because we need to omit the match node for i := 1; i < len(context.Nodes); i++ { elem := caseElement{} list, isList := context.Nodes[i].(*parser.ListNode) if isList { for _, caseNode := range list.Nodes { result, err := context.Block.EvalNode(caseNode) if err != nil { return nil, err } elem.cases = append(elem.cases, result) } i++ elem.callback = context.Nodes[i] elems = append(elems, elem) } else { ident, isIdent := context.Nodes[i].(*parser.IdentifierNode) if isIdent && ident.Token.Data == "_" { if otherwise != nil { return nil, runtime.NewRuntimeError(ident.Pos(), "match can only have one otherwise case") } i++ otherwise = context.Nodes[i] } else { return nil, runtime.NewRuntimeError(context.Nodes[i].Pos(), "expected a list") } } } for _, elem := range elems { for _, possibility := range elem.cases { if possibility.Equals(match) { return context.Block.EvalNode(elem.callback) } } } if otherwise != nil { return context.Block.EvalNode(otherwise) } return runtime.Nil, nil }