Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: Evaluation of 200 lines takes >130s #3654

Open
loisch opened this issue Jan 5, 2025 · 2 comments
Open

Performance: Evaluation of 200 lines takes >130s #3654

loisch opened this issue Jan 5, 2025 · 2 comments
Labels
evaluator NeedsInfo More information or clarification needed from the user

Comments

@loisch
Copy link

loisch commented Jan 5, 2025

I'm opening an issue because #2850 told me to.

Context: I'm evaluating CUE as a configuration language for our hospital information system with about 120.000 loc (just 2,5 MB) of declarative configuration that consists of data (catalogs) and DSL code. The configuration uses files with XML and (Emacs) org-mode syntax. Currently our Haskell backend parses and checks the configuration in less than a second. I'd like to replace that configuration using CUE.

Thank's to @myitcv I've managed to write CUE definitions that check my input and generate my old-style org-mode configuration. But just with about 40 lines of test data cue eval already needs 130s on my Mac M2. Haskell's lazyness has got it's surprises so I'm used to think differently about performance for different languages.

Why does my CUE program take so long and what can I do about it? How do I need to think about performance when writing CUE code? Which constructions can be evaluated in linear / quadratic / cubic (or worse?) time? Am I using too many disjunctions?

I've tried the new evaluator (using my own build at commit f2775f8) but that didn't change anything.

Without my org-generation the checking is fast. So maybe CUE is just not suited as a programming language for generating my old-style org-syntax configuration format?

import "strings"

import "encoding/json"

#FinDefSetElemType: {
	#Type!: _
}
#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
		}
		only: {
			Only: [...#Elem.#Type]
		}
	}
}

#mkFinDefSet: {
	#Definition!: #FinDefSetDefinition
	#Type: or([for _, v in #Definition.#Alternatives {v}])
}


#FinDefSetElemType: {
	#Type: _
	toString!: {
		_input:  #Type
		_output: string
	}
}

#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
			let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "AllBut [\(str)]"
		}
		only: {
			Only: [...#Elem.#Type]
			let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#ConfigText: string | {UseSectionName: true}

#ConfigTextRule: #ConfigText | {Append: #ConfigText} | {Prepend: #ConfigText}

#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]

#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
	#Type: #ConfigTextRules
}

#FinDefSetOfConfigText: (
	#mkFinDefSet & {
		#Definition: {#Elem: #FinDefSetConfigTextElem}
	}).#Type

#PatSetCfg: {
	wards?:                           *null | #FinDefSetOfConfigText
	facilities?:                      *null | #FinDefSetOfConfigText
	stayKind?:                        "OnlyInpatients" | "InAndOutpatients" | "OnlyOutpatients" | "OnlyConsultations"
	id?:                              *null | #ConfigTextRules
	servers?:                         *null | #FinDefSetOfConfigText
	includeInpatientsBeforeStayDays?: int & >=0 & <30
}

#PatSetNode: string | {
	name:    string
	config?: #PatSetCfg
	children?: [...#PatSetNode]
}




#FinDefSetConfigTextElem: {
	toString: {
		_input: #ConfigTextRules
		_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
	}
}

#orgForConfigText: {
	IN=source: #ConfigText
	orgSyntax: [
		if (IN & string) != _|_ {
			json.Marshal(IN)
		},
		if IN.UseSectionName != _|_ {
			"UseSectionName"
		},
		"Wrong input \(IN) in orgForConfigText",
	][0]
}

