gloss 0.0.1 → 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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/crystal_specs.yml +26 -0
  4. data/.github/workflows/ruby_specs.yml +33 -0
  5. data/.gitignore +2 -1
  6. data/.rspec +1 -0
  7. data/Gemfile +0 -1
  8. data/Gemfile.lock +26 -34
  9. data/LICENSE +21 -0
  10. data/README.md +59 -7
  11. data/Rakefile +4 -0
  12. data/exe/gloss +15 -1
  13. data/ext/gloss/Makefile +27 -5
  14. data/ext/gloss/extconf.rb +3 -25
  15. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  16. data/ext/gloss/lib/rbs_types.cr +3 -0
  17. data/ext/gloss/spec/parser_spec.cr +124 -0
  18. data/ext/gloss/spec/spec_helper.cr +2 -0
  19. data/ext/gloss/src/cr_ast.cr +129 -31
  20. data/ext/gloss/src/gloss.cr +12 -18
  21. data/ext/gloss/src/lexer.cr +59 -1
  22. data/ext/gloss/src/parser.cr +548 -254
  23. data/ext/gloss/src/rb_ast.cr +153 -27
  24. data/gloss.gemspec +1 -0
  25. data/lib/gloss.rb +4 -1
  26. data/lib/gloss/builder.rb +607 -409
  27. data/lib/gloss/cli.rb +64 -24
  28. data/lib/gloss/config.rb +16 -10
  29. data/lib/gloss/errors.rb +13 -7
  30. data/lib/gloss/initializer.rb +11 -6
  31. data/lib/gloss/logger.rb +34 -0
  32. data/lib/gloss/parser.rb +35 -0
  33. data/lib/gloss/scope.rb +8 -3
  34. data/lib/gloss/source.rb +18 -15
  35. data/lib/gloss/type_checker.rb +96 -0
  36. data/lib/gloss/version.rb +6 -1
  37. data/lib/gloss/watcher.rb +63 -19
  38. data/lib/gloss/writer.rb +18 -12
  39. data/sig/gloss.rbs +3 -0
  40. data/sig/listen.rbs +1 -0
  41. data/src/lib/gloss/builder.gl +546 -0
  42. data/src/lib/gloss/cli.gl +55 -0
  43. data/src/lib/gloss/config.gl +21 -0
  44. data/src/lib/gloss/errors.gl +11 -0
  45. data/src/lib/gloss/initializer.gl +20 -0
  46. data/src/lib/gloss/logger.gl +26 -0
  47. data/src/lib/gloss/parser.gl +31 -0
  48. data/src/lib/gloss/scope.gl +9 -0
  49. data/src/lib/gloss/source.gl +32 -0
  50. data/src/lib/gloss/type_checker.gl +101 -0
  51. data/src/lib/gloss/version.gl +3 -0
  52. data/src/lib/gloss/watcher.gl +67 -0
  53. data/src/lib/gloss/writer.gl +33 -0
  54. metadata +42 -6
  55. data/lib/gloss.bundle.dwarf +0 -0
  56. data/src/lib/hrb/initializer.gl +0 -22
  57. data/src/lib/hrb/watcher.gl +0 -32
data/lib/gloss/cli.rb CHANGED
@@ -1,35 +1,75 @@
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
-
21
- puts "=====> Writing #{fp}"
22
- Writer.new(Builder.new(content).run, fp).run
12
+ def run()
13
+ command = @argv.first
14
+ files = @argv.[](((1)..(-1)))
15
+ err_msg = catch(:"error") { ||
16
+ case command
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
+ }
32
+ Watcher.new(files)
33
+ .watch
34
+ when "build"
35
+ (if files.empty?
36
+ Dir.glob("#{Config.src_dir}/**/*.gl")
37
+ else
38
+ files
39
+ end)
40
+ .each() { |fp|
41
+ Gloss.logger
42
+ .info("Building #{fp}")
43
+ content = File.read(fp)
44
+ tree_hash = Parser.new(content)
45
+ .run
46
+ type_checker = TypeChecker.new
47
+ rb_output = Builder.new(tree_hash, type_checker)
48
+ .run
49
+ type_checker.run(rb_output)
50
+ Gloss.logger
51
+ .info("Writing #{fp}")
52
+ Writer.new(rb_output, fp)
53
+ .run
54
+ }
55
+ when "init"
56
+ force = false
57
+ OptionParser.new() { |opt|
58
+ opt.on("--force", "-f") { ||
59
+ force = true
60
+ }
61
+ }
62
+ .parse(@argv)
63
+ Initializer.new(force)
64
+ .run
65
+ else
66
+ throw(:"error", "Gloss doesn't know how to #{command}")
23
67
  end
