aura-lang 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aura
4
+ # Lightweight semantic pass over the node list. Catches the class of errors a
5
+ # context-free grammar can't: referencing a model in `train`/`evaluate`/`route`
6
+ # that was never defined. Stays silent for valid programs (only raises),
7
+ # which keeps `Aura.transpile` free of side-effects.
8
+ class Analyzer
9
+ KNOWN_PROVIDERS = %i[openai ollama].freeze
10
+ HTTP_VERBS = %w[get post put patch delete options head].freeze
11
+
12
+ def self.analyze(nodes)
13
+ new(nodes).analyze
14
+ end
15
+
16
+ def initialize(nodes)
17
+ @nodes = nodes
18
+ end
19
+
20
+ TRAINABLE_KINDS = %i[torch transfer].freeze
21
+
22
+ def analyze
23
+ models = @nodes.select { |n| n[:type] == :model }
24
+ defined_models = models.map { |n| n[:name] }
25
+ kinds = models.each_with_object({}) { |n, h| h[n[:name]] = n[:kind] }
26
+ check_unique_models!(defined_models)
27
+ check_unique_routes!
28
+ check_unique_environment!
29
+ check_providers!
30
+ check_http_verbs!
31
+
32
+ @nodes.each do |node|
33
+ case node[:type]
34
+ when :train
35
+ require_model!(defined_models, node[:model], "train")
36
+ require_trainable!(kinds, node[:model], "train")
37
+ when :evaluate
38
+ require_model!(defined_models, node[:model], "evaluate")
39
+ require_trainable!(kinds, node[:model], "evaluate")
40
+ when :route
41
+ require_model!(defined_models, node[:model], "route #{node[:path]}") if node[:model]
42
+ end
43
+ end
44
+
45
+ @nodes
46
+ end
47
+
48
+ private
49
+
50
+ # Only neural-network / transfer models have a trainable `<name>_model`;
51
+ # training an LLM or text model would emit `<name>_model.train` -> NameError.
52
+ def require_trainable!(kinds, name, context)
53
+ kind = kinds[name]
54
+ return if kind.nil? || TRAINABLE_KINDS.include?(kind)
55
+
56
+ raise SemanticError,
57
+ "Cannot #{context} model '#{name}' (kind: #{kind}). Only neural_network and " \
58
+ "transfer models can be trained/evaluated."
59
+ end
60
+
61
+ # A single `environment` block is generated into one `AuraConfig`; multiple
62
+ # blocks would redefine the same class/constant.
63
+ def check_unique_environment!
64
+ envs = @nodes.count { |n| n[:type] == :environment }
65
+ return if envs <= 1
66
+
67
+ raise SemanticError, "Multiple `environment` blocks found (#{envs}); declare at most one."
68
+ end
69
+
70
+ # LLM models compile to a provider-specific HTTP client; an unknown provider
71
+ # would otherwise be silently treated as OpenAI.
72
+ def check_providers!
73
+ @nodes.each do |node|
74
+ next unless node[:type] == :model && node[:kind] == :llm
75
+ next if KNOWN_PROVIDERS.include?(node[:provider])
76
+
77
+ raise SemanticError,
78
+ "Unknown LLM provider '#{node[:provider]}' for model '#{node[:name]}'. " \
79
+ "Supported: #{KNOWN_PROVIDERS.join(', ')}."
80
+ end
81
+ end
82
+
83
+ # A route's verb becomes a Sinatra DSL method; reject anything that isn't a
84
+ # real HTTP verb so it fails at compile time, not at boot.
85
+ def check_http_verbs!
86
+ @nodes.each do |node|
87
+ next unless node[:type] == :route
88
+ next if HTTP_VERBS.include?(node[:method].to_s.downcase)
89
+
90
+ raise SemanticError,
91
+ "Unsupported HTTP method '#{node[:method]}' in route #{node[:path]}. " \
92
+ "Supported: #{HTTP_VERBS.join(', ')}."
93
+ end
94
+ end
95
+
96
+ # A model name must be unique: two definitions would both generate the same
97
+ # class/accessor, silently shadowing one another.
98
+ def check_unique_models!(names)
99
+ dups = names.group_by(&:itself).select { |_, v| v.size > 1 }.keys
100
+ return if dups.empty?
101
+
102
+ raise SemanticError,
103
+ "Duplicate model name#{'s' if dups.size > 1}: #{dups.join(', ')}. Each model must have a unique name."
104
+ end
105
+
106
+ # The same HTTP verb + path can't be routed twice -- the second handler
107
+ # would be unreachable.
108
+ def check_unique_routes!
109
+ seen = {}
110
+ @nodes.select { |n| n[:type] == :route }.each do |route|
111
+ key = "#{route[:method].to_s.upcase} #{route[:path]}"
112
+ raise SemanticError, "Duplicate route: #{key} is defined more than once." if seen[key]
113
+
114
+ seen[key] = true
115
+ end
116
+ end
117
+
118
+ def require_model!(defined_models, name, context)
119
+ return if name.nil? || defined_models.include?(name)
120
+
121
+ known = defined_models.empty? ? "(none)" : defined_models.join(", ")
122
+ raise SemanticError, "Undefined model '#{name}' referenced in #{context}. Defined models: #{known}"
123
+ end
124
+ end
125
+ end