package loader import ( "errors" "fmt" "math/big" "strconv" "strings" "github.com/marzeq/qk/attributes" "github.com/marzeq/qk/ir" "github.com/marzeq/qk/parser" "github.com/marzeq/qk/sema" "github.com/marzeq/qk/shared" qktarget "github.com/marzeq/qk/target" "github.com/marzeq/qk/tokeniser" "github.com/marzeq/qk/types" ) type StageReleaseMode uint8 const ( StageDebug StageReleaseMode = iota StageRelease ) type StageConfig struct { TargetTriple string Sysroot string ReleaseMode StageReleaseMode NoLibc bool NoStdlib bool } // StageEnvironment supplies already selected dependency modules to compile-time // evaluation. Staging clones these modules before running the ordinary semantic // and IR pipelines, so it gets normal import resolution without mutating the // frontend's real semantic state. type StageEnvironment struct { Modules map[string]*ModuleInfo Order []string TrustedStandardLibrary bool } type stageKind uint8 const ( stageBool stageKind = iota stageInteger stageFloat stageEnum stageEnumLiteral ) type stageValue struct { kind stageKind boolean bool integer *big.Int floating string typ types.Type domain string name string } type stageSelector struct { config StageConfig module string bindings map[string]stageValue evaluated map[parser.ExpressionNode]stageValue unresolved map[parser.ExpressionNode]struct{} needsIR bool syntheticID int environment *StageEnvironment } // SelectCompileTime lowers unresolved staged expressions through the ordinary // semantic and IR pipelines, evaluates them, and removes parsed when nodes. func SelectCompileTime(root *parser.RootNode, module string, config StageConfig) error { return selectCompileTime(root, module, config, nil) } func selectCompileTime(root *parser.RootNode, module string, config StageConfig, environment *StageEnvironment) error { selector := &stageSelector{ config: config, module: module, bindings: make(map[string]stageValue), evaluated: make(map[parser.ExpressionNode]stageValue), unresolved: make(map[parser.ExpressionNode]struct{}), environment: environment, } selector.primeBindings(root.Body) body, err := selector.selectBody(root.Body) if err != nil { return err } root.Body = body if selector.needsIR { if err := selector.evaluateUnresolved(root); err != nil { return err } selector.needsIR = false selector.unresolved = make(map[parser.ExpressionNode]struct{}) body, err = selector.selectBody(root.Body) if err != nil { return err } root.Body = body } if selector.needsIR { return fmt.Errorf("compile-time selection left an unresolved when condition") } return nil } // SelectCompileTimeRoots selects a module's parsed files together, preserving // same-module compile-time visibility independently of file discovery order. func SelectCompileTimeRoots(roots []*parser.RootNode, module string, config StageConfig) error { return SelectCompileTimeRootsWithEnvironment(roots, module, config, nil) } // SelectCompileTimeRootsWithEnvironment selects a module while making its // already selected dependencies available through ordinary imports. func SelectCompileTimeRootsWithEnvironment(roots []*parser.RootNode, module string, config StageConfig, environment *StageEnvironment) error { if len(roots) == 0 { return nil } combined := &parser.RootNode{Loc: roots[0].Loc} owners := make(map[string]*parser.RootNode, len(roots)) for _, root := range roots { combined.Body = append(combined.Body, root.Body...) owners[root.Loc.FilePath] = root root.Body = nil } if err := selectCompileTime(combined, module, config, environment); err != nil { return err } for _, node := range combined.Body { owner := owners[node.GetLoc().FilePath] if owner == nil { owner = roots[0] } owner.Body = append(owner.Body, node) } return nil } func (s *stageSelector) primeBindings(body []parser.Node) { remaining := make(map[*parser.DeclarationNode]bool) for _, node := range body { if declaration, ok := node.(*parser.DeclarationNode); ok && declaration.Comptime { remaining[declaration] = true } } for progress := true; progress; { progress = false for declaration := range remaining { value, resolved, err := s.evaluate(declaration.Value) if err != nil || !resolved { continue } s.bindings[declaration.Name] = value delete(remaining, declaration) progress = true } } } func (s *stageSelector) selectBody(body []parser.Node) ([]parser.Node, error) { var result []parser.Node for _, node := range body { selected, err := s.selectNode(node) if err != nil { return nil, err } result = append(result, selected...) } return result, nil } func (s *stageSelector) selectNode(node parser.Node) ([]parser.Node, error) { switch node := node.(type) { case *parser.WhenNode: selected, resolved, err := s.selectWhen(node) if err != nil { return nil, err } if !resolved { return []parser.Node{node}, nil } return selected, nil case *parser.CompilerDirectiveNode: if err := s.evaluateDirective(node); err != nil { return nil, err } if _, unresolved := s.unresolved[node.Condition]; unresolved { return []parser.Node{node}, nil } return nil, nil case *parser.ModuleNode: for index, attribute := range node.Attributes { parsed, ok := attribute.(parser.ParsedLinkAttribute) if !ok { continue } links, resolved, err := s.selectLinkItems(parsed.Items) if err != nil { return nil, err } if resolved { node.Attributes[index] = attributes.ModuleAttributeLink{Links: links} } } case *parser.DeclarationNode: if node.Comptime { if value, resolved, err := s.evaluate(node.Value); err != nil { return nil, err } else if resolved { s.bindings[node.Name] = value node.Value = stageLiteral(value, node.Value.GetLoc()) if node.TypeNode == nil { node.TypeNode = stageTypeNode(value, node.NameLoc) } } else { s.needsIR = true s.unresolved[node.Value] = struct{}{} } } var err error node.Value, err = s.selectExpression(node.Value) if err != nil { return nil, err } node.TypeNode, err = s.selectType(node.TypeNode) if err != nil { return nil, err } case *parser.TypeAliasNode: var err error node.Type, err = s.selectType(node.Type) if err != nil { return nil, err } case *parser.FunctionDefNode: var err error for _, arg := range node.Args { arg.Type, err = s.selectType(arg.Type) if err != nil { return nil, err } arg.Default, err = s.selectExpression(arg.Default) if err != nil { return nil, err } } node.RetTypeNode, err = s.selectType(node.RetTypeNode) if err != nil { return nil, err } if block, ok := node.Body.(*parser.BlockNode); ok { selected, err := s.selectBody(block.Body) if err != nil { return nil, err } block.Body = selected } else if expression, ok := node.Body.(parser.ExpressionNode); ok { node.Body, err = s.selectExpression(expression) if err != nil { return nil, err } } case *parser.BlockNode: selected, err := s.selectBody(node.Body) if err != nil { return nil, err } node.Body = selected case *parser.IfNode: var err error node.IfBranch.Condition, err = s.selectExpression(node.IfBranch.Condition) if err != nil { return nil, err } if _, err = s.selectNode(node.IfBranch.Node); err != nil { return nil, err } for index := range node.ElseIfBranches { branch := &node.ElseIfBranches[index] branch.Condition, err = s.selectExpression(branch.Condition) if err != nil { return nil, err } if _, err = s.selectNode(branch.Node); err != nil { return nil, err } } if node.ElseBranch != nil { if _, err = s.selectNode(node.ElseBranch); err != nil { return nil, err } } case *parser.ForNode: selected, err := s.selectBody(node.ExprsOrStmts) if err != nil { return nil, err } node.ExprsOrStmts = selected if _, err = s.selectNode(node.Body); err != nil { return nil, err } case *parser.RangeForNode: var err error node.Start, err = s.selectExpression(node.Start) if err != nil { return nil, err } node.End, err = s.selectExpression(node.End) if err != nil { return nil, err } if _, err = s.selectNode(node.Body); err != nil { return nil, err } case *parser.ForEachNode: var err error node.Iterable, err = s.selectExpression(node.Iterable) if err != nil { return nil, err } if _, err = s.selectNode(node.Body); err != nil { return nil, err } case *parser.MatchNode: var err error for index := range node.Subjects { node.Subjects[index], err = s.selectExpression(node.Subjects[index]) if err != nil { return nil, err } } for index := range node.Arms { arm := &node.Arms[index] arm.Guard, err = s.selectExpression(arm.Guard) if err != nil { return nil, err } arm.Body, err = s.selectExpression(arm.Body) if err != nil { return nil, err } } case *parser.ControlKeywordNode: var err error node.ReturnValue, err = s.selectExpression(node.ReturnValue) if err != nil { return nil, err } for index := range node.ReturnValues { node.ReturnValues[index], err = s.selectExpression(node.ReturnValues[index]) if err != nil { return nil, err } } case *parser.DeferNode: selected, err := s.selectNode(node.Action) if err != nil { return nil, err } if len(selected) == 1 { node.Action = selected[0] } case *parser.AssignmentNode: var err error node.Value, err = s.selectExpression(node.Value) if err != nil { return nil, err } case *parser.MultiDeclarationNode: var err error node.Value, err = s.selectExpression(node.Value) if err != nil { return nil, err } } return []parser.Node{node}, nil } func (s *stageSelector) selectExpression(expression parser.ExpressionNode) (parser.ExpressionNode, error) { if expression == nil { return nil, nil } if when, ok := expression.(*parser.WhenNode); ok { value, resolved, err := s.selectWhenValue(when) if err != nil || !resolved { return expression, err } return s.selectExpression(value.(parser.ExpressionNode)) } var err error switch node := expression.(type) { case *parser.BlockNode: _, err = s.selectNode(node) case *parser.IfNode: _, err = s.selectNode(node) case *parser.MatchNode: _, err = s.selectNode(node) case *parser.UnaryOpNode: node.Operand, err = s.selectExpression(node.Operand) case *parser.BinaryOpNode: node.Operand1, err = s.selectExpression(node.Operand1) if err == nil { node.Operand2, err = s.selectExpression(node.Operand2) } case *parser.FunctionCallNode: node.Callee, err = s.selectExpression(node.Callee) for index := range node.Args { if err != nil { break } node.Args[index], err = s.selectExpression(node.Args[index]) } case *parser.IndexExprNode: node.Subject, err = s.selectExpression(node.Subject) if err == nil { node.Index, err = s.selectExpression(node.Index) } case *parser.SliceExprNode: node.Subject, err = s.selectExpression(node.Subject) if err == nil { node.Start, err = s.selectExpression(node.Start) } if err == nil { node.End, err = s.selectExpression(node.End) } case *parser.CastNode: node.ToType, err = s.selectType(node.ToType) if err == nil { node.Operand, err = s.selectExpression(node.Operand) } case *parser.StructLiteralNode: for index := range node.Fields { if err != nil { break } node.Fields[index].R, err = s.selectExpression(node.Fields[index].R) } case *parser.SliceLiteralNode: for index := range node.Elements { if err != nil { break } node.Elements[index], err = s.selectExpression(node.Elements[index]) } if err == nil { node.RepeatValue, err = s.selectExpression(node.RepeatValue) } if err == nil { node.RepeatAmount, err = s.selectExpression(node.RepeatAmount) } } return expression, err } func (s *stageSelector) selectType(typeNode parser.TypeNode) (parser.TypeNode, error) { if typeNode == nil { return nil, nil } if when, ok := typeNode.(*parser.WhenNode); ok { value, resolved, err := s.selectWhenValue(when) if err != nil || !resolved { return typeNode, err } return s.selectType(value.(parser.TypeNode)) } var err error switch node := typeNode.(type) { case *parser.NamedTypeNode: for index := range node.TypeArguments { if err != nil { break } node.TypeArguments[index], err = s.selectType(node.TypeArguments[index]) } case *parser.ReprTypeNode: node.Operand, err = s.selectType(node.Operand) case *parser.PointerTypeNode: node.BaseType, err = s.selectType(node.BaseType) case *parser.SliceTypeNode: node.ElementType, err = s.selectType(node.ElementType) case *parser.ArrayTypeNode: node.ElementType, err = s.selectType(node.ElementType) if err == nil { node.Length, err = s.selectExpression(node.Length) node.Length = s.inlineStageExpression(node.Length) } case *parser.DynTypeNode: node.TraitType, err = s.selectType(node.TraitType) case *parser.FunctionTypeNode: for index := range node.Parameters { if err != nil { break } node.Parameters[index], err = s.selectType(node.Parameters[index]) } if err == nil { node.ReturnType, err = s.selectType(node.ReturnType) } case *parser.MultipleReturnTypeNode: for index := range node.Types { if err != nil { break } node.Types[index], err = s.selectType(node.Types[index]) } case *parser.StructTypeNode: for index := range node.Fields { if err != nil { break } node.Fields[index].Type, err = s.selectType(node.Fields[index].Type) } case *parser.FlagsTypeNode: node.Underlying, err = s.selectType(node.Underlying) case *parser.UnionTypeNode: node.TagType, err = s.selectType(node.TagType) for index := range node.Fields { if err != nil { break } node.Fields[index].Type, err = s.selectType(node.Fields[index].Type) } for variantIndex := range node.Variants { for fieldIndex := range node.Variants[variantIndex].Fields { if err != nil { break } field := &node.Variants[variantIndex].Fields[fieldIndex] field.Type, err = s.selectType(field.Type) } } } return typeNode, err } func (s *stageSelector) inlineStageExpression(expression parser.ExpressionNode) parser.ExpressionNode { switch node := expression.(type) { case *parser.IdentifierNode: if value, found := s.bindings[node.Name]; found && (value.kind == stageBool || value.kind == stageInteger || value.kind == stageFloat) { return stageLiteral(value, node.Loc) } case *parser.UnaryOpNode: node.Operand = s.inlineStageExpression(node.Operand) case *parser.BinaryOpNode: node.Operand1 = s.inlineStageExpression(node.Operand1) node.Operand2 = s.inlineStageExpression(node.Operand2) case *parser.CastNode: node.Operand = s.inlineStageExpression(node.Operand) } return expression } func (s *stageSelector) selectWhen(node *parser.WhenNode) ([]parser.Node, bool, error) { for _, branch := range node.Branches { value, resolved, err := s.evaluate(branch.Condition) if err != nil { return nil, false, err } if !resolved { s.needsIR = true s.unresolved[branch.Condition] = struct{}{} return nil, false, nil } if value.kind != stageBool { return nil, false, shared.NewError(branch.Condition.GetLoc(), "compile-time condition must be boolean") } if value.boolean { body, err := s.selectBody(branch.Body) return body, true, err } } body, err := s.selectBody(node.ElseBody) return body, true, err } func (s *stageSelector) selectWhenValue(node *parser.WhenNode) (parser.Node, bool, error) { for _, branch := range node.Branches { condition, resolved, err := s.evaluate(branch.Condition) if err != nil || !resolved { if err == nil { s.needsIR = true s.unresolved[branch.Condition] = struct{}{} } return nil, resolved, err } if condition.kind != stageBool { return nil, false, shared.NewError(branch.Condition.GetLoc(), "compile-time condition must be boolean") } if condition.boolean { if directive, ok := branch.Value.(*parser.CompilerDirectiveNode); ok { return nil, true, s.evaluateDirective(directive) } if nested, ok := branch.Value.(*parser.WhenNode); ok { return s.selectWhenValue(nested) } return branch.Value, true, nil } } if directive, ok := node.ElseValue.(*parser.CompilerDirectiveNode); ok { return nil, true, s.evaluateDirective(directive) } if nested, ok := node.ElseValue.(*parser.WhenNode); ok { return s.selectWhenValue(nested) } return node.ElseValue, true, nil } func (s *stageSelector) selectLinkItems(items []parser.LinkItemNode) ([]attributes.Link, bool, error) { var links []attributes.Link for _, item := range items { if item.Link != nil { links = append(links, *item.Link) continue } if item.Directive != nil { if err := s.evaluateDirective(item.Directive); err != nil { return nil, false, err } if _, unresolved := s.unresolved[item.Directive.Condition]; unresolved { return nil, false, nil } continue } if item.When != nil { selected, resolved, err := s.selectLinkWhen(item.When) if err != nil || !resolved { return nil, resolved, err } links = append(links, selected...) } } return links, true, nil } func (s *stageSelector) selectLinkWhen(node *parser.LinkWhenNode) ([]attributes.Link, bool, error) { for _, branch := range node.Branches { condition, resolved, err := s.evaluate(branch.Condition) if err != nil || !resolved { if err == nil { s.needsIR = true s.unresolved[branch.Condition] = struct{}{} } return nil, resolved, err } if condition.kind != stageBool { return nil, false, shared.NewError(branch.Condition.GetLoc(), "@link condition must be boolean") } if condition.boolean { return s.selectLinkItems(branch.Items) } } return s.selectLinkItems(node.ElseItems) } func (s *stageSelector) evaluateDirective(node *parser.CompilerDirectiveNode) error { if node.Name == "compiler_error" { return shared.NewError(node.NameLoc, "%s", node.Message) } condition, resolved, err := s.evaluate(node.Condition) if err != nil { return err } if !resolved { s.needsIR = true s.unresolved[node.Condition] = struct{}{} return nil } if condition.kind != stageBool || !condition.boolean { return shared.NewError(node.Loc, "compiler assertion failed: %s", node.Message) } return nil } func stageLiteral(value stageValue, loc shared.Location) parser.ExpressionNode { if value.kind == stageBool { text := string(tokeniser.KeywordFalse) if value.boolean { text = string(tokeniser.KeywordTrue) } return &parser.BoolLiteralNode{Value: text, Loc: loc} } if value.kind == stageFloat { return &parser.FloatLiteralNode{Value: value.floating, Loc: loc} } return &parser.IntegerLiteralNode{Value: value.integer.String(), Loc: loc} } func stageTypeNode(value stageValue, loc shared.Location) parser.TypeNode { primitive, ok := types.Underlying(value.typ).(types.PrimitiveType) if !ok || primitive == types.PrimitiveVoid { return nil } return &parser.NamedTypeNode{Name: string(primitive), Loc: loc} } func (s *stageSelector) evaluate(node parser.ExpressionNode) (stageValue, bool, error) { if value, ok := s.evaluated[node]; ok { return value, true, nil } switch node := node.(type) { case *parser.BoolLiteralNode: return stageValue{kind: stageBool, boolean: node.Value == string(tokeniser.KeywordTrue)}, true, nil case *parser.IntegerLiteralNode: value, ok := new(big.Int).SetString(node.Value, 0) if !ok { return stageValue{}, false, shared.NewError(node.Loc, "invalid compile-time integer") } return stageValue{kind: stageInteger, integer: value}, true, nil case *parser.FloatLiteralNode: return stageValue{kind: stageFloat, floating: node.Value}, true, nil case *parser.EnumLiteralNode: return stageValue{kind: stageEnumLiteral, name: node.Variant}, true, nil case *parser.IdentifierNode: if value, ok := s.targetValue(node.Name); ok { return value, true, nil } value, ok := s.bindings[node.Name] return value, ok, nil case *parser.UnaryOpNode: operand, resolved, err := s.evaluate(node.Operand) if err != nil || !resolved { return stageValue{}, resolved, err } if node.Op == parser.UnaryOpLogicalNot && operand.kind == stageBool { operand.boolean = !operand.boolean return operand, true, nil } if node.Op == parser.UnaryOpNegate && operand.kind == stageInteger { return stageValue{kind: stageInteger, integer: new(big.Int).Neg(operand.integer)}, true, nil } if node.Op == parser.UnaryOpNegate && operand.kind == stageFloat { value := operand.floating if strings.HasPrefix(value, "-") { value = strings.TrimPrefix(value, "-") } else { value = "-" + value } return stageValue{kind: stageFloat, floating: value}, true, nil } return stageValue{}, false, shared.NewError(node.Loc, "invalid compile-time unary operation") case *parser.BinaryOpNode: return s.evaluateBinary(node) case *parser.FunctionCallNode: return stageValue{}, false, nil default: return stageValue{}, false, nil } } func (s *stageSelector) evaluateBinary(node *parser.BinaryOpNode) (stageValue, bool, error) { left, resolved, err := s.evaluate(node.Operand1) if err != nil || !resolved { return stageValue{}, resolved, err } right, resolved, err := s.evaluate(node.Operand2) if err != nil || !resolved { return stageValue{}, resolved, err } switch node.Op { case parser.BinaryOpLogicalAnd, parser.BinaryOpLogicalOr: if left.kind != stageBool || right.kind != stageBool { return stageValue{}, false, shared.NewError(node.Loc, "logical compile-time operation requires booleans") } value := left.boolean && right.boolean if node.Op == parser.BinaryOpLogicalOr { value = left.boolean || right.boolean } return stageValue{kind: stageBool, boolean: value}, true, nil case parser.BinaryOpEqual, parser.BinaryOpNotEqual: equal := stageEqual(left, right) if node.Op == parser.BinaryOpNotEqual { equal = !equal } return stageValue{kind: stageBool, boolean: equal}, true, nil case parser.BinaryOpAdd, parser.BinaryOpSubtract, parser.BinaryOpMultiply, parser.BinaryOpDivide, parser.BinaryOpModulo: if left.kind != stageInteger || right.kind != stageInteger { return stageValue{}, false, shared.NewError(node.Loc, "compile-time arithmetic requires integers") } value := new(big.Int) switch node.Op { case parser.BinaryOpAdd: value.Add(left.integer, right.integer) case parser.BinaryOpSubtract: value.Sub(left.integer, right.integer) case parser.BinaryOpMultiply: value.Mul(left.integer, right.integer) case parser.BinaryOpDivide: if right.integer.Sign() == 0 { return stageValue{}, false, shared.NewError(node.Loc, "division by zero") } value.Quo(left.integer, right.integer) case parser.BinaryOpModulo: if right.integer.Sign() == 0 { return stageValue{}, false, shared.NewError(node.Loc, "division by zero") } value.Rem(left.integer, right.integer) } return stageValue{kind: stageInteger, integer: value}, true, nil case parser.BinaryOpLess, parser.BinaryOpLessEqual, parser.BinaryOpGreater, parser.BinaryOpGreaterEqual: if left.kind != stageInteger || right.kind != stageInteger { return stageValue{}, false, shared.NewError(node.Loc, "compile-time comparison requires integers") } comparison := left.integer.Cmp(right.integer) value := comparison < 0 if node.Op == parser.BinaryOpLessEqual { value = comparison <= 0 } if node.Op == parser.BinaryOpGreater { value = comparison > 0 } if node.Op == parser.BinaryOpGreaterEqual { value = comparison >= 0 } return stageValue{kind: stageBool, boolean: value}, true, nil } return stageValue{}, false, nil } func stageEqual(left, right stageValue) bool { if left.kind == stageEnum && right.kind == stageEnumLiteral { return left.name == right.name } if right.kind == stageEnum && left.kind == stageEnumLiteral { return right.name == left.name } if left.kind != right.kind { return false } if left.kind == stageBool { return left.boolean == right.boolean } if left.kind == stageInteger { return left.integer.Cmp(right.integer) == 0 } if left.kind == stageFloat { return left.floating == right.floating } return left.domain == right.domain && left.name == right.name } func (s *stageSelector) targetValue(name string) (stageValue, bool) { triple := qktarget.EffectiveTriple(s.config.TargetTriple) lower := strings.ToLower(triple) switch name { case "PointerBits": bits, _ := qktarget.PointerBits(triple) return stageValue{kind: stageInteger, integer: big.NewInt(int64(bits))}, true case "CCharSigned": return stageValue{kind: stageBool, boolean: qktarget.CCharSigned(triple)}, true case "ReleaseMode": value := "Debug" if s.config.ReleaseMode == StageRelease { value = "Release" } return stageValue{kind: stageEnum, domain: "ReleaseMode", name: value}, true case "OS": value := "Unknown" switch { case strings.Contains(lower, "windows"), strings.Contains(lower, "mingw"), strings.Contains(lower, "msvc"): value = "Windows" case strings.Contains(lower, "linux"): value = "Linux" case strings.Contains(lower, "darwin"), strings.Contains(lower, "apple"): value = "MacOS" case strings.Contains(lower, "freebsd"): value = "FreeBSD" case strings.Contains(lower, "openbsd"): value = "OpenBSD" case strings.Contains(lower, "netbsd"): value = "NetBSD" case strings.Contains(lower, "dragonfly"): value = "DragonFly" case strings.Contains(lower, "wasi"): value = "WASI" } return stageValue{kind: stageEnum, domain: "OS", name: value}, true case "Arch": value := map[string]string{"386": "X86", "x86_64": "X86_64", "amd64": "X86_64", "aarch64": "AArch64", "arm64": "AArch64", "wasm32": "Wasm32", "wasm64": "Wasm64"}[qktarget.Arch(triple)] return stageValue{kind: stageEnum, domain: "Arch", name: value}, true case "Environment": value := "Unknown" if strings.Contains(lower, "msvc") { value = "MSVC" } else if strings.Contains(lower, "musl") { value = "Musl" } else if strings.Contains(lower, "gnu") || strings.Contains(lower, "mingw") { value = "GNU" } return stageValue{kind: stageEnum, domain: "Environment", name: value}, true case "TargetHasLibc", "TargetHasFilesystem", "TargetHasEnvironment", "TargetHasProcessExit": os, _ := s.targetValue("OS") native := os.name == "Windows" || os.name == "Linux" || os.name == "MacOS" || os.name == "FreeBSD" || os.name == "OpenBSD" || os.name == "NetBSD" || os.name == "DragonFly" return stageValue{kind: stageBool, boolean: native && !(name == "TargetHasLibc" && s.config.NoLibc)}, true } return stageValue{}, false } func (s *stageSelector) evaluateUnresolved(root *parser.RootNode) error { staged := &parser.RootNode{Loc: root.Loc} reachableFunctions := s.reachableStageFunctions(root) hasModule := false for _, node := range root.Body { switch node := node.(type) { case *parser.WhenNode, *parser.CompilerDirectiveNode: continue case *parser.ImportNode: if s.environment != nil { staged.Body = append(staged.Body, node) } case *parser.ModuleNode: if hasModule { continue } hasModule = true copy := *node copy.Attributes = nil staged.Body = append(staged.Body, ©) case *parser.FunctionDefNode: if reachableFunctions[node] { staged.Body = append(staged.Body, node) } default: staged.Body = append(staged.Body, node) } } names := make(map[parser.ExpressionNode]string, len(s.unresolved)) for expression := range s.unresolved { name := fmt.Sprintf("__qk_stage_%d", s.syntheticID) s.syntheticID++ names[expression] = name staged.Body = append(staged.Body, &parser.DeclarationNode{ Name: name, NameLoc: expression.GetLoc(), Value: expression, Comptime: true, Loc: expression.GetLoc(), }) } modules, order, err := s.stageModules(staged) if err != nil { return err } analyser := sema.NewAnalyser() if errors, _ := RunSemanticPipeline(modules, analyser, order, false, false); len(errors) != 0 { return errors[0] } PropagateSpecializationDemands(modules, order) irModules, irErrors := GenerateIRModules(modules, s.module, order, false, false) if len(irErrors) != 0 { return irErrors[0] } templates := make(map[string][]ir.GenericTemplate, len(order)) for _, name := range order { templates[name] = GenerateModuleGenericTemplateIR(modules[name]) } specializations, err := ExtractGenericSpecializations(irModules, templates, analyser.ModuleInterfaces()) if err != nil { return err } allIR := make([]*ir.Module, 0, len(order)+1) for _, name := range order { if moduleIR := irModules[name]; moduleIR != nil { allIR = append(allIR, moduleIR) } } if specializations != nil { allIR = append(allIR, specializations) } evaluator := ir.NewEvaluator(allIR...) moduleIR := irModules[s.module] if moduleIR.Initializer != "" { if _, err := evaluator.Run(moduleIR.Initializer); err != nil { var unavailable *ir.CompileTimeUnavailableError if errors.As(err, &unavailable) && unavailable.SourceLoc.FilePath != "" { return stageUnavailableDiagnostic(unavailable) } if len(names) == 1 { for expression := range names { return shared.NewError(expression.GetLoc(), err.Error()) } } return err } } for expression, name := range names { value, ok := findEvaluatedGlobal(evaluator.Globals, name) if !ok { return shared.NewError(expression.GetLoc(), "compile-time expression did not produce a value") } s.evaluated[expression] = stageValueFromIR(value) } for _, raw := range root.Body { declaration, ok := raw.(*parser.DeclarationNode) if !ok || !declaration.Comptime { continue } if value, ok := findEvaluatedGlobal(evaluator.Globals, declaration.Name); ok { stagedValue := stageValueFromIR(value) s.bindings[declaration.Name] = stagedValue s.evaluated[declaration.Value] = stagedValue } } return nil } func stageUnavailableDiagnostic(unavailable *ir.CompileTimeUnavailableError) error { if len(unavailable.CallStack) == 0 { return shared.NewError(unavailable.SourceLoc, unavailable.Error()) } root := unavailable.CallStack[len(unavailable.CallStack)-1] cause := ir.SourceOrigin{Name: unavailable.SourceName, Loc: unavailable.SourceLoc} if cause.Loc.FilePath != root.Loc.FilePath { for _, call := range unavailable.CallStack[:len(unavailable.CallStack)-1] { if call.Loc.FilePath == root.Loc.FilePath { cause = call break } } } if sameStageLocation(root.Loc, cause.Loc) { return shared.NewError(cause.Loc, "%s is not available in a compile-time context", cause.Name) } return shared.NewError(root.Loc, "%s cannot be evaluated at compile time", root.Name). WithNote(cause.Loc, "evaluation reaches %s, which is not available in a compile-time context", cause.Name) } func sameStageLocation(left, right shared.Location) bool { return left.FilePath == right.FilePath && left.Offset == right.Offset && left.EndOffset == right.EndOffset } func (s *stageSelector) stageModules(staged *parser.RootNode) (map[string]*ModuleInfo, []string, error) { modules := make(map[string]*ModuleInfo) var order []string if s.environment != nil { for _, name := range s.environment.Order { dependency := s.environment.Modules[name] if dependency == nil || name == s.module { continue } modules[name] = &ModuleInfo{ Path: name, Name: dependency.Name, Imports: append([]string(nil), dependency.Imports...), Root: parser.CloneSyntax(dependency.Root).(*parser.RootNode), TrustedStandardLibrary: dependency.TrustedStandardLibrary, } order = append(order, name) } } trusted := s.environment != nil && s.environment.TrustedStandardLibrary currentRoot := parser.CloneSyntax(staged).(*parser.RootNode) partial, err := CollectModuleInfo(currentRoot, trusted) if err != nil { return nil, nil, err } modules[s.module] = &ModuleInfo{ Path: s.module, Name: partial.Name, Imports: partial.Imports, Root: currentRoot, TrustedStandardLibrary: trusted, } order = append(order, s.module) return modules, order, nil } func (s *stageSelector) reachableStageFunctions(root *parser.RootNode) map[*parser.FunctionDefNode]bool { definitions := make(map[string][]*parser.FunctionDefNode) for _, node := range root.Body { if function, ok := node.(*parser.FunctionDefNode); ok { definitions[function.Name] = append(definitions[function.Name], function) } } names := make(map[string]bool) for expression := range s.unresolved { collectStageCalls(expression, names) } result := make(map[*parser.FunctionDefNode]bool) for progress := true; progress; { progress = false for name := range names { for _, function := range definitions[name] { if result[function] { continue } result[function] = true collectStageCalls(function.Body, names) progress = true } } } return result } func collectStageCalls(node parser.Node, names map[string]bool) { if node == nil { return } switch node := node.(type) { case *parser.FunctionCallNode: if identifier, ok := node.Callee.(*parser.IdentifierNode); ok { names[identifier.Name] = true } collectStageCalls(node.Callee, names) for _, argument := range node.Args { collectStageCalls(argument, names) } case *parser.BlockNode: for _, child := range node.Body { collectStageCalls(child, names) } case *parser.DeclarationNode: collectStageCalls(node.Value, names) case *parser.MultiDeclarationNode: collectStageCalls(node.Value, names) case *parser.AssignmentNode: collectStageCalls(node.Value, names) case *parser.ControlKeywordNode: collectStageCalls(node.ReturnValue, names) for _, value := range node.ReturnValues { collectStageCalls(value, names) } case *parser.DeferNode: collectStageCalls(node.Action, names) case *parser.IfNode: collectStageCalls(node.IfBranch.Condition, names) collectStageCalls(node.IfBranch.Node, names) for _, branch := range node.ElseIfBranches { collectStageCalls(branch.Condition, names) collectStageCalls(branch.Node, names) } if node.ElseBranch != nil { collectStageCalls(node.ElseBranch, names) } case *parser.ForNode: for _, child := range node.ExprsOrStmts { collectStageCalls(child, names) } collectStageCalls(node.Body, names) case *parser.RangeForNode: collectStageCalls(node.Start, names) collectStageCalls(node.End, names) collectStageCalls(node.Body, names) case *parser.ForEachNode: collectStageCalls(node.Iterable, names) collectStageCalls(node.Body, names) case *parser.MatchNode: for _, subject := range node.Subjects { collectStageCalls(subject, names) } for _, arm := range node.Arms { collectStageCalls(arm.Guard, names) collectStageCalls(arm.Body, names) } case *parser.UnaryOpNode: collectStageCalls(node.Operand, names) case *parser.BinaryOpNode: collectStageCalls(node.Operand1, names) collectStageCalls(node.Operand2, names) case *parser.IndexExprNode: collectStageCalls(node.Subject, names) collectStageCalls(node.Index, names) case *parser.SliceExprNode: collectStageCalls(node.Subject, names) collectStageCalls(node.Start, names) collectStageCalls(node.End, names) case *parser.CastNode: collectStageCalls(node.Operand, names) case *parser.StructLiteralNode: for _, field := range node.Fields { collectStageCalls(field.R, names) } case *parser.SliceLiteralNode: for _, element := range node.Elements { collectStageCalls(element, names) } collectStageCalls(node.RepeatValue, names) collectStageCalls(node.RepeatAmount, names) } } func findEvaluatedGlobal(globals map[string]ir.EvalValue, sourceName string) (ir.EvalValue, bool) { suffix := "_global_" + sourceName for name, value := range globals { if strings.HasSuffix(name, suffix) { return value, true } } return ir.EvalValue{}, false } func stageValueFromIR(value ir.EvalValue) stageValue { if value.Float != nil { return stageValue{kind: stageFloat, floating: strconv.FormatFloat(*value.Float, 'g', -1, 64), typ: value.Type} } if value.Integer == nil { return stageValue{kind: stageBool, boolean: value.Boolean, typ: value.Type} } return stageValue{kind: stageInteger, integer: new(big.Int).Set(value.Integer), typ: value.Type} }