gloss 0.0.5 → 0.0.6
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 +4 -4
- data/.gitattributes +3 -0
- data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
- data/.github/workflows/{ruby.yml → ruby_specs.yml} +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +36 -5
- data/exe/gloss +15 -1
- data/ext/gloss/src/cr_ast.cr +0 -6
- data/ext/gloss/src/gloss.cr +0 -1
- data/lib/gloss.rb +1 -0
- data/lib/gloss/builder.rb +17 -5
- data/lib/gloss/cli.rb +21 -4
- data/lib/gloss/initializer.rb +2 -1
- data/lib/gloss/logger.rb +34 -0
- data/lib/gloss/parser.rb +17 -2
- data/lib/gloss/type_checker.rb +40 -10
- data/lib/gloss/version.rb +1 -1
- data/lib/gloss/watcher.rb +41 -19
- data/src/lib/gloss/builder.gl +8 -6
- data/src/lib/gloss/cli.gl +14 -3
- data/src/lib/gloss/initializer.gl +1 -1
- data/src/lib/gloss/logger.gl +26 -0
- data/src/lib/gloss/parser.gl +17 -5
- data/src/lib/gloss/type_checker.gl +49 -21
- data/src/lib/gloss/version.gl +1 -1
- data/src/lib/gloss/watcher.gl +41 -22
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9315ca2d8d487ca5729f7665abcba9e922ec4e0ee85ac54787248d07b54b7ed5
|
4
|
+
data.tar.gz: 78928faa1a7e77e0184594a31786c0befdc796a4a9b46c5f509adb95c39d37c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3178c5cf74e3946e327cbe97b5c5f421090e7b678d331c34ce5c8a45aea137cdc9628f91481425e18f66cf3245983d1e5bf965feaae211d619d8d9f5ed55647
|
7
|
+
data.tar.gz: 9d9b5ef709cffe8efb6b0124762bbe025948caa51772cf33a38143862003048f15ee83198c2fd0f6a05128f14da65a9feb22b45205563b686bacb70d51dd5741
|
data/.gitattributes
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
# Gloss
|
2
|
+
[](https://rubygems.org/gems/gloss)
|
3
|
+
[](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Ruby+Specs%22)
|
4
|
+
[](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Crystal+Specs%22)
|
5
|
+
[&total_label=)](https://rubygems.org/gems/gloss)
|
6
|
+
[&metric=true)](https://rubygems.org/gems/gloss)
|
7
|
+
[](https://rubygems.org/gems/gloss)
|
2
8
|
|
3
9
|
[Gloss](https://en.wikipedia.org/wiki/Gloss_(annotation)) is a high-level programming language based on [Ruby](https://github.com/ruby/ruby) and [Crystal](https://github.com/crystal-lang/crystal), which compiles to ruby; its aims are on transparency,
|
4
|
-
efficiency, and to enhance ruby's goal of developer happiness and productivity.
|
10
|
+
efficiency, and to enhance ruby's goal of developer happiness and productivity.
|
11
|
+
|
12
|
+
### Current features
|
5
13
|
|
6
14
|
- Type checking, via optional type annotations
|
7
15
|
- Compile-time macros
|
@@ -10,11 +18,26 @@ efficiency, and to enhance ruby's goal of developer happiness and productivity.
|
|
10
18
|
- All ruby files are valid gloss files (a small exceptions for now; workarounds are mostly available)
|
11
19
|
- Other syntactic sugar
|
12
20
|
|
13
|
-
|
14
|
-
|
21
|
+
### Current Status
|
22
|
+
|
23
|
+
This project is at a stage where the core non-crystal parts are written in Gloss and compile to ruby (essentially self-hosting), albeit with the type checking being fairly loose. However the project is still in the very early stages; with (as of yet) no Linux support nor error handling (see roadmap below). Use at your own discretion!
|
24
|
+
|
25
|
+
### Approx. roadmap:
|
26
|
+
|
27
|
+
- Improve error handling and logging (currently almost non-existant)
|
28
|
+
- Address Linux compatibility (currently more or less non-existant)
|
29
|
+
- Implement different strictnesses of type checking
|
30
|
+
- Metaprogramming helpers/safety:*
|
31
|
+
- Abstract classes and methods
|
32
|
+
- Method lookup/existence checking at compile time
|
33
|
+
- Method overloading
|
34
|
+
|
35
|
+
#### Related items:
|
36
|
+
|
37
|
+
- Rails helpers; probably some time away*
|
38
|
+
- Editor plugins/syntax highlighting/langserver; probably some time away*
|
15
39
|
|
16
|
-
|
17
|
-
- Method overloading
|
40
|
+
*__Dependent on popularity__
|
18
41
|
|
19
42
|
## Examples:
|
20
43
|
|
@@ -223,4 +246,12 @@ then
|
|
223
246
|
|
224
247
|
then
|
225
248
|
|
249
|
+
`mkdir src && echo "puts 'hello world'" > src/hello_world.gl`
|
250
|
+
|
251
|
+
then
|
252
|
+
|
226
253
|
`gloss build`
|
254
|
+
|
255
|
+
then
|
256
|
+
|
257
|
+
`ruby ./hello_world.rb`
|
data/exe/gloss
CHANGED
@@ -3,4 +3,18 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "gloss"
|
5
5
|
|
6
|
-
|
6
|
+
begin
|
7
|
+
Gloss::CLI.new(ARGV).run
|
8
|
+
rescue SystemExit
|
9
|
+
# raised by `abort` or `exit`; no op
|
10
|
+
rescue => e
|
11
|
+
Gloss.logger.fatal <<~MSG
|
12
|
+
Unexpected error: #{e.class.name}
|
13
|
+
Message: #{e.message}
|
14
|
+
Trace:
|
15
|
+
#{e.backtrace.join("\n")}
|
16
|
+
|
17
|
+
This is probably a bug and may warrant a bug report at https://github.com/johansenja/gloss/issues
|
18
|
+
MSG
|
19
|
+
exit 1
|
20
|
+
end
|
data/ext/gloss/src/cr_ast.cr
CHANGED
@@ -235,12 +235,6 @@ module Crystal
|
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
238
|
-
class MacroExpression < ASTNode
|
239
|
-
def to_rb
|
240
|
-
Rb::AST::EmptyNode.new(self.class.name)
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
238
|
class MacroIf < ASTNode
|
245
239
|
def to_rb
|
246
240
|
Rb::AST::MacroIf.new(@cond.to_rb, @then.to_rb, @else.to_rb)
|
data/ext/gloss/src/gloss.cr
CHANGED
data/lib/gloss.rb
CHANGED
data/lib/gloss/builder.rb
CHANGED
@@ -107,7 +107,14 @@ case node.[](:"type")
|
|
107
107
|
src.write_ln("end")
|
108
108
|
when "DefNode"
|
109
109
|
args = render_args(node)
|
110
|
-
|
110
|
+
receiver = (if node.[](:"receiver")
|
111
|
+
visit_node(node.[](:"receiver"))
|
112
|
+
else
|
113
|
+
nil
|
114
|
+
end)
|
115
|
+
src.write_ln("def #{(if receiver
|
116
|
+
"#{receiver}."
|
117
|
+
end)}#{node.[](:"name")}#{args.[](:"representation")}")
|
111
118
|
return_type = (if node.[](:"return_type")
|
112
119
|
RBS::Types::ClassInstance.new(name: RBS::TypeName.new(name: eval(visit_node(node.[](:"return_type")))
|
113
120
|
.to_s
|
@@ -121,7 +128,11 @@ case node.[](:"type")
|
|
121
128
|
nil
|
122
129
|
end), location: node.[](:"location"))]
|
123
130
|
method_definition = RBS::AST::Members::MethodDefinition.new(name: node.[](:"name")
|
124
|
-
.to_sym, kind:
|
131
|
+
.to_sym, kind: (if receiver
|
132
|
+
:"class"
|
133
|
+
else
|
134
|
+
:"instance"
|
135
|
+
end), types: method_types, annotations: EMPTY_ARRAY, location: node.[](:"location"), comment: node.[](:"comment"), overload: false)
|
125
136
|
(if @current_scope
|
126
137
|
@current_scope.members
|
127
138
|
.<<(method_definition)
|
@@ -197,7 +208,7 @@ EMPTY_ARRAY }
|
|
197
208
|
else
|
198
209
|
".."
|
199
210
|
end)
|
200
|
-
src.write("(", visit_node(node.[](:"from")), dots, visit_node(node.[](:"to")), ")")
|
211
|
+
src.write("(", "(", visit_node(node.[](:"from")), ")", dots, "(", visit_node(node.[](:"to")), ")", ")")
|
201
212
|
when "LiteralNode"
|
202
213
|
src.write(node.[](:"value"))
|
203
214
|
when "ArrayLiteral"
|
@@ -216,7 +227,7 @@ EMPTY_ARRAY }
|
|
216
227
|
str.<<(case c.[](:"type")
|
217
228
|
when "LiteralNode"
|
218
229
|
c.[](:"value")
|
219
|
-
.[]((1
|
230
|
+
.[](((1)...(-1)))
|
220
231
|
else
|
221
232
|
["\#{", visit_node(c)
|
222
233
|
.strip, "}"].join
|
@@ -278,11 +289,12 @@ EMPTY_ARRAY }
|
|
278
289
|
key = case k
|
279
290
|
when String
|
280
291
|
k.to_sym
|
292
|
+
.inspect
|
281
293
|
else
|
282
294
|
visit_node(k)
|
283
295
|
end
|
284
296
|
value = visit_node(v)
|
285
|
-
"#{key
|
297
|
+
"#{key} => #{value}" }
|
286
298
|
src.write("{#{contents.join(",\n")}}")
|
287
299
|
(if node.[](:"frozen")
|
288
300
|
src.write(".freeze")
|
data/lib/gloss/cli.rb
CHANGED
@@ -11,10 +11,24 @@ module Gloss
|
|
11
11
|
end
|
12
12
|
def run()
|
13
13
|
command = @argv.first
|
14
|
-
files = @argv.[]((1
|
14
|
+
files = @argv.[](((1)..(-1)))
|
15
15
|
err_msg = catch(:"error") { ||
|
16
16
|
case command
|
17
17
|
when "watch"
|
18
|
+
files = files.map() { |f|
|
19
|
+
path = (if Pathname.new(f)
|
20
|
+
.absolute?
|
21
|
+
f
|
22
|
+
else
|
23
|
+
File.join(Dir.pwd, f)
|
24
|
+
end)
|
25
|
+
(if Pathname.new(path)
|
26
|
+
.exist?
|
27
|
+
path
|
28
|
+
else
|
29
|
+
throw(:"error", "Pathname #{f} does not exist")
|
30
|
+
end)
|
31
|
+
}
|
18
32
|
Watcher.new(files)
|
19
33
|
.watch
|
20
34
|
when "build"
|
@@ -24,7 +38,8 @@ case command
|
|
24
38
|
files
|
25
39
|
end)
|
26
40
|
.each() { |fp|
|
27
|
-
|
41
|
+
Gloss.logger
|
42
|
+
.info("Building #{fp}")
|
28
43
|
content = File.read(fp)
|
29
44
|
tree_hash = Parser.new(content)
|
30
45
|
.run
|
@@ -32,7 +47,8 @@ case command
|
|
32
47
|
rb_output = Builder.new(tree_hash, type_checker)
|
33
48
|
.run
|
34
49
|
type_checker.run(rb_output)
|
35
|
-
|
50
|
+
Gloss.logger
|
51
|
+
.info("Writing #{fp}")
|
36
52
|
Writer.new(rb_output, fp)
|
37
53
|
.run
|
38
54
|
}
|
@@ -51,7 +67,8 @@ case command
|
|
51
67
|
end
|
52
68
|
nil }
|
53
69
|
(if err_msg
|
54
|
-
|
70
|
+
Gloss.logger.fatal(err_msg)
|
71
|
+
exit(1)
|
55
72
|
end)
|
56
73
|
end
|
57
74
|
end
|
data/lib/gloss/initializer.rb
CHANGED
data/lib/gloss/logger.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
5
|
+
|
6
|
+
module Gloss
|
7
|
+
def self.logger()
|
8
|
+
(if @logger
|
9
|
+
@logger
|
10
|
+
else
|
11
|
+
env_log_level = ENV.fetch("LOG_LEVEL") { ||
|
12
|
+
"INFO" }
|
13
|
+
real_log_level = {"UNKNOWN" => Logger::UNKNOWN,
|
14
|
+
"FATAL" => Logger::FATAL,
|
15
|
+
"ERROR" => Logger::ERROR,
|
16
|
+
"WARN" => Logger::WARN,
|
17
|
+
"INFO" => Logger::INFO,
|
18
|
+
"DEBUG" => Logger::DEBUG,
|
19
|
+
"NIL" => nil,
|
20
|
+
nil => nil,
|
21
|
+
"" => nil}.fetch(env_log_level)
|
22
|
+
@logger = Logger.new((if real_log_level
|
23
|
+
STDOUT
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end))
|
27
|
+
formatter = Logger::Formatter.new
|
28
|
+
@logger.formatter=(proc() { |severity, datetime, progname, msg|
|
29
|
+
formatter.call(severity, datetime, progname, msg)
|
30
|
+
})
|
31
|
+
@logger
|
32
|
+
end)
|
33
|
+
end
|
34
|
+
end
|
data/lib/gloss/parser.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
4
|
##### See src/ to make changes
|
5
5
|
|
6
|
-
|
6
|
+
module Gloss
|
7
7
|
class Parser
|
8
8
|
def initialize(str)
|
9
9
|
@str = str
|
@@ -13,7 +13,22 @@
|
|
13
13
|
begin
|
14
14
|
JSON.parse(tree_json, symbolize_names: true)
|
15
15
|
rescue JSON::ParserError
|
16
|
-
|
16
|
+
error_message = tree_json
|
17
|
+
error_message.match(/.+\s:(\d+)$/)
|
18
|
+
(if $~.[](1)
|
19
|
+
line_number = $~.[](1)
|
20
|
+
.to_i
|
21
|
+
context = @str.lines
|
22
|
+
.[]((( line_number.-(2)
|
23
|
+
)..(line_number)))
|
24
|
+
.map
|
25
|
+
.with_index() { |line, index|
|
26
|
+
"#{index.-(1)
|
27
|
+
.+(line_number)}| #{line}" }
|
28
|
+
.join
|
29
|
+
error_message = "#{context.rstrip}\n\n#{error_message}\n"
|
30
|
+
end)
|
31
|
+
throw(:"error", error_message)
|
17
32
|
end
|
18
33
|
end
|
19
34
|
end
|
data/lib/gloss/type_checker.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
4
|
##### See src/ to make changes
|
5
5
|
|
6
|
-
|
6
|
+
require "pry-byebug"
|
7
|
+
module Gloss
|
7
8
|
class TypeChecker
|
8
9
|
Project = Struct.new(:"targets")
|
9
10
|
attr_reader(:"steep_target", :"top_level_decls")
|
@@ -15,30 +16,40 @@
|
|
15
16
|
@top_level_decls = {}
|
16
17
|
end
|
17
18
|
def run(rb_str)
|
18
|
-
|
19
|
-
|
19
|
+
begin
|
20
|
+
valid_types = check_types(rb_str)
|
21
|
+
rescue ParseError => e
|
22
|
+
throw(:"error", "")
|
23
|
+
rescue => e
|
24
|
+
throw(:"error", "Type checking Error: #{e.message} (#{e.class})")
|
25
|
+
end
|
26
|
+
unless valid_types
|
27
|
+
errors = @steep_target.errors
|
20
28
|
.map() { |e|
|
21
29
|
case e
|
22
|
-
when Steep::
|
30
|
+
when Steep::Diagnostic::Ruby::NoMethod
|
23
31
|
"Unknown method :#{e.method}, location: #{e.type
|
24
32
|
.location
|
25
33
|
.inspect}"
|
26
|
-
when Steep::
|
34
|
+
when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
|
27
35
|
"Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
|
28
|
-
when Steep::
|
29
|
-
"
|
36
|
+
when Steep::Diagnostic::Ruby::IncompatibleArguments
|
37
|
+
"Invalid argmuents - method type: #{e.method_type}\nmethod name: #{e.method_type
|
30
38
|
.method_decls
|
31
39
|
.first
|
32
40
|
.method_name}"
|
33
|
-
when Steep::
|
41
|
+
when Steep::Diagnostic::Ruby::ReturnTypeMismatch
|
34
42
|
"Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
|
35
|
-
when Steep::
|
43
|
+
when Steep::Diagnostic::Ruby::IncompatibleAssignment
|
36
44
|
"Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
|
45
|
+
when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
|
46
|
+
"Unexpected block given"
|
37
47
|
else
|
38
48
|
e.inspect
|
39
49
|
end
|
40
50
|
}
|
41
|
-
.join("\n")
|
51
|
+
.join("\n")
|
52
|
+
throw(:"error", errors)
|
42
53
|
end
|
43
54
|
true
|
44
55
|
end
|
@@ -58,6 +69,25 @@ true
|
|
58
69
|
env = env.resolve_type_names
|
59
70
|
@steep_target.instance_variable_set("@environment", env)
|
60
71
|
@steep_target.type_check
|
72
|
+
(if @steep_target.status
|
73
|
+
.is_a?(Steep::Project::Target::SignatureErrorStatus)
|
74
|
+
throw(:"error", @steep_target.status
|
75
|
+
.errors
|
76
|
+
.map() { |e|
|
77
|
+
" SignatureSyntaxError:\n Location: #{e.location}\n Message: \"#{e.exception
|
78
|
+
.error_value
|
79
|
+
.value}\"" }
|
80
|
+
.join("\n"))
|
81
|
+
end)
|
82
|
+
@steep_target.source_files
|
83
|
+
.each() { |path, f|
|
84
|
+
(if f.status
|
85
|
+
.is_a?(Steep::Project::SourceFile::ParseErrorStatus)
|
86
|
+
e = f.status
|
87
|
+
.error
|
88
|
+
throw(:"error", "#{e.class}: #{e.message}")
|
89
|
+
end)
|
90
|
+
}
|
61
91
|
@steep_target.status
|
62
92
|
.is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
|
63
93
|
.empty?
|
data/lib/gloss/version.rb
CHANGED
data/lib/gloss/watcher.rb
CHANGED
@@ -3,50 +3,72 @@
|
|
3
3
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
4
|
##### See src/ to make changes
|
5
5
|
|
6
|
-
|
6
|
+
require "listen"
|
7
7
|
module Gloss
|
8
8
|
class Watcher
|
9
9
|
def initialize(paths)
|
10
10
|
@paths = paths
|
11
11
|
(if @paths.empty?
|
12
12
|
@paths = [File.join(Dir.pwd, Config.src_dir)]
|
13
|
+
@only = /\.gl$/
|
14
|
+
else
|
15
|
+
file_names = Array.new
|
16
|
+
paths = Array.new
|
17
|
+
@paths.each() { |pa|
|
18
|
+
pn = Pathname.new(pa)
|
19
|
+
paths.<<(pn.parent
|
20
|
+
.to_s)
|
21
|
+
file_names.<<((if pn.file?
|
22
|
+
pn.basename
|
23
|
+
.to_s
|
24
|
+
else
|
25
|
+
pa
|
26
|
+
end))
|
27
|
+
}
|
28
|
+
@paths = paths.uniq
|
29
|
+
@only = /#{Regexp.union(file_names)}/
|
13
30
|
end)
|
14
31
|
end
|
15
32
|
def watch()
|
16
|
-
|
17
|
-
|
33
|
+
Gloss.logger
|
34
|
+
.info("Now listening for changes in #{@paths.join(", ")}")
|
35
|
+
listener = Listen.to(*@paths, latency: 2, only: @only) { |modified, added, removed|
|
18
36
|
modified.+(added)
|
19
37
|
.each() { |f|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
puts("====> Rewriting #{f}")
|
38
|
+
Gloss.logger
|
39
|
+
.info("Rewriting #{f}")
|
24
40
|
content = File.read(f)
|
25
|
-
|
41
|
+
err = catch(:"error") { ||
|
42
|
+
Writer.new(Builder.new(Parser.new(content)
|
26
43
|
.run)
|
27
44
|
.run, f)
|
28
45
|
.run
|
29
|
-
|
46
|
+
nil }
|
47
|
+
(if err
|
48
|
+
Gloss.logger
|
49
|
+
.error(err)
|
50
|
+
else
|
51
|
+
Gloss.logger
|
52
|
+
.info("Done")
|
53
|
+
end)
|
30
54
|
}
|
31
55
|
removed.each() { |f|
|
32
|
-
unless f.end_with?(".gl")
|
33
|
-
next
|
34
|
-
end
|
35
56
|
out_path = Utils.src_path_to_output_path(f)
|
36
|
-
|
57
|
+
Gloss.logger
|
58
|
+
.info("Removing #{out_path}")
|
37
59
|
(if File.exist?(out_path)
|
38
60
|
File.delete(out_path)
|
39
61
|
end)
|
40
|
-
|
62
|
+
Gloss.logger
|
63
|
+
.info("Done")
|
41
64
|
}
|
42
65
|
}
|
43
|
-
listener.start
|
44
66
|
begin
|
45
|
-
|
46
|
-
|
47
|
-
}
|
67
|
+
listener.start
|
68
|
+
sleep
|
48
69
|
rescue Interrupt
|
49
|
-
|
70
|
+
Gloss.logger
|
71
|
+
.info("Interrupt signal received, shutting down")
|
50
72
|
exit(0)
|
51
73
|
end
|
52
74
|
end
|
data/src/lib/gloss/builder.gl
CHANGED
@@ -113,7 +113,8 @@ module Gloss
|
|
113
113
|
src.write_ln "end"
|
114
114
|
when "DefNode"
|
115
115
|
args = render_args(node)
|
116
|
-
|
116
|
+
receiver = node[:receiver] ? visit_node(node[:receiver]) : nil
|
117
|
+
src.write_ln "def #{"#{receiver}." if receiver}#{node[:name]}#{args[:representation]}"
|
117
118
|
|
118
119
|
return_type = if node[:return_type]
|
119
120
|
RBS::Types::ClassInstance.new(
|
@@ -162,7 +163,7 @@ module Gloss
|
|
162
163
|
]
|
163
164
|
method_definition = RBS::AST::Members::MethodDefinition.new(
|
164
165
|
name: node[:name].to_sym,
|
165
|
-
kind: :instance,
|
166
|
+
kind: receiver ? :class : :instance,
|
166
167
|
types: method_types,
|
167
168
|
annotations: EMPTY_ARRAY, # TODO
|
168
169
|
location: node[:location],
|
@@ -215,9 +216,10 @@ module Gloss
|
|
215
216
|
when "RangeLiteral"
|
216
217
|
dots = node[:exclusive] ? "..." : ".."
|
217
218
|
|
218
|
-
# parentheses help the compatibility with precendence of operators in some situations
|
219
|
+
# parentheses around the whole thing help the compatibility with precendence of operators in some situations
|
219
220
|
# eg. (1..3).cover? 2 vs. 1..3.cover? 2
|
220
|
-
|
221
|
+
# parentheses around either number help with things like arithemtic ((x-1)..(y+5))
|
222
|
+
src.write "(", "(", visit_node(node[:from]), ")", dots, "(", visit_node(node[:to]), ")", ")"
|
221
223
|
|
222
224
|
when "LiteralNode"
|
223
225
|
|
@@ -296,12 +298,12 @@ module Gloss
|
|
296
298
|
contents = node[:elements].map do |k, v|
|
297
299
|
key = case k
|
298
300
|
when String
|
299
|
-
k.to_sym
|
301
|
+
k.to_sym.inspect
|
300
302
|
else
|
301
303
|
visit_node k
|
302
304
|
end
|
303
305
|
value = visit_node v
|
304
|
-
"#{key
|
306
|
+
"#{key} => #{value}"
|
305
307
|
end
|
306
308
|
|
307
309
|
src.write "{#{contents.join(",\n")}}"
|
data/src/lib/gloss/cli.gl
CHANGED
@@ -13,17 +13,25 @@ module Gloss
|
|
13
13
|
err_msg = catch :error do
|
14
14
|
case command
|
15
15
|
when "watch"
|
16
|
+
files = files.map do |f|
|
17
|
+
path = Pathname.new(f).absolute? ? f : File.join(Dir.pwd, f)
|
18
|
+
if Pathname.new(path).exist?
|
19
|
+
path
|
20
|
+
else
|
21
|
+
throw :error, "Pathname #{f} does not exist"
|
22
|
+
end
|
23
|
+
end
|
16
24
|
Watcher.new(files).watch
|
17
25
|
when "build"
|
18
26
|
(files.empty? ? Dir.glob("#{Config.src_dir}/**/*.gl") : files).each do |fp|
|
19
|
-
|
27
|
+
Gloss.logger.info "Building #{fp}"
|
20
28
|
content = File.read(fp)
|
21
29
|
tree_hash = Parser.new(content).run
|
22
30
|
type_checker = TypeChecker.new
|
23
31
|
rb_output = Builder.new(tree_hash, type_checker).run
|
24
32
|
type_checker.run(rb_output)
|
25
33
|
|
26
|
-
|
34
|
+
Gloss.logger.info "Writing #{fp}"
|
27
35
|
Writer.new(rb_output, fp).run
|
28
36
|
end
|
29
37
|
when "init"
|
@@ -38,7 +46,10 @@ module Gloss
|
|
38
46
|
nil
|
39
47
|
end
|
40
48
|
|
41
|
-
|
49
|
+
if err_msg
|
50
|
+
Gloss.logger.fatal err_msg
|
51
|
+
exit 1
|
52
|
+
end
|
42
53
|
end
|
43
54
|
end
|
44
55
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Gloss
|
2
|
+
def self.logger
|
3
|
+
if @logger
|
4
|
+
@logger
|
5
|
+
else
|
6
|
+
env_log_level = ENV.fetch("LOG_LEVEL") { "INFO" }
|
7
|
+
real_log_level = {
|
8
|
+
"UNKNOWN" => Logger::UNKNOWN,
|
9
|
+
"FATAL" => Logger::FATAL,
|
10
|
+
"ERROR" => Logger::ERROR,
|
11
|
+
"WARN" => Logger::WARN,
|
12
|
+
"INFO" => Logger::INFO,
|
13
|
+
"DEBUG" => Logger::DEBUG,
|
14
|
+
"NIL" => nil,
|
15
|
+
nil => nil,
|
16
|
+
"" => nil
|
17
|
+
}.fetch env_log_level
|
18
|
+
@logger = Logger.new(real_log_level ? STDOUT : nil)
|
19
|
+
formatter = Logger::Formatter.new
|
20
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
21
|
+
formatter.call(severity, datetime, progname, msg)
|
22
|
+
end
|
23
|
+
@logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/src/lib/gloss/parser.gl
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Gloss
|
4
2
|
class Parser
|
5
3
|
def initialize(@str : String)
|
@@ -10,9 +8,23 @@ module Gloss
|
|
10
8
|
begin
|
11
9
|
JSON.parse tree_json, symbolize_names: true
|
12
10
|
rescue JSON::ParserError
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
error_message = tree_json
|
12
|
+
error_message.match /.+\s:(\d+)$/
|
13
|
+
if $1
|
14
|
+
line_number = $1.to_i
|
15
|
+
# line numbers start at 1, but array index starts at 0; so this still gives one line
|
16
|
+
# either side of the offending line
|
17
|
+
context = @str.lines[(line_number - 2)..(line_number)].map.with_index { |line, index|
|
18
|
+
"#{index - 1 + line_number}| #{line}"
|
19
|
+
}.join
|
20
|
+
error_message = <<~MSG
|
21
|
+
#{context.rstrip}
|
22
|
+
|
23
|
+
#{error_message}
|
24
|
+
|
25
|
+
MSG
|
26
|
+
end
|
27
|
+
throw :error, error_message
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "pry-byebug"
|
2
3
|
|
3
4
|
module Gloss
|
4
5
|
class TypeChecker
|
@@ -20,27 +21,37 @@ module Gloss
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def run(rb_str)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
24
|
+
begin
|
25
|
+
valid_types = check_types rb_str
|
26
|
+
rescue ParseError => e
|
27
|
+
throw :error, ""
|
28
|
+
rescue => e
|
29
|
+
throw :error, "Type checking Error: #{e.message} (#{e.class})"
|
30
|
+
end
|
31
|
+
|
32
|
+
unless valid_types
|
33
|
+
errors = @steep_target.errors.map { |e|
|
34
|
+
case e
|
35
|
+
when Steep::Diagnostic::Ruby::NoMethod
|
36
|
+
"Unknown method :#{e.method}, location: #{e.type.location.inspect}"
|
37
|
+
when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
|
38
|
+
"Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
|
39
|
+
when Steep::Diagnostic::Ruby::IncompatibleArguments
|
40
|
+
<<-ERR
|
41
|
+
Invalid argmuents - method type: #{e.method_type}
|
42
|
+
method name: #{e.method_type.method_decls.first.method_name}
|
43
|
+
ERR
|
44
|
+
when Steep::Diagnostic::Ruby::ReturnTypeMismatch
|
45
|
+
"Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
|
46
|
+
when Steep::Diagnostic::Ruby::IncompatibleAssignment
|
47
|
+
"Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
|
48
|
+
when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
|
49
|
+
"Unexpected block given"
|
50
|
+
else
|
51
|
+
e.inspect
|
52
|
+
end
|
53
|
+
}.join("\n")
|
54
|
+
throw :error, errors
|
44
55
|
end
|
45
56
|
|
46
57
|
true
|
@@ -65,6 +76,23 @@ module Gloss
|
|
65
76
|
|
66
77
|
@steep_target.type_check
|
67
78
|
|
79
|
+
if @steep_target.status.is_a? Steep::Project::Target::SignatureErrorStatus
|
80
|
+
throw :error, @steep_target.status.errors.map { |e|
|
81
|
+
<<~MSG
|
82
|
+
SignatureSyntaxError:
|
83
|
+
Location: #{e.location}
|
84
|
+
Message: "#{e.exception.error_value.value}"
|
85
|
+
MSG
|
86
|
+
}.join("\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
@steep_target.source_files.each do |path, f|
|
90
|
+
if f.status.is_a? Steep::Project::SourceFile::ParseErrorStatus
|
91
|
+
e = f.status.error
|
92
|
+
throw :error, "#{e.class}: #{e.message}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
68
96
|
@steep_target.status.is_a?(Steep::Project::Target::TypeCheckStatus) &&
|
69
97
|
@steep_target.no_error? &&
|
70
98
|
@steep_target.errors.empty?
|
data/src/lib/gloss/version.gl
CHANGED
data/src/lib/gloss/watcher.gl
CHANGED
@@ -5,42 +5,61 @@ require "listen"
|
|
5
5
|
module Gloss
|
6
6
|
class Watcher
|
7
7
|
def initialize(@paths : Array[String])
|
8
|
-
|
8
|
+
if @paths.empty?
|
9
|
+
@paths = [File.join(Dir.pwd, Config.src_dir)]
|
10
|
+
@only = /\.gl$/
|
11
|
+
else
|
12
|
+
file_names = Array.new
|
13
|
+
paths = Array.new
|
14
|
+
@paths.each do |pa|
|
15
|
+
pn = Pathname.new(pa)
|
16
|
+
paths << pn.parent.to_s
|
17
|
+
file_names << (pn.file? ? pn.basename.to_s : pa)
|
18
|
+
end
|
19
|
+
@paths = paths.uniq
|
20
|
+
@only = /#{Regexp.union(file_names)}/
|
21
|
+
end
|
9
22
|
end
|
10
23
|
|
11
24
|
def watch
|
12
|
-
|
13
|
-
listener = Listen.to(
|
25
|
+
Gloss.logger.info "Now listening for changes in #{@paths.join(', ')}"
|
26
|
+
listener = Listen.to(
|
27
|
+
*@paths,
|
28
|
+
latency: 2,
|
29
|
+
only: @only
|
30
|
+
) do |modified, added, removed|
|
14
31
|
(modified + added).each do |f|
|
15
|
-
|
16
|
-
|
17
|
-
puts "====> Rewriting #{f}"
|
32
|
+
Gloss.logger.info "Rewriting #{f}"
|
18
33
|
content = File.read(f)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
err = catch :error do
|
35
|
+
Writer.new(
|
36
|
+
Builder.new(
|
37
|
+
Parser.new(
|
38
|
+
content
|
39
|
+
).run
|
40
|
+
).run, f
|
41
|
+
).run
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
if err
|
45
|
+
Gloss.logger.error err
|
46
|
+
else
|
47
|
+
Gloss.logger.info "Done"
|
48
|
+
end
|
28
49
|
end
|
29
50
|
removed.each do |f|
|
30
|
-
next unless f.end_with? ".gl"
|
31
|
-
|
32
51
|
out_path = Utils.src_path_to_output_path(f)
|
33
|
-
|
52
|
+
Gloss.logger.info "Removing #{out_path}"
|
34
53
|
File.delete out_path if File.exist? out_path
|
35
54
|
|
36
|
-
|
55
|
+
Gloss.logger.info "Done"
|
37
56
|
end
|
38
57
|
end
|
39
|
-
listener.start
|
40
58
|
begin
|
41
|
-
|
59
|
+
listener.start
|
60
|
+
sleep
|
42
61
|
rescue Interrupt
|
43
|
-
|
62
|
+
Gloss.logger.info "Interrupt signal received, shutting down"
|
44
63
|
exit 0
|
45
64
|
end
|
46
65
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gloss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- johansenja
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fast_blank
|
@@ -131,8 +131,9 @@ extensions:
|
|
131
131
|
- ext/gloss/extconf.rb
|
132
132
|
extra_rdoc_files: []
|
133
133
|
files:
|
134
|
-
- ".
|
135
|
-
- ".github/workflows/
|
134
|
+
- ".gitattributes"
|
135
|
+
- ".github/workflows/crystal_specs.yml"
|
136
|
+
- ".github/workflows/ruby_specs.yml"
|
136
137
|
- ".gitignore"
|
137
138
|
- ".gloss.yml"
|
138
139
|
- ".rspec"
|
@@ -163,6 +164,7 @@ files:
|
|
163
164
|
- lib/gloss/config.rb
|
164
165
|
- lib/gloss/errors.rb
|
165
166
|
- lib/gloss/initializer.rb
|
167
|
+
- lib/gloss/logger.rb
|
166
168
|
- lib/gloss/parser.rb
|
167
169
|
- lib/gloss/scope.rb
|
168
170
|
- lib/gloss/source.rb
|
@@ -177,6 +179,7 @@ files:
|
|
177
179
|
- src/lib/gloss/config.gl
|
178
180
|
- src/lib/gloss/errors.gl
|
179
181
|
- src/lib/gloss/initializer.gl
|
182
|
+
- src/lib/gloss/logger.gl
|
180
183
|
- src/lib/gloss/parser.gl
|
181
184
|
- src/lib/gloss/scope.gl
|
182
185
|
- src/lib/gloss/source.gl
|