#orgForConfigTextRule: {
	IN=source: #ConfigTextRule
	orgSyntax: [
		if IN.Append != _|_ {
			"Append " + (#orgForConfigText & {source: IN.Append}).orgSyntax
		},
		if IN.Prepend != _|_ {
			"Prepend " + (#orgForConfigText & {source: IN.Prepend}).orgSyntax
		},
		(#orgForConfigText & {source: IN}).orgSyntax,
	][0]
}

#orgForConfigTextRules: {
	IN=source: #ConfigTextRules
	orgSyntax: {
		let INL = IN & [...]
		[
			if INL != _|_ {
				let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
				let result = strings.Join(elems, "\",\"")
				"[\(result)]"
			},
			(#orgForConfigTextRule & {source: IN}).orgSyntax,
		][0]
	}
}

#PatSetCfg: {
	wards?:                           _
	facilities?:                      _
	stayKind?:                        _
	id?:                              _
	includeInpatientsBeforeStayDays?: _
	let parts = [
		if wards != _|_ {
			"- wards :: " + wards.orgSyntax
		},
		if facilities != _|_ {
			"- facilities :: " + facilities.orgSyntax
		},
		if stayKind != _|_ {
			"- stayKind :: " + stayKind
		},
		if id != _|_ {
			"- id :: " + (#orgForConfigText & {source: id}).orgSyntax
		},
		if includeInpatientsBeforeStayDays != _|_ {
			"- includeInpatientsBeforeStayDays :: \(includeInpatientsBeforeStayDays)"
		},
	]
	orgSyntax: strings.Join(parts, "\n")
}

_nodes2: ["prod-live-demo-int-node-n-01", "prod-live-real-int-node-n-02", "staging-live-real-int-node-n-02", "dev-live-fake-int-node-n-02"]

top: #PatSetNode
top: {
	name: "Top"
	children: [abteilung]
}

abteilung: #PatSetNode
abteilung: {
	name: "Abteilung"
	config: {
		wards: AllBut: []
		facilities: Only: [{UseSectionName: true}]
		stayKind:                        "OnlyInpatients"
		includeInpatientsBeforeStayDays: 3
	}
	children: [abteilungAbteilung]
}

abteilungAbteilung: #PatSetNode
abteilungAbteilung: {
	name: "Abteilung/Abteilung"
	children: [node2]
}

node2: #PatSetNode
node2: {
	name: "Node2"
	config: servers: Only: _nodes2
	children: [
		"KIM-ENDO",
		kimGast,
	]
}

kimGast: #PatSetNode
kimGast: {
	name: "KIM-GAST"
	config: {
		wards: AllBut: ["KIM6T"]
	}
}
@myitcv
Copy link
Member

myitcv commented Jan 6, 2025

@loisch thanks for raising this. Please can you help to provide the following details (taken from the bug report template):

### What version of CUE are you using (`cue version`)?

<pre>
$ cue version

</pre>

### Does this issue reproduce with the latest stable release?



### What did you do?

<!--
If possible, provide a recipe for reproducing the error.

For advice on how to create a good reproducer, please see:

https://github.com/cue-lang/cue/wiki/Creating-test-or-performance-reproducers
-->



### What did you expect to see?



### What did you see instead?

I've already noted that under evalv3 we run into #3597

@myitcv myitcv added NeedsInfo More information or clarification needed from the user evaluator and removed Triage Requires triage/attention labels Jan 6, 2025
@loisch
Copy link
Author

loisch commented Jan 7, 2025

Sorry... I've made testscripts to explain (see below).

Overview

There seem to be three - possibly related - issues concerning

  1. performance of my program
  2. correctness of my program
  3. (non) referential transparency of the cue language

1) Performance

With my definitions every operation (cue vet, cue export, cue eval) is very slow. I'm processing a tree of six nodes defined at the bottom of my program (see reproducers below):

top ->
  [ abteilung ->
    [  abteilungAbteilung ->
      [ node2 ->
        [ "KIM-ENDO",
          kimGast
        ]
      ]
    ]
  ]

When leaving out the top node cue vet doesn't report errors but needs ~21s.

| tree-size | wall-time |
|-----------+-----------|
|         6 |   130.0s  |
|         5 |    21.0s  |
|         4 |     3.5s  |
|         3 |     0.5s  |
|         2 |     0.1s  |

2) Correctness

When the top node is included cue vet reports errors:

