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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
  4. data/.github/workflows/{ruby.yml → ruby_specs.yml} +1 -1
  5. data/.gloss.yml +1 -0
  6. data/.rspec +1 -0
  7. data/Gemfile.lock +25 -27
  8. data/README.md +36 -5
  9. data/exe/gloss +13 -2
  10. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  11. data/ext/gloss/lib/rbs_types.cr +3 -0
  12. data/ext/gloss/spec/parser_spec.cr +83 -50
  13. data/ext/gloss/src/cr_ast.cr +146 -72
  14. data/ext/gloss/src/gloss.cr +2 -2
  15. data/ext/gloss/src/lexer.cr +59 -1
  16. data/ext/gloss/src/parser.cr +4 -4
  17. data/ext/gloss/src/rb_ast.cr +152 -57
  18. data/lib/gloss.rb +15 -7
  19. data/lib/gloss/cli.rb +85 -27
  20. data/lib/gloss/config.rb +18 -10
  21. data/lib/gloss/errors.rb +13 -7
  22. data/lib/gloss/initializer.rb +11 -6
  23. data/lib/gloss/logger.rb +29 -0
  24. data/lib/gloss/parser.rb +22 -5
  25. data/lib/gloss/prog_loader.rb +141 -0
  26. data/lib/gloss/scope.rb +7 -2
  27. data/lib/gloss/source.rb +17 -14
  28. data/lib/gloss/type_checker.rb +105 -66
  29. data/lib/gloss/utils.rb +44 -0
  30. data/lib/gloss/version.rb +6 -1
  31. data/lib/gloss/visitor.rb +667 -0
  32. data/lib/gloss/watcher.rb +63 -19
  33. data/lib/gloss/writer.rb +35 -18
  34. data/sig/core.rbs +2 -0
  35. data/sig/fast_blank.rbs +4 -0
  36. data/sig/gls.rbs +3 -0
  37. data/sig/listen.rbs +1 -0
  38. data/sig/optparse.rbs +6 -0
  39. data/sig/rubygems.rbs +9 -0
  40. data/sig/yaml.rbs +3 -0
  41. data/src/exe/gloss +19 -0
  42. data/src/lib/gloss.gl +26 -0
  43. data/src/lib/gloss/cli.gl +70 -0
  44. data/src/lib/gloss/config.gl +21 -0
  45. data/src/lib/gloss/errors.gl +11 -0
  46. data/src/lib/gloss/initializer.gl +20 -0
  47. data/src/lib/gloss/logger.gl +21 -0
  48. data/src/lib/gloss/parser.gl +31 -0
  49. data/src/lib/gloss/prog_loader.gl +133 -0
  50. data/src/lib/gloss/scope.gl +7 -0
  51. data/src/lib/gloss/source.gl +32 -0
  52. data/src/lib/gloss/type_checker.gl +119 -0
  53. data/src/lib/gloss/utils.gl +38 -0
  54. data/src/lib/gloss/version.gl +3 -0
  55. data/src/lib/gloss/visitor.gl +575 -0
  56. data/src/lib/gloss/watcher.gl +66 -0
  57. data/src/lib/gloss/writer.gl +35 -0
  58. metadata +35 -8
  59. data/lib/gloss/builder.rb +0 -393
  60. data/src/lib/hrb/initializer.gl +0 -22
  61. 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/builder"
20
+ require "gloss/visitor"
19
21
  require "gloss/errors"
20
-
21
- require "gls" unless ENV["CI"] # a bit of a hack for now
22
-
23
- EMPTY_ARRAY = [].freeze
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
- require "optparse"
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
- def run
12
- command, *files = @argv
13
- case command
14
- when "watch"
15
- Watcher.new.watch
16
- when "build"
17
- (files.empty? ? Dir.glob("#{Config.src_dir}/**/*.rb") : files).each do |fp|
18
- puts "=====> Building #{fp}"
19
- content = File.read(fp)
20
- tree_hash = Parser.new(content).run
21
- type_checker = TypeChecker.new
22
- rb_output = Builder.new(tree_hash, type_checker)
23
-
24
- puts "=====> Writing #{fp}"
25
- Writer.new(rb_output, fp).run
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
- when "init"
28
- force = false
29
- OptionParser.new do |opt|
30
- opt.on("--force", "-f") { force = true }
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
- user_config = YAML.safe_load(File.read(".gloss.yml"))
8
- Config = OpenStruct.new(
9
- default_config: {
10
- frozen_string_literals: true,
11
- src_dir: "src",
12
- }.freeze
13
- )
14
- Config.default_config.each { |k, v| Config.send(:"#{k}=", user_config[k.to_s] || v) }
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
- module Gloss
2
- module Errors
3
- class BaseGlossError < StandardError; end
4
-
5
- class TypeValidationError < BaseGlossError; end
1
+ # frozen_string_literal: true
6
2
 
7
- class TypeError < BaseGlossError; end
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
8
5
 
9
- class ParserError < BaseGlossError; end
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
@@ -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?(".gloss.yml") && !@force
10
- abort(".gloss.yml file already exists - aborting. Use --force to override.")
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(".gloss.yml", "wb") { |file|
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
- puts("Created .gloss.yml with default preferences")
21
+ Gloss.logger
22
+ .info("Created #{CONFIG_PATH} with default preferences")
18
23
  end
19
24
  end
20
25
  end
@@ -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 tree_json, symbolize_names: true
14
+ JSON.parse(tree_json, symbolize_names: true)
13
15
  rescue JSON::ParserError
14
- raise Errors::ParserError, tree_json
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