gloss 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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