top.children.0.config.facilities: 3 errors in empty disjunction:
top.children.0.config.facilities: conflicting values null and {AllBut:[...#Elem.#Type],let xs#1=[for _, x in AllBut {(#Elem.toString & {_input:x})._output}],let str#2=strings.Join(xs, "\",\""),orgSyntax:"AllBut [\(〈0;let str#2〉)]"} (mismatched types null and struct):
    ./out.cue:22:52
    ./out.cue:37:11
    ./out.cue:63:25
    ./out.cue:70:37
    ./out.cue:70:44
    ./out.cue:79:11
    ./out.cue:165:13
    ./out.cue:168:12
top.children.0.config.facilities.Only: field not allowed:
    ./out.cue:11:11
    ./out.cue:22:51
    ./out.cue:22:52
    ./out.cue:37:11
    ./out.cue:63:25
    ./out.cue:70:44
    ./out.cue:79:11
    ./out.cue:80:14
    ./out.cue:80:17
    ./out.cue:162:6
    ./out.cue:165:13
    ./out.cue:168:12
    ./out.cue:173:15
invalid interpolation: cycle error:
    ./out.cue:47:15

I don't understand these errors. Probably this is my fault but it's still a problem when using CUE if it's so hard to understand what's wrong.

cue eval outputs

...
top: {
    name: "Top"
    children: [{
        name: "Abteilung"
        config: {
            wards: {
                AllBut: []
                orgSyntax: "AllBut []"
            }
            facilities: _ & _ & (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & {
                Only: [{
                    UseSectionName: true
                }]
            }

I don't know where this
_ & _ & (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true}] } comes from. From my intuitive perspective it should unify but I'm not sure if CUE really works this way. I'd expect that the following is true for &:

a & b = b & a
_ & a = a
a & a = a
(a | b) & c = a & c | b & c

so the upper should be equivalent to (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true}] } which unifies in my program.

3) Referential transparency

Coming from Haskell I'd expect a pure / side-effect-free language such as CUE to have referential transparency, that is replacing a name with it's definition doesn't change the meaning of the program. That's not the case in CUE:

When repeating the same term in an expression unification fails:

value: (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }

while it unifies when the term appears only once

value: (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }

When I give (*null | #FinDefSetOfConfigText) a name, then it also works when I use that name twice.

#MyType: *null | #FinDefSetOfConfigText
value: #MyType & #MyType & { Only: [{ UseSectionName: true }] }

I hope that this behavior is due to a bug, because I'd have no idea how I could work with CUE when I have to exactly know how the evaluation order is and when a name is replaced with it's definition to be able to predict if unification will succeed or fail. I also seems very hard to optimize the evaluator when replacing a name with it's definition changes semantics.

Reproducers

What version of CUE are you using (cue version)?

I've tried the latest stable release:

cue version v0.11.1

go version go1.23.4
      -buildmode exe
       -compiler gc
       -trimpath true
  DefaultGODEBUG asynctimerchan=1,gotypesalias=0,httpservecontentkeepheaders=1,tls3des=1,tlskyber=0,x509keypairleaf=0,x509negativeserial=1
     CGO_ENABLED 1
          GOARCH arm64
            GOOS darwin
         GOARM64 v8.0
cue.lang.version v0.11.1

and reproduced with current master:

cue version v0.12.0-e41efd5c8c29872c23dc27a36d44fd5d6876b01f

go version go1.22.1
      -buildmode exe
       -compiler gc
        -ldflags -X cuelang.org/go/cmd/cue/cmd.version=v0.12.0-e41efd5c8c29872c23dc27a36d44fd5d6876b01f
     CGO_ENABLED 1
          GOARCH arm64
            GOOS darwin
             vcs git
    vcs.revision e41efd5c8c29872c23dc27a36d44fd5d6876b01f
        vcs.time 2025-01-06T17:14:50Z
    vcs.modified false
cue.lang.version v0.12.0

What did you do?

I have two reproducers (slow.txtar and incorrect.txtar below) with the same definitions.
slow.txtar checks a tree with 5 nodes in ~21s and succeeds:

# time testscript slow.txtar
PASS

________________________________________________________
Executed in   22.00 secs    fish           external
   usr time   40.14 secs    0.21 millis   40.14 secs
   sys time    0.84 secs    1.37 millis    0.84 secs

incorrect.txtar checks a (seemingly?) correct tree of 6 nodes and fails after ~130s.

What did you expect to see?

I'd expect both reproducers to succeed in < 1s.

slow.txtar

exec cue vet out.cue

-- out.cue --
import "strings"

import "encoding/json"

#FinDefSetElemType: {
	#Type!: _
}
#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
		}
		only: {
			Only: [...#Elem.#Type]
		}
	}
}

#mkFinDefSet: {
	#Definition!: #FinDefSetDefinition
	#Type: or([for _, v in #Definition.#Alternatives {v}])
}


#FinDefSetElemType: {
	#Type: _
	toString!: {
		_input:  #Type
		_output: string
	}
}

#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
			let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "AllBut [\(str)]"
		}
		only: {
			Only: [...#Elem.#Type]
			let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#ConfigText: string | {UseSectionName: true}

#ConfigTextRule: #ConfigText | {Append: #ConfigText} | {Prepend: #ConfigText}

#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]

#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
	#Type: #ConfigTextRules
}

#FinDefSetOfConfigText: (
	#mkFinDefSet & {
		#Definition: {#Elem: #FinDefSetConfigTextElem}
	}).#Type

