From 3f21ecb4877b6618816d0290f49899bf3d71c3f2 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 26 Dec 2021 17:41:24 +0900 Subject: [PATCH 1/5] reduce cyclomatic complexity of builtins --- engine/builtin.go | 767 ++++++++++++++++++++-------------------------- engine/stream.go | 72 +++++ 2 files changed, 411 insertions(+), 428 deletions(-) diff --git a/engine/builtin.go b/engine/builtin.go index 190877b6..a98ffba2 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -370,23 +370,17 @@ func (state *State) Op(priority, specifier, operator Term, k func(*Env) *Promise if !ok { return Error(typeErrorAtom(specifier)) } - var spec OperatorSpecifier - switch s { - case "fx": - spec = OperatorSpecifierFX - case "fy": - spec = OperatorSpecifierFY - case "xf": - spec = OperatorSpecifierXF - case "yf": - spec = OperatorSpecifierYF - case "xfx": - spec = OperatorSpecifierXFX - case "xfy": - spec = OperatorSpecifierXFY - case "yfx": - spec = OperatorSpecifierYFX - default: + + spec, ok := map[Atom]OperatorSpecifier{ + "fx": OperatorSpecifierFX, + "fy": OperatorSpecifierFY, + "xf": OperatorSpecifierXF, + "yf": OperatorSpecifierYF, + "xfx": OperatorSpecifierXFX, + "xfy": OperatorSpecifierXFY, + "yfx": OperatorSpecifierYFX, + }[s] + if !ok { return Error(domainErrorOperatorSpecifier(s)) } @@ -444,10 +438,15 @@ func (state *State) CurrentOp(priority, specifier, operator Term, k func(*Env) * case Variable: break case Atom: - switch s { - case "xf", "yf", "xfx", "xfy", "yfx", "fx", "fy": - break - default: + if _, ok := map[Atom]struct{}{ + "xf": {}, + "yf": {}, + "xfx": {}, + "xfy": {}, + "yfx": {}, + "fx": {}, + "fy": {}, + }[s]; !ok { return Error(domainErrorOperatorSpecifier(s)) } default: @@ -904,14 +903,13 @@ func (state *State) Open(SourceSink, mode, stream, options Term, k func(*Env) *P case Variable: return Error(InstantiationError(mode)) case Atom: - switch m { - case "read": - streamMode = StreamModeRead - case "write": - streamMode = StreamModeWrite - case "append": - streamMode = StreamModeAppend - default: + var ok bool + streamMode, ok = map[Atom]StreamMode{ + "read": StreamModeRead, + "write": StreamModeWrite, + "append": StreamModeAppend, + }[m] + if !ok { return Error(domainErrorIOMode(m)) } default: @@ -924,91 +922,13 @@ func (state *State) Open(SourceSink, mode, stream, options Term, k func(*Env) *P var opts []StreamOption if err := EachList(env.Resolve(options), func(option Term) error { - switch o := env.Resolve(option).(type) { - case Variable: - return InstantiationError(option) - case *Compound: - if len(o.Args) != 1 { - return domainErrorStreamOption(option) - } - arg := o.Args[0] - switch o.Functor { - case "type": - switch t := env.Resolve(arg).(type) { - case Variable: - return InstantiationError(arg) - case Atom: - switch t { - case "text": - opts = append(opts, WithStreamType(StreamTypeText)) - return nil - case "binary": - opts = append(opts, WithStreamType(StreamTypeBinary)) - return nil - default: - return domainErrorStreamOption(option) - } - default: - return typeErrorAtom(arg) - } - case "reposition": - switch b := env.Resolve(arg).(type) { - case Variable: - return InstantiationError(arg) - case Atom: - switch b { - case "true": - opts = append(opts, WithReposition(true)) - return nil - case "false": - opts = append(opts, WithReposition(false)) - return nil - default: - return domainErrorStreamOption(option) - } - default: - return typeErrorAtom(arg) - } - case "alias": - switch a := env.Resolve(arg).(type) { - case Variable: - return InstantiationError(arg) - case Atom: - if _, ok := state.streams[a]; ok { - return PermissionError("open", "source_sink", option, "%s is already defined as an alias.", a) - } - opts = append(opts, WithAlias(state, a)) - return nil - default: - return domainErrorStreamOption(option) - } - case "eof_action": - switch a := env.Resolve(arg).(type) { - case Variable: - return InstantiationError(arg) - case Atom: - switch a { - case "error": - opts = append(opts, WithEOFAction(EOFActionError)) - return nil - case "eof_code": - opts = append(opts, WithEOFAction(EOFActionEOFCode)) - return nil - case "reset": - opts = append(opts, WithEOFAction(EOFActionReset)) - return nil - default: - return domainErrorStreamOption(option) - } - default: - return domainErrorStreamOption(option) - } - default: - return domainErrorStreamOption(option) - } - default: - return domainErrorStreamOption(option) + opt, err := streamOption(state, option, env) + if err != nil { + return err } + + opts = append(opts, opt) + return nil }, env); err != nil { return Error(err) } @@ -1023,6 +943,64 @@ func (state *State) Open(SourceSink, mode, stream, options Term, k func(*Env) *P }) } +func streamOption(state *State, option Term, env *Env) (StreamOption, error) { + type optionIndicator struct { + functor Atom + arg Atom + } + + var oi optionIndicator + switch o := env.Resolve(option).(type) { + case Variable: + return nil, InstantiationError(option) + case *Compound: + if len(o.Args) != 1 { + return nil, domainErrorStreamOption(option) + } + switch a := o.Args[0].(type) { + case Variable: + return nil, InstantiationError(a) + case Atom: + oi = optionIndicator{ + functor: o.Functor, + arg: a, + } + default: + return nil, typeErrorAtom(a) + } + default: + return nil, domainErrorStreamOption(option) + } + + // alias is a bit different. + if oi.functor == "alias" { + if _, ok := state.streams[oi.arg]; ok { + return nil, PermissionError("open", "source_sink", option, "%s is already defined as an alias.", oi.arg) + } + + return WithAlias(state, oi.arg), nil + } + + switch oi { + case optionIndicator{functor: "type", arg: "text"}: + return WithStreamType(StreamTypeText), nil + case optionIndicator{functor: "type", arg: "binary"}: + return WithStreamType(StreamTypeBinary), nil + case optionIndicator{functor: "reposition", arg: "true"}: + return WithReposition(true), nil + case optionIndicator{functor: "reposition", arg: "false"}: + return WithReposition(false), nil + case optionIndicator{functor: "eof_action", arg: "error"}: + return WithEOFAction(EOFActionError), nil + case optionIndicator{functor: "eof_action", arg: "eof_code"}: + return WithEOFAction(EOFActionEOFCode), nil + case optionIndicator{functor: "eof_action", arg: "reset"}: + return WithEOFAction(EOFActionReset), nil + default: + return nil, domainErrorStreamOption(option) + } +} + // Close closes a stream specified by streamOrAlias. func (state *State) Close(streamOrAlias, options Term, k func(*Env) *Promise, env *Env) *Promise { s, err := state.stream(streamOrAlias, env) @@ -1121,49 +1099,7 @@ func (state *State) WriteTerm(streamOrAlias, t, options Term, k func(*Env) *Prom Priority: 1200, } if err := EachList(env.Resolve(options), func(option Term) error { - switch option := env.Resolve(option).(type) { - case Variable: - return InstantiationError(option) - case *Compound: - if len(option.Args) != 1 { - return domainErrorWriteOption(option) - } - - var b bool - switch v := env.Resolve(option.Args[0]).(type) { - case Variable: - return InstantiationError(v) - case Atom: - switch v { - case "false": - b = false - case "true": - b = true - default: - return domainErrorWriteOption(option) - } - default: - return domainErrorWriteOption(option) - } - - switch option.Functor { - case "quoted": - opts.Quoted = b - case "ignore_ops": - if b { - opts.Ops = nil - } else { - opts.Ops = state.operators - } - case "numbervars": - opts.NumberVars = b - default: - return domainErrorWriteOption(option) - } - return nil - default: - return domainErrorWriteOption(option) - } + return writeTermOption(&opts, state, option, env) }, env); err != nil { return Error(err) } @@ -1175,6 +1111,52 @@ func (state *State) WriteTerm(streamOrAlias, t, options Term, k func(*Env) *Prom return k(env) } +func writeTermOption(opts *WriteTermOptions, state *State, option Term, env *Env) error { + type optionIndicator struct { + functor, arg Atom + } + + var oi optionIndicator + switch option := env.Resolve(option).(type) { + case Variable: + return InstantiationError(option) + case *Compound: + if len(option.Args) != 1 { + return domainErrorWriteOption(option) + } + + switch v := env.Resolve(option.Args[0]).(type) { + case Variable: + return InstantiationError(v) + case Atom: + oi = optionIndicator{functor: option.Functor, arg: v} + default: + return domainErrorWriteOption(option) + } + default: + return domainErrorWriteOption(option) + } + + switch oi { + case optionIndicator{functor: "quoted", arg: "true"}: + opts.Quoted = true + case optionIndicator{functor: "quoted", arg: "false"}: + opts.Quoted = false + case optionIndicator{functor: "ignore_ops", arg: "true"}: + opts.Ops = nil + case optionIndicator{functor: "ignore_ops", arg: "false"}: + opts.Ops = state.operators + case optionIndicator{functor: "numbervars", arg: "true"}: + opts.NumberVars = true + case optionIndicator{functor: "numbervars", arg: "false"}: + opts.NumberVars = false + default: + return domainErrorWriteOption(option) + } + + return nil +} + // CharCode converts a single-rune Atom char to an Integer code, or vice versa. func CharCode(char, code Term, k func(*Env) *Promise, env *Env) *Promise { switch ch := env.Resolve(char).(type) { @@ -1289,6 +1271,12 @@ func (state *State) PutCode(streamOrAlias, code Term, k func(*Env) *Promise, env } } +type readTermOptions struct { + singletons Term + variables Term + variableNames Term +} + // ReadTerm reads from the stream represented by streamOrAlias and unifies with stream. func (state *State) ReadTerm(streamOrAlias, out, options Term, k func(*Env) *Promise, env *Env) *Promise { s, err := state.stream(streamOrAlias, env) @@ -1304,35 +1292,13 @@ func (state *State) ReadTerm(streamOrAlias, out, options Term, k func(*Env) *Pro return Error(permissionErrorInputBinaryStream(streamOrAlias)) } - var opts struct { - singletons Term - variables Term - variableNames Term + opts := readTermOptions{ + singletons: NewVariable(), + variables: NewVariable(), + variableNames: NewVariable(), } if err := EachList(env.Resolve(options), func(option Term) error { - switch option := env.Resolve(option).(type) { - case Variable: - return InstantiationError(option) - case *Compound: - if len(option.Args) != 1 { - return domainErrorReadOption(option) - } - - v := env.Resolve(option.Args[0]) - switch option.Functor { - case "singletons": - opts.singletons = v - case "variables": - opts.variables = v - case "variable_names": - opts.variableNames = v - default: - return domainErrorReadOption(option) - } - return nil - default: - return domainErrorReadOption(option) - } + return readTermOption(&opts, option, env) }, env); err != nil { return Error(err) } @@ -1385,24 +1351,17 @@ func (state *State) ReadTerm(streamOrAlias, out, options Term, k func(*Env) *Pro }) } - var ok bool - if opts.singletons != nil { - env, ok = opts.singletons.Unify(List(singletons...), false, env) - if !ok { - return Bool(false) - } - } - if opts.variables != nil { - env, ok = opts.variables.Unify(List(variables...), false, env) - if !ok { - return Bool(false) - } - } - if opts.variableNames != nil { - env, ok = opts.variableNames.Unify(List(variableNames...), false, env) - if !ok { - return Bool(false) - } + env, ok := (&Compound{Args: []Term{ + opts.singletons, + opts.variables, + opts.variableNames, + }}).Unify(&Compound{Args: []Term{ + List(singletons...), + List(variables...), + List(variableNames...), + }}, false, env) + if !ok { + return Bool(false) } return Delay(func(context.Context) *Promise { @@ -1410,6 +1369,32 @@ func (state *State) ReadTerm(streamOrAlias, out, options Term, k func(*Env) *Pro }) } +func readTermOption(opts *readTermOptions, option Term, env *Env) error { + switch option := env.Resolve(option).(type) { + case Variable: + return InstantiationError(option) + case *Compound: + if len(option.Args) != 1 { + return domainErrorReadOption(option) + } + + v := env.Resolve(option.Args[0]) + switch option.Functor { + case "singletons": + opts.singletons = v + case "variables": + opts.variables = v + case "variable_names": + opts.variableNames = v + default: + return domainErrorReadOption(option) + } + return nil + default: + return domainErrorReadOption(option) + } +} + var readByte = (*bufio.Reader).ReadByte // GetByte reads a byte from the stream represented by streamOrAlias and unifies it with inByte. @@ -1782,37 +1767,16 @@ func SubAtom(atom, before, length, after, subAtom Term, k func(*Env) *Promise, e case Atom: rs := []rune(whole) - switch b := env.Resolve(before).(type) { - case Variable: - break - case Integer: - if b < 0 { - return Error(domainErrorNotLessThanZero(before)) - } - default: - return Error(typeErrorInteger(before)) + if err := checkPositiveInteger(before, env); err != nil { + return Error(err) } - switch l := env.Resolve(length).(type) { - case Variable: - break - case Integer: - if l < 0 { - return Error(domainErrorNotLessThanZero(length)) - } - default: - return Error(typeErrorInteger(length)) + if err := checkPositiveInteger(length, env); err != nil { + return Error(err) } - switch a := env.Resolve(after).(type) { - case Variable: - break - case Integer: - if a < 0 { - return Error(domainErrorNotLessThanZero(after)) - } - default: - return Error(typeErrorInteger(after)) + if err := checkPositiveInteger(after, env); err != nil { + return Error(err) } switch env.Resolve(subAtom).(type) { @@ -1839,6 +1803,20 @@ func SubAtom(atom, before, length, after, subAtom Term, k func(*Env) *Promise, e } } +func checkPositiveInteger(n Term, env *Env) error { + switch b := env.Resolve(n).(type) { + case Variable: + return nil + case Integer: + if b < 0 { + return domainErrorNotLessThanZero(n) + } + return nil + default: + return typeErrorInteger(n) + } +} + // AtomChars breaks down atom into list of characters and unifies with chars, or constructs an atom from a list of // characters chars and unifies it with atom. func AtomChars(atom, chars Term, k func(*Env) *Promise, env *Env) *Promise { @@ -2169,11 +2147,9 @@ func (fs FunctionSet) compare(lhs, rhs Term, k func(*Env) *Promise, pi func(Inte func (fs FunctionSet) eval(expression Term, env *Env) (_ Term, err error) { defer func() { if r := recover(); r != nil { - if e, ok := r.(error); ok { - if e.Error() == "runtime error: integer divide by zero" { - err = evaluationErrorZeroDivisor() - return - } + if e, _ := r.(error); e.Error() == "runtime error: integer divide by zero" { + err = evaluationErrorZeroDivisor() + return } panic(r) } @@ -2227,12 +2203,9 @@ func (fs FunctionSet) eval(expression Term, env *Env) (_ Term, err error) { return nil, err } return f(x, y, env) - default: - return nil, typeErrorEvaluable(t) } - default: - return nil, typeErrorEvaluable(t) } + return nil, typeErrorEvaluable(expression) } // DefaultFunctionSet is a FunctionSet with builtin functions. @@ -2414,120 +2387,72 @@ func (state *State) StreamProperty(streamOrAlias, property Term, k func(*Env) *P return Error(domainErrorStreamOrAlias(streamOrAlias)) } + if err := checkStreamProperty(property, env); err != nil { + return Error(err) + } + + var ks []func(context.Context) *Promise + for _, s := range streams { + properties, err := s.properties() + if err != nil { + return Error(err) + } + + for i := range properties { + p := properties[i] + ks = append(ks, func(context.Context) *Promise { + return Unify(property, p, k, env) + }) + } + } + return Delay(ks...) +} + +func checkStreamProperty(property Term, env *Env) error { switch p := env.Resolve(property).(type) { case Variable: - break + return nil case Atom: switch p { case "input", "output": - break + return nil default: - return Error(domainErrorStreamProperty(property)) + return domainErrorStreamProperty(property) } case *Compound: if len(p.Args) != 1 { - return Error(domainErrorStreamProperty(property)) + return domainErrorStreamProperty(property) } arg := p.Args[0] switch p.Functor { case "file_name", "mode", "alias", "end_of_stream", "eof_action", "reposition": - switch env.Resolve(arg).(type) { - case Variable, Atom: - break - default: - return Error(typeErrorAtom(arg)) - } + return checkAtom(arg, env) case "position": - if len(p.Args) != 1 { - return Error(domainErrorStreamProperty(property)) - } - switch env.Resolve(p.Args[0]).(type) { - case Variable, Integer: - break - default: - return Error(typeErrorAtom(arg)) - } + return checkInteger(arg, env) default: - return Error(domainErrorStreamProperty(property)) + return domainErrorStreamProperty(property) } default: - return Error(domainErrorStreamProperty(property)) + return domainErrorStreamProperty(property) } +} - var ks []func(context.Context) *Promise - for _, s := range streams { - var properties []Term - - switch s.mode { - case StreamModeRead: - properties = append(properties, &Compound{Functor: "mode", Args: []Term{Atom("read")}}) - case StreamModeWrite: - properties = append(properties, &Compound{Functor: "mode", Args: []Term{Atom("write")}}) - case StreamModeAppend: - properties = append(properties, &Compound{Functor: "mode", Args: []Term{Atom("append")}}) - } - - if s.alias != "" { - properties = append(properties, &Compound{Functor: "alias", Args: []Term{s.alias}}) - } - - switch s.eofAction { - case EOFActionError: - properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom("error")}}) - case EOFActionEOFCode: - properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom("eof_code")}}) - case EOFActionReset: - properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom("reset")}}) - } - - if f, ok := s.file.(*os.File); ok { - pos, err := f.Seek(0, 1) - if err != nil { - return Error(err) - } - pos -= int64(s.buf.Buffered()) - - fi, err := f.Stat() - if err != nil { - return Error(err) - } - - eos := "not" - switch { - case pos == fi.Size(): - eos = "at" - case pos > fi.Size(): - eos = "past" - } - - properties = append(properties, - &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, - &Compound{Functor: "position", Args: []Term{Integer(pos)}}, - &Compound{Functor: "end_of_stream", Args: []Term{Atom(eos)}}, - ) - } - - if s.reposition { - properties = append(properties, &Compound{Functor: "reposition", Args: []Term{Atom("true")}}) - } else { - properties = append(properties, &Compound{Functor: "reposition", Args: []Term{Atom("false")}}) - } - - switch s.streamType { - case StreamTypeText: - properties = append(properties, &Compound{Functor: "type", Args: []Term{Atom("text")}}) - case StreamTypeBinary: - properties = append(properties, &Compound{Functor: "type", Args: []Term{Atom("false")}}) - } +func checkAtom(t Term, env *Env) error { + switch env.Resolve(t).(type) { + case Variable, Atom: + return nil + default: + return typeErrorAtom(t) + } +} - for i := range properties { - p := properties[i] - ks = append(ks, func(context.Context) *Promise { - return Unify(property, p, k, env) - }) - } +func checkInteger(t Term, env *Env) error { + switch env.Resolve(t).(type) { + case Variable, Integer: + return nil + default: + return typeErrorAtom(t) } - return Delay(ks...) } var seek = io.Seeker.Seek @@ -2658,119 +2583,105 @@ func (state *State) SetPrologFlag(flag, value Term, k func(*Env) *Promise, env * case Variable: return Error(InstantiationError(flag)) case Atom: + var modify func(value Atom) error switch f { case "bounded", "max_integer", "min_integer", "integer_rounding_function", "max_arity": return Error(PermissionError("modify", "flag", f, "%s is not modifiable.", f)) case "char_conversion": - switch a := env.Resolve(value).(type) { - case Variable: - return Error(InstantiationError(value)) - case Atom: - switch a { - case "on": - state.charConvEnabled = true - return k(env) - case "off": - state.charConvEnabled = false - return k(env) - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{flag, value}, - })) - } + modify = state.modifyCharConversion case "debug": - switch a := env.Resolve(value).(type) { - case Variable: - return Error(InstantiationError(value)) - case Atom: - switch a { - case "on": - state.debug = true - return k(env) - case "off": - state.debug = false - return k(env) - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } + modify = state.modifyDebug case "unknown": - switch a := env.Resolve(value).(type) { - case Variable: - return Error(InstantiationError(value)) - case Atom: - switch a { - case "error": - state.unknown = unknownError - return k(env) - case "warning": - state.unknown = unknownWarning - return k(env) - case "fail": - state.unknown = unknownFail - return k(env) - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } + modify = state.modifyUnknown case "double_quotes": - switch a := env.Resolve(value).(type) { - case Variable: - return Error(InstantiationError(value)) - case Atom: - switch a { - case "codes": - state.doubleQuotes = DoubleQuotesCodes - return k(env) - case "chars": - state.doubleQuotes = DoubleQuotesChars - return k(env) - case "atom": - state.doubleQuotes = DoubleQuotesAtom - return k(env) - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } - default: - return Error(domainErrorFlagValue(&Compound{ - Functor: "+", - Args: []Term{f, a}, - })) - } + modify = state.modifyDoubleQuotes default: return Error(domainErrorPrologFlag(f)) } + + switch v := env.Resolve(value).(type) { + case Variable: + return Error(InstantiationError(value)) + case Atom: + if err := modify(v); err != nil { + return Error(err) + } + return k(env) + default: + return Error(domainErrorFlagValue(&Compound{ + Functor: "+", + Args: []Term{flag, value}, + })) + } default: return Error(typeErrorAtom(f)) } } +func (state *State) modifyCharConversion(value Atom) error { + switch value { + case "on": + state.charConvEnabled = true + case "off": + state.charConvEnabled = false + default: + return domainErrorFlagValue(&Compound{ + Functor: "+", + Args: []Term{Atom("char_conversion"), value}, + }) + } + return nil +} + +func (state *State) modifyDebug(value Atom) error { + switch value { + case "on": + state.debug = true + case "off": + state.debug = false + default: + return domainErrorFlagValue(&Compound{ + Functor: "+", + Args: []Term{Atom("debug"), value}, + }) + } + return nil +} + +func (state *State) modifyUnknown(value Atom) error { + switch value { + case "error": + state.unknown = unknownError + case "warning": + state.unknown = unknownWarning + case "fail": + state.unknown = unknownFail + default: + return domainErrorFlagValue(&Compound{ + Functor: "+", + Args: []Term{Atom("unknown"), value}, + }) + } + return nil +} + +func (state *State) modifyDoubleQuotes(value Atom) error { + switch value { + case "codes": + state.doubleQuotes = DoubleQuotesCodes + case "chars": + state.doubleQuotes = DoubleQuotesChars + case "atom": + state.doubleQuotes = DoubleQuotesAtom + default: + return domainErrorFlagValue(&Compound{ + Functor: "+", + Args: []Term{Atom("double_quotes"), value}, + }) + } + return nil +} + // CurrentPrologFlag succeeds iff flag is set to value. func (state *State) CurrentPrologFlag(flag, value Term, k func(*Env) *Promise, env *Env) *Promise { switch f := env.Resolve(flag).(type) { diff --git a/engine/stream.go b/engine/stream.go index dcdde245..b290b5ae 100644 --- a/engine/stream.go +++ b/engine/stream.go @@ -21,6 +21,14 @@ const ( StreamModeAppend = StreamMode(os.O_APPEND) | StreamModeWrite ) +func (m StreamMode) String() string { + return [...]string{ + StreamModeRead: "read", + StreamModeWrite: "write", + StreamModeAppend: "append", + }[m] +} + // EOFAction describes what happens when you reached to the end of the stream. type EOFAction int @@ -33,6 +41,14 @@ const ( EOFActionReset ) +func (a EOFAction) String() string { + return [...]string{ + EOFActionError: "error", + EOFActionEOFCode: "eof_code", + EOFActionReset: "reset", + }[a] +} + // StreamType describes what will be transferred in the stream, either text or binary. type StreamType int @@ -43,6 +59,13 @@ const ( StreamTypeBinary ) +func (t StreamType) String() string { + return [...]string{ + StreamTypeText: "text", + StreamTypeBinary: "false", + }[t] +} + // Stream is a prolog stream. type Stream struct { file io.ReadWriteCloser @@ -176,3 +199,52 @@ func (s *Stream) Compare(t Term, env *Env) int64 { return 1 } } + +func (s *Stream) properties() ([]Term, error) { + var properties []Term + + properties = append(properties, &Compound{Functor: "mode", Args: []Term{Atom(s.mode.String())}}) + + if s.alias != "" { + properties = append(properties, &Compound{Functor: "alias", Args: []Term{s.alias}}) + } + + properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom(s.eofAction.String())}}) + + if f, ok := s.file.(*os.File); ok { + pos, err := f.Seek(0, 1) + if err != nil { + return nil, err + } + pos -= int64(s.buf.Buffered()) + + fi, err := f.Stat() + if err != nil { + return nil, err + } + + eos := "not" + switch { + case pos == fi.Size(): + eos = "at" + case pos > fi.Size(): + eos = "past" + } + + properties = append(properties, + &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, + &Compound{Functor: "position", Args: []Term{Integer(pos)}}, + &Compound{Functor: "end_of_stream", Args: []Term{Atom(eos)}}, + ) + } + + if s.reposition { + properties = append(properties, &Compound{Functor: "reposition", Args: []Term{Atom("true")}}) + } else { + properties = append(properties, &Compound{Functor: "reposition", Args: []Term{Atom("false")}}) + } + + properties = append(properties, &Compound{Functor: "type", Args: []Term{Atom(s.streamType.String())}}) + + return properties, nil +} From 2f88195461ff7fda746265432368f88153f36be7 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 26 Dec 2021 18:38:04 +0900 Subject: [PATCH 2/5] add StreamProperty tests --- engine/builtin_test.go | 101 +++++++++++++++++++++++++++++++++++++++++ engine/stream.go | 6 ++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 974dfa8f..5c0f33df 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -6069,6 +6069,38 @@ func TestState_StreamProperty(t *testing.T) { assert.False(t, ok) }) + t.Run("reposition false", func(t *testing.T) { + expected := []Term{ + &Compound{Functor: "mode", Args: []Term{Atom("read")}}, + &Compound{Functor: "alias", Args: []Term{Atom("null")}}, + &Compound{Functor: "eof_action", Args: []Term{Atom("eof_code")}}, + &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, + &Compound{Functor: "position", Args: []Term{Integer(0)}}, + &Compound{Functor: "end_of_stream", Args: []Term{Atom("at")}}, + &Compound{Functor: "reposition", Args: []Term{Atom("false")}}, + &Compound{Functor: "type", Args: []Term{Atom("text")}}, + } + + s, err := Open(Atom(f.Name()), StreamModeRead) + s.reposition = false + assert.NoError(t, err) + s.alias = "null" + defer func() { + assert.NoError(t, s.Close()) + }() + + v := Variable("V") + c := 0 + var state State + ok, err := state.StreamProperty(s, v, func(env *Env) *Promise { + assert.Equal(t, expected[c], env.Resolve(v)) + c++ + return Bool(false) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.False(t, ok) + }) + t.Run("stream alias", func(t *testing.T) { expected := []Term{ &Compound{Functor: "mode", Args: []Term{Atom("write")}}, @@ -6140,6 +6172,75 @@ func TestState_StreamProperty(t *testing.T) { assert.Equal(t, existenceErrorStream(Atom("foo")), err) assert.False(t, ok) }) + + t.Run("seek failed", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + seek = func(s io.Seeker, offset int64, whence int) (int64, error) { + return 0, errors.New("failed") + } + defer func() { + seek = io.Seeker.Seek + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{ + Functor: "mode", + Args: []Term{Atom("read")}, + }, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("stat failed", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + fileStat = func(f *os.File) (os.FileInfo, error) { + return nil, errors.New("fialed") + } + defer func() { + fileStat = (*os.File).Stat + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{ + Functor: "mode", + Args: []Term{Atom("read")}, + }, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("end_of_stream past", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + seek = func(s io.Seeker, offset int64, whence int) (int64, error) { + return 1000, nil + } + defer func() { + seek = io.Seeker.Seek + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{ + Functor: "end_of_stream", + Args: []Term{Atom("past")}, + }, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) } func TestState_SetStreamPosition(t *testing.T) { diff --git a/engine/stream.go b/engine/stream.go index b290b5ae..d8113661 100644 --- a/engine/stream.go +++ b/engine/stream.go @@ -200,6 +200,8 @@ func (s *Stream) Compare(t Term, env *Env) int64 { } } +var fileStat = (*os.File).Stat + func (s *Stream) properties() ([]Term, error) { var properties []Term @@ -212,13 +214,13 @@ func (s *Stream) properties() ([]Term, error) { properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom(s.eofAction.String())}}) if f, ok := s.file.(*os.File); ok { - pos, err := f.Seek(0, 1) + pos, err := seek(f, 0, 1) if err != nil { return nil, err } pos -= int64(s.buf.Buffered()) - fi, err := f.Stat() + fi, err := fileStat(f) if err != nil { return nil, err } From 99df94250c33379e8bff34c34f652ed37452ff03 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 26 Dec 2021 20:53:57 +0900 Subject: [PATCH 3/5] add more StreamProperty tests --- engine/builtin_test.go | 136 +++++++++++++++++++++++++++++++++++++---- engine/stream.go | 7 +++ 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 5c0f33df..9307c90d 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -6041,6 +6041,7 @@ func TestState_StreamProperty(t *testing.T) { t.Run("stream", func(t *testing.T) { expected := []Term{ &Compound{Functor: "mode", Args: []Term{Atom("read")}}, + Atom("input"), &Compound{Functor: "alias", Args: []Term{Atom("null")}}, &Compound{Functor: "eof_action", Args: []Term{Atom("eof_code")}}, &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, @@ -6072,6 +6073,7 @@ func TestState_StreamProperty(t *testing.T) { t.Run("reposition false", func(t *testing.T) { expected := []Term{ &Compound{Functor: "mode", Args: []Term{Atom("read")}}, + Atom("input"), &Compound{Functor: "alias", Args: []Term{Atom("null")}}, &Compound{Functor: "eof_action", Args: []Term{Atom("eof_code")}}, &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, @@ -6104,6 +6106,7 @@ func TestState_StreamProperty(t *testing.T) { t.Run("stream alias", func(t *testing.T) { expected := []Term{ &Compound{Functor: "mode", Args: []Term{Atom("write")}}, + Atom("output"), &Compound{Functor: "alias", Args: []Term{Atom("null")}}, &Compound{Functor: "eof_action", Args: []Term{Atom("eof_code")}}, &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, @@ -6137,19 +6140,50 @@ func TestState_StreamProperty(t *testing.T) { }) t.Run("correct property value", func(t *testing.T) { - s, err := Open(Atom(f.Name()), StreamModeRead) - assert.NoError(t, err) - defer func() { - assert.NoError(t, s.Close()) - }() + t.Run("input", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() - var state State - ok, err := state.StreamProperty(s, &Compound{ - Functor: "mode", - Args: []Term{Atom("read")}, - }, Success, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) + var state State + ok, err := state.StreamProperty(s, Atom("input"), Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("mode", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{ + Functor: "mode", + Args: []Term{Atom("read")}, + }, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("position", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{ + Functor: "position", + Args: []Term{Integer(0)}, + }, Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) }) t.Run("streamOrAlias is neither a variable, a stream-term, nor an alias", func(t *testing.T) { @@ -6241,6 +6275,84 @@ func TestState_StreamProperty(t *testing.T) { assert.NoError(t, err) assert.True(t, ok) }) + + t.Run("unknown atom", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, Atom("foo"), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("unknown compound", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{Functor: "foo", Args: []Term{NewVariable()}}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("wrong arity", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{Functor: "mode", Args: []Term{NewVariable(), NewVariable()}}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("integer", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, Integer(0), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("non-atom for atom property", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{Functor: "mode", Args: []Term{Integer(0)}}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("non-integer for integer property", func(t *testing.T) { + s, err := Open(Atom(f.Name()), StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.StreamProperty(s, &Compound{Functor: "position", Args: []Term{Atom("foo")}}, Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) } func TestState_SetStreamPosition(t *testing.T) { diff --git a/engine/stream.go b/engine/stream.go index d8113661..36e7dabf 100644 --- a/engine/stream.go +++ b/engine/stream.go @@ -207,6 +207,13 @@ func (s *Stream) properties() ([]Term, error) { properties = append(properties, &Compound{Functor: "mode", Args: []Term{Atom(s.mode.String())}}) + switch s.mode { + case StreamModeRead: + properties = append(properties, Atom("input")) + case StreamModeWrite, StreamModeAppend: + properties = append(properties, Atom("output")) + } + if s.alias != "" { properties = append(properties, &Compound{Functor: "alias", Args: []Term{s.alias}}) } From 1108da071f26a752768d6a0b3812c066a6254f63 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Mon, 27 Dec 2021 19:44:49 +0900 Subject: [PATCH 4/5] add some more tests --- engine/builtin.go | 2 +- engine/builtin_test.go | 204 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 187 insertions(+), 19 deletions(-) diff --git a/engine/builtin.go b/engine/builtin.go index a98ffba2..868674a5 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -957,7 +957,7 @@ func streamOption(state *State, option Term, env *Env) (StreamOption, error) { if len(o.Args) != 1 { return nil, domainErrorStreamOption(option) } - switch a := o.Args[0].(type) { + switch a := env.Resolve(o.Args[0]).(type) { case Variable: return nil, InstantiationError(a) case Atom: diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 9307c90d..6fcf0afd 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -2713,27 +2713,199 @@ func TestState_Open(t *testing.T) { assert.NoError(t, f.Close()) - v := Variable("Stream") + t.Run("alias", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "alias", + Args: []Term{Atom("input")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) - ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ - Functor: "alias", - Args: []Term{Atom("input")}, - }), func(env *Env) *Promise { - ref, ok := env.Lookup(v) + assert.Equal(t, state.streams[Atom("input")], s) + + b, err := ioutil.ReadAll(s.buf) + assert.NoError(t, err) + assert.Equal(t, "test\n", string(b)) + + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) assert.True(t, ok) - s, ok := ref.(*Stream) + }) + + t.Run("type text", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "type", + Args: []Term{Atom("text")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.Equal(t, StreamTypeText, s.streamType) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) assert.True(t, ok) + }) - assert.Equal(t, state.streams[Atom("input")], s) + t.Run("type binary", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "type", + Args: []Term{Atom("binary")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.Equal(t, StreamTypeBinary, s.streamType) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) - b, err := ioutil.ReadAll(s.buf) + t.Run("reposition true", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "reposition", + Args: []Term{Atom("true")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.True(t, s.reposition) + return Bool(true) + }, nil).Force(context.Background()) assert.NoError(t, err) - assert.Equal(t, "test\n", string(b)) + assert.True(t, ok) + }) - return Bool(true) - }, nil).Force(context.Background()) - assert.NoError(t, err) - assert.True(t, ok) + t.Run("reposition true", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "reposition", + Args: []Term{Atom("false")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.False(t, s.reposition) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("eof_action error", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "eof_action", + Args: []Term{Atom("error")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.Equal(t, EOFActionError, s.eofAction) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("eof_action eof_code", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "eof_action", + Args: []Term{Atom("eof_code")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.Equal(t, EOFActionEOFCode, s.eofAction) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("eof_action reset", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "eof_action", + Args: []Term{Atom("reset")}, + }), func(env *Env) *Promise { + ref, ok := env.Lookup(v) + assert.True(t, ok) + s, ok := ref.(*Stream) + assert.True(t, ok) + assert.Equal(t, EOFActionReset, s.eofAction) + return Bool(true) + }, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + }) + + t.Run("unknown option", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "unknown", + Args: []Term{Atom("option")}, + }), func(env *Env) *Promise { + assert.Fail(t, "unreachable") + return Bool(true) + }, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("wrong arity", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "type", + Args: []Term{Atom("a"), Atom("b")}, + }), func(env *Env) *Promise { + assert.Fail(t, "unreachable") + return Bool(true) + }, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("variable arg", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "type", + Args: []Term{NewVariable()}, + }), func(env *Env) *Promise { + assert.Fail(t, "unreachable") + return Bool(true) + }, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + + t.Run("non-atom arg", func(t *testing.T) { + v := Variable("Stream") + ok, err := state.Open(Atom(f.Name()), Atom("read"), v, List(&Compound{ + Functor: "type", + Args: []Term{Integer(0)}, + }), func(env *Env) *Promise { + assert.Fail(t, "unreachable") + return Bool(true) + }, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) }) t.Run("write", func(t *testing.T) { @@ -2953,10 +3125,6 @@ func TestState_Open(t *testing.T) { }, "foo is already defined as an alias."), err) assert.False(t, ok) }) - - t.Run("an element E of the options list is reposition(true) and it is not possible to reposition", func(t *testing.T) { - // TODO: - }) } func TestState_Close(t *testing.T) { From c47ebacf1b8d170b1ec593e5d84b5e229b8e6fe1 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Mon, 27 Dec 2021 20:04:47 +0900 Subject: [PATCH 5/5] add SetPrologFlag tests --- engine/builtin_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 6fcf0afd..6d5ab7e0 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -6813,6 +6813,13 @@ func TestState_SetPrologFlag(t *testing.T) { assert.True(t, ok) assert.False(t, state.charConvEnabled) }) + + t.Run("unknown", func(t *testing.T) { + state := State{charConvEnabled: true} + ok, err := state.SetPrologFlag(Atom("char_conversion"), Atom("foo"), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) }) t.Run("debug", func(t *testing.T) { @@ -6831,6 +6838,13 @@ func TestState_SetPrologFlag(t *testing.T) { assert.True(t, ok) assert.False(t, state.debug) }) + + t.Run("unknown", func(t *testing.T) { + state := State{debug: true} + ok, err := state.SetPrologFlag(Atom("debug"), Atom("foo"), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) }) t.Run("max_arity", func(t *testing.T) { @@ -6864,6 +6878,46 @@ func TestState_SetPrologFlag(t *testing.T) { assert.True(t, ok) assert.Equal(t, unknownFail, state.unknown) }) + + t.Run("fail", func(t *testing.T) { + var state State + ok, err := state.SetPrologFlag(Atom("unknown"), Atom("foo"), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + }) + + t.Run("double_quotes", func(t *testing.T) { + t.Run("codes", func(t *testing.T) { + var state State + ok, err := state.SetPrologFlag(Atom("double_quotes"), Atom("codes"), Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, DoubleQuotesCodes, state.doubleQuotes) + }) + + t.Run("chars", func(t *testing.T) { + var state State + ok, err := state.SetPrologFlag(Atom("double_quotes"), Atom("chars"), Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, DoubleQuotesChars, state.doubleQuotes) + }) + + t.Run("atom", func(t *testing.T) { + var state State + ok, err := state.SetPrologFlag(Atom("double_quotes"), Atom("atom"), Success, nil).Force(context.Background()) + assert.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, DoubleQuotesAtom, state.doubleQuotes) + }) + + t.Run("unknown", func(t *testing.T) { + var state State + ok, err := state.SetPrologFlag(Atom("double_quotes"), Atom("foo"), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) }) t.Run("flag is a variable", func(t *testing.T) {