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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +154 -0
- data/LICENSE +21 -0
- data/README.md +120 -0
- data/Rakefile +59 -0
- data/aura-lang.gemspec +29 -0
- data/bin/aura +151 -0
- data/examples/assistant.aura +12 -0
- data/examples/chatbot.aura +7 -0
- data/examples/hello.aura +10 -0
- data/examples/mnist_classifier.aura +36 -0
- data/examples/sentiment.aura +13 -0
- data/examples/transfer_api.aura +25 -0
- data/lib/aura/analyzer.rb +125 -0
- data/lib/aura/codegen.rb +636 -0
- data/lib/aura/diagnostics.rb +86 -0
- data/lib/aura/docker.rb +81 -0
- data/lib/aura/emitter.rb +61 -0
- data/lib/aura/parser.rb +208 -0
- data/lib/aura/transformer.rb +152 -0
- data/lib/aura/vercel.rb +88 -0
- data/lib/aura/version.rb +8 -0
- data/lib/aura.rb +125 -0
- metadata +158 -0
|
@@ -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
|