#PatSetCfg: {
	wards?:                           *null | #FinDefSetOfConfigText
	facilities?:                      *null | #FinDefSetOfConfigText
	stayKind?:                        "OnlyInpatients" | "InAndOutpatients" | "OnlyOutpatients" | "OnlyConsultations"
	id?:                              *null | #ConfigTextRules
	servers?:                         *null | #FinDefSetOfConfigText
	includeInpatientsBeforeStayDays?: int & >=0 & <30
}

#PatSetNode: string | {
	name:    string
	config?: #PatSetCfg
	children?: [...#PatSetNode]
}




#FinDefSetConfigTextElem: {
	toString: {
		_input: #ConfigTextRules
		_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
	}
}

#orgForConfigText: {
	IN=source: #ConfigText
	orgSyntax: [
		if (IN & string) != _|_ {
			//IN
			json.Marshal(IN)
		},
		if IN.UseSectionName != _|_ {
			"UseSectionName"
		},
		"Wrong input \(IN) in orgForConfigText",
	][0]
}

#orgForConfigTextRule: {
	IN=source: #ConfigTextRule
	orgSyntax: [
		if IN.Append != _|_ {
			"Append " + (#orgForConfigText & {source: IN.Append}).orgSyntax
		},
		if IN.Prepend != _|_ {
			"Prepend " + (#orgForConfigText & {source: IN.Prepend}).orgSyntax
		},
		(#orgForConfigText & {source: IN}).orgSyntax,
	][0]
}

#orgForConfigTextRules: {
	IN=source: #ConfigTextRules
	orgSyntax: {
		let INL = IN & [...]
		[
			if INL != _|_ {
				let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
				let result = strings.Join(elems, "\",\"")
				"[\(result)]"
			},
			(#orgForConfigTextRule & {source: IN}).orgSyntax,
		][0]
	}
}

#PatSetCfg: {
	wards?:                           _
	facilities?:                      _
	stayKind?:                        _
	id?:                              _
	includeInpatientsBeforeStayDays?: _
	let parts = [
		if wards != _|_ {
			"- wards :: " + wards.orgSyntax
		},
		if facilities != _|_ {
			"- facilities :: " + facilities.orgSyntax
		},
		if stayKind != _|_ {
			"- stayKind :: " + stayKind
		},
		if id != _|_ {
			"- id :: " + (#orgForConfigText & {source: id}).orgSyntax
		},
		if includeInpatientsBeforeStayDays != _|_ {
			"- includeInpatientsBeforeStayDays :: \(includeInpatientsBeforeStayDays)"
		},
	]
	orgSyntax: strings.Join(parts, "\n")
}

_nodes2: ["prod-live-demo-int-node-n-01", "prod-live-real-int-node-n-02", "staging-live-real-int-node-n-02", "dev-live-fake-int-node-n-02"]

abteilung: #PatSetNode
abteilung: {
	name: "Abteilung"
	config: {
		wards: AllBut: []
		facilities: Only: [{UseSectionName: true}]
		stayKind:                        "OnlyInpatients"
		includeInpatientsBeforeStayDays: 3
	}
	children: [abteilungAbteilung]
}

abteilungAbteilung: #PatSetNode
abteilungAbteilung: {
	name: "Abteilung/Abteilung"
	children: [node2]
}

node2: #PatSetNode
node2: {
	name: "Node2"
	config: servers: Only: _nodes2
	children: [
		"KIM-ENDO",
		kimGast,
	]
}

kimGast: #PatSetNode
kimGast: {
	name: "KIM-GAST"
	config: {
		wards: AllBut: ["KIM6T"]
	}
}

incorrect.txtar

! exec cue vet -c out.cue
stderr 'top.children.0.config.facilities: 3 errors in empty disjunction'

-- out.cue --
import "strings"

import "encoding/json"

#FinDefSetElemType: {
	#Type!: _
}
#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
		}
		only: {
			Only: [...#Elem.#Type]
		}
	}
}

#mkFinDefSet: {
	#Definition!: #FinDefSetDefinition
	#Type: or([for _, v in #Definition.#Alternatives {v}])
}


#FinDefSetElemType: {
	#Type: _
	toString!: {
		_input:  #Type
		_output: string
	}
}

#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
			let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "AllBut [\(str)]"
		}
		only: {
			Only: [...#Elem.#Type]
			let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#ConfigText: string | {UseSectionName: true}

#ConfigTextRule: #ConfigText | {Append: #ConfigText} | {Prepend: #ConfigText}

#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]

#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
	#Type: #ConfigTextRules
}

#FinDefSetOfConfigText: (
	#mkFinDefSet & {
		#Definition: {#Elem: #FinDefSetConfigTextElem}
	}).#Type

