gloss 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/gloss.svg)](https://rubygems.org/gems/gloss)
|
3
|
+
[![Ruby Specs](https://github.com/johansenja/gloss/workflows/Ruby%20Specs/badge.svg)](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Ruby+Specs%22)
|
4
|
+
[![Crystal Specs](https://github.com/johansenja/gloss/workflows/Crystal%20Specs/badge.svg)](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Crystal+Specs%22)
|
5
|
+
[![Total Downloads](http://ruby-gem-downloads-badge.herokuapp.com/gloss?type=total&color=green&metric=true&label=downloads%20(total)&total_label=)](https://rubygems.org/gems/gloss)
|
6
|
+
[![Current Version](http://ruby-gem-downloads-badge.herokuapp.com/gloss?color=green&label=downloads%20(current%20version)&metric=true)](https://rubygems.org/gems/gloss)
|
7
|
+
[![Total Views](https://counter.gofiber.io/badge/johansenja/gloss)](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
|