24
- when "init"
25
- force = false
26
- OptionParser.new do |opt|
27
- opt.on("--force", "-f") { force = true }
28
- end.parse(@argv)
29
- Initializer.new(force).run
30
- else
31
- abort "Gloss doesn't know how to #{command}"
32
- end
68
+ nil }
69
+ (if err_msg
70
+ Gloss.logger.fatal(err_msg)
71
+ exit(1)
72
+ end)
33
73
  end
34
74
  end
35
75
  end
data/lib/gloss/config.rb CHANGED
@@ -1,15 +1,21 @@
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"}.freeze)
12
+ user_config = (if File.exist?(CONFIG_PATH)
13
+ YAML.safe_load(File.read(CONFIG_PATH))
14
+ else
15
+ Config.default_config
16
+ end)
17
+ Config.default_config
18
+ .each() { |k, v|
19
+ Config.send(:"#{k}=", user_config.[](k.to_s) || v)
20
+ }
15
21
  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,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
@@ -0,0 +1,35 @@
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
+ class Parser
8
+ def initialize(str)
9
+ @str = str
10
+ end
11
+ def run()
12
+ tree_json = Gloss.parse_buffer(@str)
13
+ begin
14
+ JSON.parse(tree_json, symbolize_names: true)
15
+ rescue JSON::ParserError
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)
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/gloss/scope.rb CHANGED
@@ -1,9 +1,14 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- module Gloss
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ module Gloss
4
7
  class Scope < Hash
5
8
  def [](k)
6
- fetch(k) { raise "Undefined expression for current scope: #{k}" }
9
+ fetch(k) { ||
10
+ raise("Undefined expression for current scope: #{k}")
11
+ }
7
12
  end
8
13
  end
9
14
  end
data/lib/gloss/source.rb CHANGED
@@ -1,31 +1,34 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- module Gloss
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ module Gloss
4
7
  class Source < String
5
8
  def initialize(indent_level)
6
- super()
7
9
  @indent_level = indent_level
10
+ super()
8
11
  end
9
-
10
12
  def write(*args)
11
- args.each do |a|
12
- self << a
13
- end
13
+ args.each() { |a|
14
+ self.<<(a)
15
+ }
16
+ self
14
17
  end
15
-
16
18
  def write_indnt(*args)
17
- write(*args.map { |a| "#{(" " * @indent_level)}#{a}" })
19
+ write(*args.map() { |a|
20
+ "#{" ".*(@indent_level)}#{a}" })
18
21
  end
19
-
20
22
  def write_ln(*args)
21
- write_indnt(*args.map { |a| a.strip << "\n" })
23
+ write_indnt(*args.map() { |a|
24
+ a.strip
25
+ .<<("\n")
26
+ })
22
27
  end
23
-
24
- def increment_indent
28
+ def increment_indent()
25
29
  @indent_level += 1
26
30
  end
27
-
28
- def decrement_indent
31
+ def decrement_indent()
29
32
  @indent_level -= 1
30
33
  end
31
34
  end
