// Issues: #667, #695, #622

// Comparing against bottom is not officially supported by the spec.
// In practice it is used for a variety of purposes.
//
// TODO: It should really be replaced with two builtins:
//
//    - exists(reference): check if a certain field exists.
//    - isvalid(value):    check if a certain value is valid (recursively).
//
// For now it implements something in between these two: it fails if a value
// resolves to an error, but not necessarily if it does so recursively.
// Although adding a recursive check is easy, it will break existing
// configurations, as a recursive evaluation will trigger cycles where these
// are perhaps not expected.

// To verify these tests, each result should have a field
//
//     X: "user@example.com"
//
// for the large and medium examples and
//
//     X: "message: hello"
//
// for the simple example.
//
// These are not automatically tested using CUE to avoid interfering with the
// evaluation.

-- in.cue --
import "regexp"

simple: {
	#message: #"^(message: (?P<message>.*))?$"#

	p1: {
		X: "message: hello"
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		Y: regexp.FindNamedSubmatch(#message, X)
		X: #aux.message
	}

	p2: {
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		X: "message: hello"
		Y: regexp.FindNamedSubmatch(#message, X)
		X: #aux.message
	}

	p3: {
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		Y: regexp.FindNamedSubmatch(#message, X)
		X: "message: hello"
		X: #aux.message
	}

	p4: {
		X: #aux.message
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		Y: regexp.FindNamedSubmatch(#message, X)
		X: "message: hello"
	}

	p5: {
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		X: #aux.message
		Y: regexp.FindNamedSubmatch(#message, X)
		X: "message: hello"
	}

	p6: {
		#aux: {
			if Y.message == _|_ {
				message: ""
			}
			if Y.message != _|_ {
				message: "message: " + Y.message
			}
		}

		Y: regexp.FindNamedSubmatch(#message, X)
		X: #aux.message
		X: "message: hello"
	}
}

medium: {
	#userHostPort: #"^((?P<userinfo>[[:alnum:]]*)@)?(?P<host>[[:alnum:].]+)$"#

	p1: {
		Y: {
			userinfo: "user"
			host:     "mod.test"
		}

		// X: "user@example.com"
		X: #X.userinfo + #X.host

		#X: {
			// userinfo: "user@"
			// host:     "mod.test"

			if Y.userinfo == _|_ {
				userinfo: ""
			}
			if Y.userinfo != _|_ {
				userinfo: Y.userinfo + "@"
			}

			host: Y.host
		}

		Y: {
			// userinfo: "user"
			// host:     "mod.test"

			if #Y.userinfo != _|_ {
				userinfo: #Y.userinfo
			}

			host: #Y.host
		}

		#Y: regexp.FindNamedSubmatch(#userHostPort, X)
	}

	p2: {
		X: #X.userinfo + #X.host

		Y: {
			userinfo: "user"
			host:     "mod.test"
		}

		#X: {
			if Y.userinfo == _|_ {
				userinfo: ""
			}
			if Y.userinfo != _|_ {
				userinfo: Y.userinfo + "@"
			}

			host: Y.host
		}

		Y: {
			if #Y.userinfo != _|_ {
				userinfo: #Y.userinfo
			}

			host: #Y.host
		}

		#Y: regexp.FindNamedSubmatch(#userHostPort, X)
	}

	p3: {
		X: #X.userinfo + #X.host

		#X: {
			if Y.userinfo == _|_ {
				userinfo: ""
			}
			if Y.userinfo != _|_ {
				userinfo: Y.userinfo + "@"
			}

			host: Y.host
		}

		Y: {
			userinfo: "user"
			host:     "mod.test"
		}

		Y: {
			if #Y.userinfo != _|_ {
				userinfo: #Y.userinfo
			}

			host: #Y.host
		}

		#Y: regexp.FindNamedSubmatch(#userHostPort, X)
	}

	p4: {
		X: #X.userinfo + #X.host

		#X: {
			if Y.userinfo == _|_ {
				userinfo: ""
			}
			if Y.userinfo != _|_ {
				userinfo: Y.userinfo + "@"
			}

			host: Y.host
		}

		Y: {
			if #Y.userinfo != _|_ {
				userinfo: #Y.userinfo
			}

			host: #Y.host
		}

		#Y: regexp.FindNamedSubmatch(#userHostPort, X)

		Y: {
			userinfo: "user"
			host:     "mod.test"
		}
	}
}
-- out/eval/stats --
Leaks:  0
Freed:  85
Reused: 73
Allocs: 12
Retain: 200

