diff --git a/ucl/ast.go b/ucl/ast.go
index fd4252d..43815d8 100644
--- a/ucl/ast.go
+++ b/ucl/ast.go
@@ -68,6 +68,7 @@ type astDot struct {
 }
 
 type astCmd struct {
+	Pos  lexer.Position
 	Name astDot   `parser:"@@"`
 	Args []astDot `parser:"@@*"`
 }
diff --git a/ucl/builtins.go b/ucl/builtins.go
index 83496da..89a26ac 100644
--- a/ucl/builtins.go
+++ b/ucl/builtins.go
@@ -263,6 +263,40 @@ func geBuiltin(ctx context.Context, args invocationArgs) (object, error) {
 	return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
 }
 
+func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
+	if err := args.expectArgn(2); err != nil {
+		return nil, err
+	}
+
+	for _, a := range args.args {
+		if a == nil || !a.Truthy() {
+			return boolObject(false), nil
+		}
+	}
+	return args.args[len(args.args)-1], nil
+}
+
+func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
+	if err := args.expectArgn(2); err != nil {
+		return nil, err
+	}
+
+	for _, a := range args.args {
+		if a != nil && a.Truthy() {
+			return a, nil
+		}
+	}
+	return boolObject(false), nil
+}
+
+func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
+	if err := args.expectArgn(1); err != nil {
+		return nil, err
+	}
+
+	return boolObject(!args.args[0].Truthy()), nil
+}
+
 var errObjectsNotEqual = errors.New("objects not equal")
 
 func objectsEqual(l, r object) bool {
@@ -319,13 +353,6 @@ func objectsEqual(l, r object) bool {
 			return false
 		}
 		return true
-	case OpaqueObject:
-		rv, ok := r.(OpaqueObject)
-		if !ok {
-			return false
-		}
-
-		return lv.v == rv.v
 	}
 	return false
 }
diff --git a/ucl/errors.go b/ucl/errors.go
new file mode 100644
index 0000000..6e0adb7
--- /dev/null
+++ b/ucl/errors.go
@@ -0,0 +1,37 @@
+package ucl
+
+import (
+	"fmt"
+	"github.com/alecthomas/participle/v2/lexer"
+)
+
+var (
+	tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
+)
+
+type errorWithPos struct {
+	err error
+	pos lexer.Position
+}
+
+func (e errorWithPos) Error() string {
+	return fmt.Sprintf("%v:%v - %v", e.pos.Line, e.pos.Offset, e.err.Error())
+}
+
+func (e errorWithPos) Unwrap() error {
+	return e.err
+}
+
+type errBadUsage struct {
+	msg string
+}
+
+func newBadUsage(msg string) func(pos lexer.Position) error {
+	return func(pos lexer.Position) error {
+		return errorWithPos{err: errBadUsage{msg: msg}, pos: pos}
+	}
+}
+
+func (e errBadUsage) Error() string {
+	return "bad usage: " + e.msg
+}
diff --git a/ucl/objs.go b/ucl/objs.go
index 3530ea3..8e18095 100644
--- a/ucl/objs.go
+++ b/ucl/objs.go
@@ -283,6 +283,16 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
 	return nil, errors.New("expected an invokable arg")
 }
 
+type errObject struct{ err error }
+
+func (eo errObject) String() string {
+	return "error:" + eo.err.Error()
+}
+
+func (eo errObject) Truthy() bool {
+	return true
+}
+
 type invocationArgs struct {
 	eval   evaluator
 	inst   *Inst