#PatSetCfg: {
	wards?:                           *null | #FinDefSetOfConfigText
	facilities?:                      *null | #FinDefSetOfConfigText
	stayKind?:                        "OnlyInpatients" | "InAndOutpatients" | "OnlyOutpatients" | "OnlyConsultations"
	id?:                              *null | #ConfigTextRules
	servers?:                         *null | #FinDefSetOfConfigText
	includeInpatientsBeforeStayDays?: int & >=0 & <30
}

#PatSetNode: string | {
	name:    string
	config?: #PatSetCfg
	children?: [...#PatSetNode]
}




#FinDefSetConfigTextElem: {
	toString: {
		_input: #ConfigTextRules
		_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
	}
}

#orgForConfigText: {
	IN=source: #ConfigText
	orgSyntax: [
		if (IN & string) != _|_ {
			//IN
			json.Marshal(IN)
		},
		if IN.UseSectionName != _|_ {
			"UseSectionName"
		},
		"Wrong input \(IN) in orgForConfigText",
	][0]
}

#orgForConfigTextRule: {
	IN=source: #ConfigTextRule
	orgSyntax: [
		if IN.Append != _|_ {
			"Append " + (#orgForConfigText & {source: IN.Append}).orgSyntax
		},
		if IN.Prepend != _|_ {
			"Prepend " + (#orgForConfigText & {source: IN.Prepend}).orgSyntax
		},
		(#orgForConfigText & {source: IN}).orgSyntax,
	][0]
}

#orgForConfigTextRules: {
	IN=source: #ConfigTextRules
	orgSyntax: {
		let INL = IN & [...]
		[
			if INL != _|_ {
				let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
				let result = strings.Join(elems, "\",\"")
				"[\(result)]"
			},
			(#orgForConfigTextRule & {source: IN}).orgSyntax,
		][0]
	}
}

#PatSetCfg: {
	wards?:                           _
	facilities?:                      _
	stayKind?:                        _
	id?:                              _
	includeInpatientsBeforeStayDays?: _
	let parts = [
		if wards != _|_ {
			"- wards :: " + wards.orgSyntax
		},
		if facilities != _|_ {
			"- facilities :: " + facilities.orgSyntax
		},
		if stayKind != _|_ {
			"- stayKind :: " + stayKind
		},
		if id != _|_ {
			"- id :: " + (#orgForConfigText & {source: id}).orgSyntax
		},
		if includeInpatientsBeforeStayDays != _|_ {
			"- includeInpatientsBeforeStayDays :: \(includeInpatientsBeforeStayDays)"
		},
	]
	orgSyntax: strings.Join(parts, "\n")
}

_nodes2: ["prod-live-demo-int-node-n-01", "prod-live-real-int-node-n-02", "staging-live-real-int-node-n-02", "dev-live-fake-int-node-n-02"]

top: #PatSetNode
top: {
	name: "Top"
	children: [abteilung]
}

abteilung: #PatSetNode
abteilung: {
	name: "Abteilung"
	config: {
		wards: AllBut: []
		facilities: Only: [{UseSectionName: true}]
		stayKind:                        "OnlyInpatients"
		includeInpatientsBeforeStayDays: 3
	}
	children: [abteilungAbteilung]
}

abteilungAbteilung: #PatSetNode
abteilungAbteilung: {
	name: "Abteilung/Abteilung"
	children: [node2]
}

node2: #PatSetNode
node2: {
	name: "Node2"
	config: servers: Only: _nodes2
	children: [
		"KIM-ENDO",
		kimGast,
	]
}

kimGast: #PatSetNode
kimGast: {
	name: "KIM-GAST"
	config: {
		wards: AllBut: ["KIM6T"]
	}
}

Referential transparency repro.txtar

exec cue export out.cue -e value1
exec cue export out.cue -e value3
exec cue export out.cue -e value4
! exec cue export out.cue -e value2

-- out.cue --
import "strings"

import "encoding/json"

#FinDefSetElemType: {
	#Type!: _
}
#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
		}
		only: {
			Only: [...#Elem.#Type]
		}
	}
}

#mkFinDefSet: {
	#Definition!: #FinDefSetDefinition
	#Type: or([for _, v in #Definition.#Alternatives {v}])
}


