gloss 0.0.1 → 0.0.6

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