gloss 0.0.2 → 0.1.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 +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/.gloss.yml +1 -0
- data/.rspec +1 -0
- data/Gemfile.lock +25 -27
- data/README.md +36 -5
- data/exe/gloss +13 -2
- data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
- data/ext/gloss/lib/rbs_types.cr +3 -0
- data/ext/gloss/spec/parser_spec.cr +83 -50
- data/ext/gloss/src/cr_ast.cr +146 -72
- data/ext/gloss/src/gloss.cr +2 -2
- data/ext/gloss/src/lexer.cr +59 -1
- data/ext/gloss/src/parser.cr +4 -4
- data/ext/gloss/src/rb_ast.cr +152 -57
- data/lib/gloss.rb +15 -7
- data/lib/gloss/cli.rb +85 -27
- data/lib/gloss/config.rb +18 -10
- data/lib/gloss/errors.rb +13 -7
- data/lib/gloss/initializer.rb +11 -6
- data/lib/gloss/logger.rb +29 -0
- data/lib/gloss/parser.rb +22 -5
- data/lib/gloss/prog_loader.rb +141 -0
- data/lib/gloss/scope.rb +7 -2
- data/lib/gloss/source.rb +17 -14
- data/lib/gloss/type_checker.rb +105 -66
- data/lib/gloss/utils.rb +44 -0
- data/lib/gloss/version.rb +6 -1
- data/lib/gloss/visitor.rb +667 -0
- data/lib/gloss/watcher.rb +63 -19
- data/lib/gloss/writer.rb +35 -18
- data/sig/core.rbs +2 -0
- data/sig/fast_blank.rbs +4 -0
- data/sig/gls.rbs +3 -0
- data/sig/listen.rbs +1 -0
- data/sig/optparse.rbs +6 -0
- data/sig/rubygems.rbs +9 -0
- data/sig/yaml.rbs +3 -0
- data/src/exe/gloss +19 -0
- data/src/lib/gloss.gl +26 -0
- data/src/lib/gloss/cli.gl +70 -0
- data/src/lib/gloss/config.gl +21 -0
- data/src/lib/gloss/errors.gl +11 -0
- data/src/lib/gloss/initializer.gl +20 -0
- data/src/lib/gloss/logger.gl +21 -0
- data/src/lib/gloss/parser.gl +31 -0
- data/src/lib/gloss/prog_loader.gl +133 -0
- data/src/lib/gloss/scope.gl +7 -0
- data/src/lib/gloss/source.gl +32 -0
- data/src/lib/gloss/type_checker.gl +119 -0
- data/src/lib/gloss/utils.gl +38 -0
- data/src/lib/gloss/version.gl +3 -0
- data/src/lib/gloss/visitor.gl +575 -0
- data/src/lib/gloss/watcher.gl +66 -0
- data/src/lib/gloss/writer.gl +35 -0
- metadata +35 -8
- data/lib/gloss/builder.rb +0 -393
- data/src/lib/hrb/initializer.gl +0 -22
- data/src/lib/hrb/watcher.gl +0 -32
data/lib/gloss.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
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
|
2
5
|
|
3
6
|
require "rbs"
|
4
7
|
require "json"
|
5
8
|
require "steep"
|
6
9
|
require "fast_blank"
|
7
|
-
|
8
10
|
require "gloss/version"
|
9
11
|
require "gloss/cli"
|
10
12
|
require "gloss/watcher"
|
@@ -15,10 +17,16 @@ require "gloss/config"
|
|
15
17
|
require "gloss/writer"
|
16
18
|
require "gloss/source"
|
17
19
|
require "gloss/scope"
|
18
|
-
require "gloss/
|
20
|
+
require "gloss/visitor"
|
19
21
|
require "gloss/errors"
|
20
|
-
|
21
|
-
require "
|
22
|
-
|
23
|
-
|
22
|
+
require "gloss/logger"
|
23
|
+
require "gloss/prog_loader"
|
24
|
+
require "gloss/utils"
|
25
|
+
is_ci = ENV.fetch("CI") { ||
|
26
|
+
false}
|
27
|
+
unless is_ci
|
28
|
+
require "gls"
|
29
|
+
end
|
30
|
+
EMPTY_ARRAY = Array.new
|
31
|
+
.freeze
|
24
32
|
EMPTY_HASH = {}.freeze
|
data/lib/gloss/cli.rb
CHANGED
@@ -1,38 +1,96 @@
|
|
1
|
-
# frozen_string_literal: true
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
4
5
|
|
6
|
+
require "optparse"
|
5
7
|
module Gloss
|
6
8
|
class CLI
|
7
9
|
def initialize(argv)
|
8
10
|
@argv = argv
|
9
11
|
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
12
|
+
def run()
|
13
|
+
command = @argv.first
|
14
|
+
files = @argv.[](((1)..(-1)))
|
15
|
+
err_msg = catch(:"error") { ||
|
16
|
+
case command
|
17
|
+
when "init"
|
18
|
+
force = false
|
19
|
+
OptionParser.new() { |opt|
|
20
|
+
opt.on("--force", "-f") { ||
|
21
|
+
force = true
|
22
|
+
}
|
23
|
+
}
|
24
|
+
.parse(@argv)
|
25
|
+
Initializer.new(force)
|
26
|
+
.run
|
27
|
+
when "version", "--version", "-v"
|
28
|
+
puts(Gloss::VERSION)
|
29
|
+
when "watch", "build"
|
30
|
+
type_checker = ProgLoader.new
|
31
|
+
.run
|
32
|
+
(if command.==("watch")
|
33
|
+
files = files.map() { |f|
|
34
|
+
path = (if Pathname.new(f)
|
35
|
+
.absolute?
|
36
|
+
f
|
37
|
+
else
|
38
|
+
File.join(Dir.pwd, f)
|
39
|
+
end)
|
40
|
+
(if Pathname.new(path)
|
41
|
+
.exist?
|
42
|
+
path
|
43
|
+
else
|
44
|
+
throw(:"error", "Pathname #{f} does not exist")
|
45
|
+
end)
|
46
|
+
}
|
47
|
+
Watcher.new(files)
|
48
|
+
.watch
|
49
|
+
else
|
50
|
+
(if command.==("build")
|
51
|
+
entry_tree = Parser.new(File.read(Config.entrypoint))
|
52
|
+
.run
|
53
|
+
Visitor.new(entry_tree, type_checker)
|
54
|
+
.run
|
55
|
+
(if files.empty?
|
56
|
+
files = Dir.glob("#{Config.src_dir}/**/*.gl")
|
57
|
+
end)
|
58
|
+
files.each() { |fp|
|
59
|
+
fp = File.absolute_path(fp)
|
60
|
+
preloaded_output = OUTPUT_BY_PATH.fetch(fp) { ||
|
61
|
+
nil }
|
62
|
+
(if preloaded_output
|
63
|
+
rb_output = preloaded_output
|
64
|
+
else
|
65
|
+
Gloss.logger
|
66
|
+
.info("Building #{fp}")
|
67
|
+
content = File.read(fp)
|
68
|
+
tree_hash = Parser.new(content)
|
69
|
+
.run
|
70
|
+
rb_output = Visitor.new(tree_hash, type_checker)
|
71
|
+
.run
|
72
|
+
end)
|
73
|
+
Gloss.logger
|
74
|
+
.info("Type checking #{fp}")
|
75
|
+
type_checker.run(fp, rb_output)
|
76
|
+
}
|
77
|
+
files.each() { |fp|
|
78
|
+
fp = File.absolute_path(fp)
|
79
|
+
rb_output = OUTPUT_BY_PATH.fetch(fp)
|
80
|
+
Gloss.logger
|
81
|
+
.info("Writing #{fp}")
|
82
|
+
Writer.new(rb_output, fp)
|
83
|
+
.run
|
84
|
+
}
|
85
|
+
end)
|
86
|
+
end)
|
87
|
+
else
|
88
|
+
throw(:"error", "Gloss doesn't know how to #{command}")
|
26
89
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end.parse(@argv)
|
32
|
-
Initializer.new(force).run
|
33
|
-
else
|
34
|
-
abort "Gloss doesn't know how to #{command}"
|
35
|
-
end
|
90
|
+
nil }
|
91
|
+
(if err_msg
|
92
|
+
abort(err_msg)
|
93
|
+
end)
|
36
94
|
end
|
37
95
|
end
|
38
96
|
end
|
data/lib/gloss/config.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
-
# frozen_string_literal: true
|
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
|
2
5
|
|
3
6
|
require "ostruct"
|
4
7
|
require "yaml"
|
5
|
-
|
6
8
|
module Gloss
|
7
|
-
|
8
|
-
Config = OpenStruct.new(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
CONFIG_PATH = ".gloss.yml"
|
10
|
+
Config = OpenStruct.new(default_config: {:frozen_string_literals => true,
|
11
|
+
:src_dir => "src",
|
12
|
+
:entrypoint => nil,
|
13
|
+
:strict_require => false}.freeze)
|
14
|
+
user_config = (if File.exist?(CONFIG_PATH)
|
15
|
+
YAML.safe_load(File.read(CONFIG_PATH))
|
16
|
+
else
|
17
|
+
Config.default_config
|
18
|
+
end)
|
19
|
+
Config.default_config
|
20
|
+
.each() { |k, v|
|
21
|
+
Config.send(:"#{k}=", user_config.[](k.to_s) || v)
|
22
|
+
}
|
15
23
|
end
|
data/lib/gloss/errors.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
|
2
|
-
module Errors
|
3
|
-
class BaseGlossError < StandardError; end
|
4
|
-
|
5
|
-
class TypeValidationError < BaseGlossError; end
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
8
5
|
|
9
|
-
|
6
|
+
module Gloss
|
7
|
+
module Errors
|
8
|
+
class BaseGlossError < StandardError
|
9
|
+
end
|
10
|
+
class TypeValidationError < BaseGlossError
|
11
|
+
end
|
12
|
+
class TypeError < BaseGlossError
|
13
|
+
end
|
14
|
+
class ParserError < BaseGlossError
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
data/lib/gloss/initializer.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
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
|
+
|
2
6
|
require "yaml"
|
3
7
|
module Gloss
|
4
8
|
class Initializer
|
@@ -6,15 +10,16 @@ module Gloss
|
|
6
10
|
@force = force
|
7
11
|
end
|
8
12
|
def run()
|
9
|
-
(if File.exist?(
|
10
|
-
|
13
|
+
(if File.exist?(CONFIG_PATH) && !@force
|
14
|
+
throw(:"error", "#{CONFIG_PATH} file already exists - aborting. Use --force to override.")
|
11
15
|
end)
|
12
|
-
File.open(
|
16
|
+
File.open(CONFIG_PATH, "wb") { |file|
|
13
17
|
file.puts(Config.default_config
|
14
|
-
.transform_keys(&:to_s)
|
18
|
+
.transform_keys(&:"to_s")
|
15
19
|
.to_yaml)
|
16
20
|
}
|
17
|
-
|
21
|
+
Gloss.logger
|
22
|
+
.info("Created #{CONFIG_PATH} with default preferences")
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
data/lib/gloss/logger.rb
ADDED
@@ -0,0 +1,29 @@
|
|
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
|
+
IO::NULL
|
26
|
+
end))
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
data/lib/gloss/parser.rb
CHANGED
@@ -1,17 +1,34 @@
|
|
1
|
-
# frozen_string_literal: true
|
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
|
2
5
|
|
3
6
|
module Gloss
|
4
7
|
class Parser
|
5
8
|
def initialize(str)
|
6
9
|
@str = str
|
7
10
|
end
|
8
|
-
|
9
|
-
def run
|
11
|
+
def run()
|
10
12
|
tree_json = Gloss.parse_buffer(@str)
|
11
13
|
begin
|
12
|
-
JSON.parse
|
14
|
+
JSON.parse(tree_json, symbolize_names: true)
|
13
15
|
rescue JSON::ParserError
|
14
|
-
|
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)
|
15
32
|
end
|
16
33
|
end
|
17
34
|
end
|
@@ -0,0 +1,141 @@
|
|
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
|
+
require "rubygems/gem_runner"
|
7
|
+
module Gloss
|
8
|
+
OUTPUT_BY_PATH = Hash.new
|
9
|
+
class ProgLoader
|
10
|
+
def initialize()
|
11
|
+
entrypoint = Config.entrypoint
|
12
|
+
(if entrypoint.==(nil) || entrypoint.==("")
|
13
|
+
throw(:"error", "Entrypoint is not yet set in .gloss.yml")
|
14
|
+
end)
|
15
|
+
@files_to_process = [Utils.absolute_path(Config.entrypoint), Utils.absolute_path(File.join(__dir__ || "", "..", "..", "sig", "core.rbs"))]
|
16
|
+
@processed_files = Set.new
|
17
|
+
@type_checker = TypeChecker.new
|
18
|
+
end
|
19
|
+
def run()
|
20
|
+
@files_to_process.each() { |path_string|
|
21
|
+
unless @processed_files.member?(path_string) || OUTPUT_BY_PATH.[](path_string)
|
22
|
+
Gloss.logger
|
23
|
+
.debug("Loading #{path_string}")
|
24
|
+
path = Utils.absolute_path(path_string)
|
25
|
+
file_contents = File.open(path)
|
26
|
+
.read
|
27
|
+
contents_tree = Parser.new(file_contents)
|
28
|
+
.run
|
29
|
+
on_new_file_referenced = proc() { |pa, relative|
|
30
|
+
(if relative
|
31
|
+
handle_require_relative(pa)
|
32
|
+
else
|
33
|
+
handle_require(pa)
|
34
|
+
end)
|
35
|
+
}
|
36
|
+
OUTPUT_BY_PATH.[]=(path_string, Visitor.new(contents_tree, @type_checker, on_new_file_referenced)
|
37
|
+
.run)
|
38
|
+
@processed_files.add(path_string)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
@type_checker
|
42
|
+
end
|
43
|
+
STDLIB_TYPE_DEPENDENCIES = {"yaml" => ["pstore", "dbm"],
|
44
|
+
"rbs" => ["logger", "set", "tsort"],
|
45
|
+
"logger" => ["monitor"]}
|
46
|
+
private def handle_require(path)
|
47
|
+
(if path.start_with?(".")
|
48
|
+
base = File.join(Dir.pwd, path)
|
49
|
+
fp = base.+(".gl")
|
50
|
+
(if File.exist?(fp)
|
51
|
+
@files_to_process.<<(fp)
|
52
|
+
end)
|
53
|
+
return
|
54
|
+
end)
|
55
|
+
full = File.absolute_path("#{File.join(Config.src_dir, "lib", path)}.gl")
|
56
|
+
pathn = Pathname.new(full)
|
57
|
+
(if pathn.file?
|
58
|
+
@files_to_process.<<(pathn.to_s)
|
59
|
+
else
|
60
|
+
pathn = Pathname.new("#{File.join(Dir.pwd, "sig", path)}.rbs")
|
61
|
+
gem_path = Utils.gem_path_for(path)
|
62
|
+
(if gem_path
|
63
|
+
sig_files = Dir.glob(File.absolute_path(File.join(gem_path, "..", "..", "sig", "**", "*.rbs")))
|
64
|
+
(if sig_files.length
|
65
|
+
.positive?
|
66
|
+
sig_files.each() { |fp|
|
67
|
+
@type_checker.load_sig_path(fp)
|
68
|
+
}
|
69
|
+
@processed_files.add(path)
|
70
|
+
rbs_type_deps = STDLIB_TYPE_DEPENDENCIES.fetch(path) { ||
|
71
|
+
nil }
|
72
|
+
(if rbs_type_deps
|
73
|
+
rbs_type_deps.each() { |d|
|
74
|
+
handle_require(d)
|
75
|
+
}
|
76
|
+
end)
|
77
|
+
return
|
78
|
+
end)
|
79
|
+
end)
|
80
|
+
(if pathn.file?
|
81
|
+
@type_checker.load_sig_path(pathn.to_s)
|
82
|
+
@processed_files.add(pathn.to_s)
|
83
|
+
else
|
84
|
+
rbs_stdlib_dir = File.absolute_path(File.join(@type_checker.rbs_gem_dir, "..", "..", "stdlib", path))
|
85
|
+
(if Pathname.new(rbs_stdlib_dir)
|
86
|
+
.exist?
|
87
|
+
load_rbs_from_require_path(path)
|
88
|
+
rbs_type_deps = STDLIB_TYPE_DEPENDENCIES.fetch(path) { ||
|
89
|
+
nil }
|
90
|
+
(if rbs_type_deps
|
91
|
+
rbs_type_deps.each() { |d|
|
92
|
+
load_rbs_from_require_path(d)
|
93
|
+
}
|
94
|
+
end)
|
95
|
+
else
|
96
|
+
(if Config.strict_require
|
97
|
+
throw(:"error", "Cannot resolve require path for #{path}")
|
98
|
+
else
|
99
|
+
Gloss.logger
|
100
|
+
.debug("No path found for #{path}")
|
101
|
+
end)
|
102
|
+
end)
|
103
|
+
end)
|
104
|
+
end)
|
105
|
+
end
|
106
|
+
private def handle_require_relative(path)
|
107
|
+
base = File.join(@filepath, "..", path)
|
108
|
+
# @type var pn: String?
|
109
|
+
pn = nil
|
110
|
+
Gem.suffixes
|
111
|
+
.each() { |ext|
|
112
|
+
full = File.absolute_path(base.+(ext))
|
113
|
+
(if File.exist?(full)
|
114
|
+
pn = full
|
115
|
+
end)
|
116
|
+
}
|
117
|
+
(if pn
|
118
|
+
unless @files_to_process.include?(pn)
|
119
|
+
@files_to_process.<<(pn)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
(if Config.strict_require
|
123
|
+
throw(:"error", "Cannot resolve require path for #{pn}")
|
124
|
+
else
|
125
|
+
Gloss.logger
|
126
|
+
.debug("No path found for #{path}")
|
127
|
+
end)
|
128
|
+
end)
|
129
|
+
end
|
130
|
+
private def rbs_stdlib_path_for(libr)
|
131
|
+
File.absolute_path(File.join(@type_checker.rbs_gem_dir, "..", "..", "stdlib", libr))
|
132
|
+
end
|
133
|
+
private def load_rbs_from_require_path(path)
|
134
|
+
Dir.glob(File.join(rbs_stdlib_path_for(path), "**", "*.rbs"))
|
135
|
+
.each() { |fp|
|
136
|
+
@type_checker.load_sig_path(fp)
|
137
|
+
@processed_files.add(fp)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|