#FinDefSetElemType: {
	#Type: _
	toString!: {
		_input:  #Type
		_output: string
	}
}

#FinDefSetDefinition: {
	#Elem!: #FinDefSetElemType
	#Alternatives: {
		allBut: {
			AllBut: [...#Elem.#Type]
			let xs = [for x in AllBut {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "AllBut [\(str)]"
		}
		only: {
			Only: [...#Elem.#Type]
			let xs = [for x in Only {(#Elem.toString & {_input: x})._output}]
			let str = strings.Join(xs, "\",\"")
			orgSyntax: "Only [\(str)]"
		}
	}
}

#ConfigText: string | {UseSectionName: true}

#ConfigTextRule: #ConfigText | {Append: #ConfigText} | {Prepend: #ConfigText}

#ConfigTextRules: #ConfigTextRule | [#ConfigTextRule, #ConfigTextRule, ...#ConfigTextRule]

#FinDefSetConfigTextElem: #FinDefSetElemType
#FinDefSetConfigTextElem: {
	#Type: #ConfigTextRules
}

#FinDefSetOfConfigText: (
	#mkFinDefSet & {
		#Definition: {#Elem: #FinDefSetConfigTextElem}
	}).#Type

#PatSetCfg: {
	wards?:                           *null | #FinDefSetOfConfigText
	facilities?:                      *null | #FinDefSetOfConfigText
	stayKind?:                        "OnlyInpatients" | "InAndOutpatients" | "OnlyOutpatients" | "OnlyConsultations"
	id?:                              *null | #ConfigTextRules
	servers?:                         *null | #FinDefSetOfConfigText
	includeInpatientsBeforeStayDays?: int & >=0 & <30
}

#PatSetNode: string | {
	name:    string
	config?: #PatSetCfg
	children?: [...#PatSetNode]
}




#FinDefSetConfigTextElem: {
	toString: {
		_input: #ConfigTextRules
		_output: (#orgForConfigTextRules & {source: _input}).orgSyntax
	}
}

#orgForConfigText: {
	IN=source: #ConfigText
	orgSyntax: [
		if (IN & string) != _|_ {
			//IN
			json.Marshal(IN)
		},
		if IN.UseSectionName != _|_ {
			"UseSectionName"
		},
		"Wrong input \(IN) in orgForConfigText",
	][0]
}

#orgForConfigTextRule: {
	IN=source: #ConfigTextRule
	orgSyntax: [
		if IN.Append != _|_ {
			"Append " + (#orgForConfigText & {source: IN.Append}).orgSyntax
		},
		if IN.Prepend != _|_ {
			"Prepend " + (#orgForConfigText & {source: IN.Prepend}).orgSyntax
		},
		(#orgForConfigText & {source: IN}).orgSyntax,
	][0]
}

#orgForConfigTextRules: {
	IN=source: #ConfigTextRules
	orgSyntax: {
		let INL = IN & [...]
		[
			if INL != _|_ {
				let elems = [for x in IN {(#orgForConfigTextRule & {source: x}).orgSyntax}]
				let result = strings.Join(elems, "\",\"")
				"[\(result)]"
			},
			(#orgForConfigTextRule & {source: IN}).orgSyntax,
		][0]
	}
}

#PatSetCfg: {
	wards?:                           _
	facilities?:                      _
	stayKind?:                        _
	id?:                              _
	includeInpatientsBeforeStayDays?: _
	let parts = [
		if wards != _|_ {
			"- wards :: " + wards.orgSyntax
		},
		if facilities != _|_ {
			"- facilities :: " + facilities.orgSyntax
		},
		if stayKind != _|_ {
			"- stayKind :: " + stayKind
		},
		if id != _|_ {
			"- id :: " + (#orgForConfigText & {source: id}).orgSyntax
		},
		if includeInpatientsBeforeStayDays != _|_ {
			"- includeInpatientsBeforeStayDays :: \(includeInpatientsBeforeStayDays)"
		},
	]
	orgSyntax: strings.Join(parts, "\n")
}

#MyType: *null | #FinDefSetOfConfigText

value1: (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }
value2: (*null | #FinDefSetOfConfigText) & (*null | #FinDefSetOfConfigText) & { Only: [{ UseSectionName: true }] }
value3: #MyType & { Only: [{ UseSectionName: true }] }
value4: #MyType & #MyType & { Only: [{ UseSectionName: true }] }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
evaluator NeedsInfo More information or clarification needed from the user
Projects
None yet
Development

No branches or pull requests

2 participants