Unifications: 85
Conjuncts:    152
Disjuncts:    194
-- out/evalalpha --
(struct){
  simple: (struct){
    #message: (string){ "^(message: (?P<message>.*))?$" }
    p1: (struct){
      X: (string){ "message: hello" }
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p2: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      X: (string){ "message: hello" }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p3: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
      X: (string){ "message: hello" }
    }
    p4: (struct){
      X: (string){ "message: hello" }
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p5: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      X: (string){ "message: hello" }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p6: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
      X: (string){ "message: hello" }
    }
  }
  medium: (struct){
    #userHostPort: (string){ "^((?P<userinfo>[[:alnum:]]*)@)?(?P<host>[[:alnum:].]+)$" }
    p1: (struct){
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        host: (string){ "mod.test" }
        userinfo: (string){ "user" }
      }
    }
    p2: (struct){
      X: (string){ "user@mod.test" }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
    }
    p3: (struct){
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
    }
    p4: (struct){
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
    }
  }
}
-- diff/-out/evalalpha<==>+out/eval --
diff old new
--- old
+++ new
@@ -84,38 +84,38 @@
         host: (string){ "mod.test" }
       }
       #Y: (#struct){
-        host: (string){ "mod.test" }
-        userinfo: (string){ "user" }
-      }
-    }
-    p3: (struct){
-      X: (string){ "user@mod.test" }
-      #X: (#struct){
-        userinfo: (string){ "user@" }
-        host: (string){ "mod.test" }
-      }
-      Y: (struct){
-        userinfo: (string){ "user" }
-        host: (string){ "mod.test" }
-      }
-      #Y: (#struct){
-        host: (string){ "mod.test" }
-        userinfo: (string){ "user" }
-      }
-    }
-    p4: (struct){
-      X: (string){ "user@mod.test" }
-      #X: (#struct){
-        userinfo: (string){ "user@" }
-        host: (string){ "mod.test" }
-      }
-      Y: (struct){
-        userinfo: (string){ "user" }
-        host: (string){ "mod.test" }
-      }
-      #Y: (#struct){
-        host: (string){ "mod.test" }
-        userinfo: (string){ "user" }
+        userinfo: (string){ "user" }
+        host: (string){ "mod.test" }
+      }
+    }
+    p3: (struct){
+      X: (string){ "user@mod.test" }
+      #X: (#struct){
+        userinfo: (string){ "user@" }
+        host: (string){ "mod.test" }
+      }
+      Y: (struct){
+        userinfo: (string){ "user" }
+        host: (string){ "mod.test" }
+      }
+      #Y: (#struct){
+        userinfo: (string){ "user" }
+        host: (string){ "mod.test" }
+      }
+    }
+    p4: (struct){
+      X: (string){ "user@mod.test" }
+      #X: (#struct){
+        userinfo: (string){ "user@" }
+        host: (string){ "mod.test" }
+      }
+      Y: (struct){
+        userinfo: (string){ "user" }
+        host: (string){ "mod.test" }
+      }
+      #Y: (#struct){
+        userinfo: (string){ "user" }
+        host: (string){ "mod.test" }
       }
     }
   }