@@ -0,0 +1,96 @@
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 "pry-byebug"
7
+ module Gloss
8
+ class TypeChecker
9
+ Project = Struct.new(:"targets")
10
+ attr_reader(:"steep_target", :"top_level_decls")
11
+ def initialize()
12
+ @steep_target = Steep::Project::Target.new(name: "gloss", options: Steep::Project::Options.new
13
+ .tap() { |o|
14
+ o.allow_unknown_constant_assignment=(true)
15
+ }, source_patterns: ["gloss.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
16
+ @top_level_decls = {}
17
+ end
18
+ def run(rb_str)
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
28
+ .map() { |e|
29
+ case e
30
+ when Steep::Diagnostic::Ruby::NoMethod
31
+ "Unknown method :#{e.method}, location: #{e.type
32
+ .location
33
+ .inspect}"
34
+ when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
35
+ "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
36
+ when Steep::Diagnostic::Ruby::IncompatibleArguments
37
+ "Invalid argmuents - method type: #{e.method_type}\nmethod name: #{e.method_type
38
+ .method_decls
39
+ .first
40
+ .method_name}"
41
+ when Steep::Diagnostic::Ruby::ReturnTypeMismatch
42
+ "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
43
+ when Steep::Diagnostic::Ruby::IncompatibleAssignment
44
+ "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
45
+ when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
46
+ "Unexpected block given"
47
+ else
48
+ e.inspect
49
+ end
50
+ }
51
+ .join("\n")
52
+ throw(:"error", errors)
53
+ end
54
+ true
55
+ end
56
+ def check_types(rb_str)
57
+ env_loader = RBS::EnvironmentLoader.new
58
+ env = RBS::Environment.from_loader(env_loader)
59
+ project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
60
+ .realpath)
61
+ project.targets
62
+ .<<(@steep_target)
63
+ loader = Steep::Project::FileLoader.new(project: project)
64
+ loader.load_signatures
65
+ @steep_target.add_source("gloss.rb", rb_str)
66
+ @top_level_decls.each() { |_, decl|
67
+ env.<<(decl)
68
+ }
69
+ env = env.resolve_type_names
70
+ @steep_target.instance_variable_set("@environment", env)
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
+ }
91
+ @steep_target.status
92
+ .is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
93
+ .empty?
94
+ end
95
+ end
96
+ end
data/lib/gloss/version.rb CHANGED
@@ -1,3 +1,8 @@
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
+
1
6
  module Gloss
2
- VERSION = "0.0.1"
7
+ VERSION = "0.0.6"
3
8
  end
data/lib/gloss/watcher.rb CHANGED
@@ -1,31 +1,75 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- require "listen"
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
4
5
 
6
+ require "listen"
5
7
  module Gloss
6
8
  class Watcher
7
- def initialize
8
- @paths = %w[src/]
9
+ def initialize(paths)
10
+ @paths = paths
11
+ (if @paths.empty?
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)}/
30
+ end)
9
31
  end
10
-
11
- def watch
12
- puts "=====> Now listening for changes in #{@paths.join(', ')}"
13
- listener = Listen.to(*@paths, latency: 2) do |modified, added, removed|
14
- (modified + added).each do |f|
32
+ def watch()
33
+ Gloss.logger
34
+ .info("Now listening for changes in #{@paths.join(", ")}")
35
+ listener = Listen.to(*@paths, latency: 2, only: @only) { |modified, added, removed|
36
+ modified.+(added)
37
+ .each() { |f|
38
+ Gloss.logger
39
+ .info("Rewriting #{f}")
15
40
  content = File.read(f)
16
- Writer.new(Builder.new(content).run, f).run
17
- end
18
- removed.each do |f|
41
+ err = catch(:"error") { ||
42
+ Writer.new(Builder.new(Parser.new(content)
43
+ .run)
44
+ .run, f)
45
+ .run
46
+ nil }
47
+ (if err
48
+ Gloss.logger
49
+ .error(err)
50
+ else
51
+ Gloss.logger
52
+ .info("Done")
53
+ end)
54
+ }
55
+ removed.each() { |f|
19
56
  out_path = Utils.src_path_to_output_path(f)
20
- File.delete out_path if File.exist? out_path
21
- end
22
- end
23
- listener.start
57
+ Gloss.logger
58
+ .info("Removing #{out_path}")
59
+ (if File.exist?(out_path)
60
+ File.delete(out_path)
61
+ end)
62
+ Gloss.logger
63
+ .info("Done")
64
+ }
65
+ }
24
66
  begin
25
- loop { sleep 10 }
67
+ listener.start
68
+ sleep
26
69
  rescue Interrupt
27
- puts "=====> Interrupt signal received, shutting down"
28
- exit 0
70
+ Gloss.logger
71
+ .info("Interrupt signal received, shutting down")
72
+ exit(0)
29
73
  end
30
74
  end
31
75
  end