-- diff/todo/p3 --
Reordering
-- out/eval --
(struct){
  simple: (struct){
    #message: (string){ "^(message: (?P<message>.*))?$" }
    p1: (struct){
      X: (string){ "message: hello" }
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p2: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      X: (string){ "message: hello" }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p3: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
      X: (string){ "message: hello" }
    }
    p4: (struct){
      X: (string){ "message: hello" }
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p5: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      X: (string){ "message: hello" }
      Y: (struct){
        message: (string){ "hello" }
      }
    }
    p6: (struct){
      #aux: (#struct){
        message: (string){ "message: hello" }
      }
      Y: (struct){
        message: (string){ "hello" }
      }
      X: (string){ "message: hello" }
    }
  }
  medium: (struct){
    #userHostPort: (string){ "^((?P<userinfo>[[:alnum:]]*)@)?(?P<host>[[:alnum:].]+)$" }
    p1: (struct){
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        host: (string){ "mod.test" }
        userinfo: (string){ "user" }
      }
    }
    p2: (struct){
      X: (string){ "user@mod.test" }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        host: (string){ "mod.test" }
        userinfo: (string){ "user" }
      }
    }
    p3: (struct){
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        host: (string){ "mod.test" }
        userinfo: (string){ "user" }
      }
    }
    p4: (struct){
      X: (string){ "user@mod.test" }
      #X: (#struct){
        userinfo: (string){ "user@" }
        host: (string){ "mod.test" }
      }
      Y: (struct){
        userinfo: (string){ "user" }
        host: (string){ "mod.test" }
      }
      #Y: (#struct){
        host: (string){ "mod.test" }
        userinfo: (string){ "user" }
      }
    }
  }
}
-- out/compile --
--- in.cue
{
  simple: {
    #message: "^(message: (?P<message>.*))?$"
    p1: {
      X: "message: hello"
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: 〈0;#aux〉.message
    }
    p2: {
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      X: "message: hello"
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: 〈0;#aux〉.message
    }
    p3: {
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: "message: hello"
      X: 〈0;#aux〉.message
    }
    p4: {
      X: 〈0;#aux〉.message
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: "message: hello"
    }
    p5: {
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      X: 〈0;#aux〉.message
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: "message: hello"
    }
    p6: {
      #aux: {
        if (〈1;Y〉.message == _|_(explicit error (_|_ literal) in source)) {
          message: ""
        }
        if (〈1;Y〉.message != _|_(explicit error (_|_ literal) in source)) {
          message: ("message: " + 〈2;Y〉.message)
        }
      }
      Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#message〉, 〈0;X〉)
      X: 〈0;#aux〉.message
      X: "message: hello"
    }
  }
  medium: {
    #userHostPort: "^((?P<userinfo>[[:alnum:]]*)@)?(?P<host>[[:alnum:].]+)$"
    p1: {
      Y: {
        userinfo: "user"
        host: "mod.test"
      }
      X: (〈0;#X〉.userinfo + 〈0;#X〉.host)
      #X: {
        if (〈1;Y〉.userinfo == _|_(explicit error (_|_ literal) in source)) {
          userinfo: ""
        }
        if (〈1;Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: (〈2;Y〉.userinfo + "@")
        }
        host: 〈1;Y〉.host
      }
      Y: {
        if (〈1;#Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: 〈2;#Y〉.userinfo
        }
        host: 〈1;#Y〉.host
      }
      #Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#userHostPort〉, 〈0;X〉)
    }
    p2: {
      X: (〈0;#X〉.userinfo + 〈0;#X〉.host)
      Y: {
        userinfo: "user"
        host: "mod.test"
      }
      #X: {
        if (〈1;Y〉.userinfo == _|_(explicit error (_|_ literal) in source)) {
          userinfo: ""
        }
        if (〈1;Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: (〈2;Y〉.userinfo + "@")
        }
        host: 〈1;Y〉.host
      }
      Y: {
        if (〈1;#Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: 〈2;#Y〉.userinfo
        }
        host: 〈1;#Y〉.host
      }
      #Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#userHostPort〉, 〈0;X〉)
    }
    p3: {
      X: (〈0;#X〉.userinfo + 〈0;#X〉.host)
      #X: {
        if (〈1;Y〉.userinfo == _|_(explicit error (_|_ literal) in source)) {
          userinfo: ""
        }
        if (〈1;Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: (〈2;Y〉.userinfo + "@")
        }
        host: 〈1;Y〉.host
      }
      Y: {
        userinfo: "user"
        host: "mod.test"
      }
      Y: {
        if (〈1;#Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: 〈2;#Y〉.userinfo
        }
        host: 〈1;#Y〉.host
      }
      #Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#userHostPort〉, 〈0;X〉)
    }
    p4: {
      X: (〈0;#X〉.userinfo + 〈0;#X〉.host)
      #X: {
        if (〈1;Y〉.userinfo == _|_(explicit error (_|_ literal) in source)) {
          userinfo: ""
        }
        if (〈1;Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: (〈2;Y〉.userinfo + "@")
        }
        host: 〈1;Y〉.host
      }
      Y: {
        if (〈1;#Y〉.userinfo != _|_(explicit error (_|_ literal) in source)) {
          userinfo: 〈2;#Y〉.userinfo
        }
        host: 〈1;#Y〉.host
      }
      #Y: 〈import;regexp〉.FindNamedSubmatch(〈1;#userHostPort〉, 〈0;X〉)
      Y: {
        userinfo: "user"
        host: "mod.test"
      }
    }
  }
}
