gloss 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a26f64e872c2884f2fa5969d762e66ed6f1346732933f08ae7b92eb07aea5ab3
4
- data.tar.gz: 9cf39520e8dc43e6a74f9ea606a719815e2bd503d19744c7979478f746b8345f
3
+ metadata.gz: 9315ca2d8d487ca5729f7665abcba9e922ec4e0ee85ac54787248d07b54b7ed5
4
+ data.tar.gz: 78928faa1a7e77e0184594a31786c0befdc796a4a9b46c5f509adb95c39d37c3
5
5
  SHA512:
6
- metadata.gz: bdb98f72e57945e9de8bbd03296d95b2cfc221f915e7e8e8fd511e8b4794ef3bb65f1830d9769486c30a004595c3a7f84e3e785bb1b1297d778cff984a0a3234
7
- data.tar.gz: 9758d730e858adf27c60f89ec9e88ffb07d3950d2a4f493b1d0ad2b00b7a10999fd76c9ae802753428fdfb0cab005adfe773456c415b504809217507ac30fbc5
6
+ metadata.gz: c3178c5cf74e3946e327cbe97b5c5f421090e7b678d331c34ce5c8a45aea137cdc9628f91481425e18f66cf3245983d1e5bf965feaae211d619d8d9f5ed55647
7
+ data.tar.gz: 9d9b5ef709cffe8efb6b0124762bbe025948caa51772cf33a38143862003048f15ee83198c2fd0f6a05128f14da65a9feb22b45205563b686bacb70d51dd5741
data/.gitattributes ADDED
@@ -0,0 +1,3 @@
1
+ lib/gloss/**/*.rb linguist-generated
2
+ # this will basically cover 99% of the syntax anyway
3
+ **/*.gl linguist-language=Crystal
@@ -1,4 +1,4 @@
1
- name: Crystal CI
1
+ name: Crystal Specs
2
2
 
3
3
  on:
4
4
  push:
@@ -5,7 +5,7 @@
5
5
  # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
6
  # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
7
 
8
- name: Ruby
8
+ name: Ruby Specs
9
9
 
10
10
  on:
11
11
  push:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gloss (0.0.5)
4
+ gloss (0.0.6)
5
5
  fast_blank
6
6
  listen
7
7
  rbs
data/README.md CHANGED
@@ -1,7 +1,15 @@
1
1
  # Gloss
2
+ [![Gem Version](https://badge.fury.io/rb/gloss.svg)](https://rubygems.org/gems/gloss)
3
+ [![Ruby Specs](https://github.com/johansenja/gloss/workflows/Ruby%20Specs/badge.svg)](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Ruby+Specs%22)
4
+ [![Crystal Specs](https://github.com/johansenja/gloss/workflows/Crystal%20Specs/badge.svg)](https://github.com/johansenja/gloss/actions?query=workflow%3A%22Crystal+Specs%22)
5
+ [![Total Downloads](http://ruby-gem-downloads-badge.herokuapp.com/gloss?type=total&color=green&metric=true&label=downloads%20(total)&total_label=)](https://rubygems.org/gems/gloss)
6
+ [![Current Version](http://ruby-gem-downloads-badge.herokuapp.com/gloss?color=green&label=downloads%20(current%20version)&metric=true)](https://rubygems.org/gems/gloss)
7
+ [![Total Views](https://counter.gofiber.io/badge/johansenja/gloss)](https://rubygems.org/gems/gloss)
2
8
 
3
9
  [Gloss](https://en.wikipedia.org/wiki/Gloss_(annotation)) is a high-level programming language based on [Ruby](https://github.com/ruby/ruby) and [Crystal](https://github.com/crystal-lang/crystal), which compiles to ruby; its aims are on transparency,
4
- efficiency, and to enhance ruby's goal of developer happiness and productivity. Some of the features include:
10
+ efficiency, and to enhance ruby's goal of developer happiness and productivity.
11
+
12
+ ### Current features
5
13
 
6
14
  - Type checking, via optional type annotations
7
15
  - Compile-time macros
@@ -10,11 +18,26 @@ efficiency, and to enhance ruby's goal of developer happiness and productivity.
10
18
  - All ruby files are valid gloss files (a small exceptions for now; workarounds are mostly available)
11
19
  - Other syntactic sugar
12
20
 
13
- Coming soon:
14
- - abstract classes
21
+ ### Current Status
22
+
23
+ This project is at a stage where the core non-crystal parts are written in Gloss and compile to ruby (essentially self-hosting), albeit with the type checking being fairly loose. However the project is still in the very early stages; with (as of yet) no Linux support nor error handling (see roadmap below). Use at your own discretion!
24
+
25
+ ### Approx. roadmap:
26
+
27
+ - Improve error handling and logging (currently almost non-existant)
28
+ - Address Linux compatibility (currently more or less non-existant)
29
+ - Implement different strictnesses of type checking
30
+ - Metaprogramming helpers/safety:*
31
+ - Abstract classes and methods
32
+ - Method lookup/existence checking at compile time
33
+ - Method overloading
34
+
35
+ #### Related items:
36
+
37
+ - Rails helpers; probably some time away*
38
+ - Editor plugins/syntax highlighting/langserver; probably some time away*
15
39
 
16
- Maybe on the roadmap:
17
- - Method overloading
40
+ *__Dependent on popularity__
18
41
 
19
42
  ## Examples:
20
43
 
@@ -223,4 +246,12 @@ then
223
246
 
224
247
  then
225
248
 
249
+ `mkdir src && echo "puts 'hello world'" > src/hello_world.gl`
250
+
251
+ then
252
+
226
253
  `gloss build`
254
+
255
+ then
256
+
257
+ `ruby ./hello_world.rb`
data/exe/gloss CHANGED
@@ -3,4 +3,18 @@
3
3
  require "bundler/setup"
4
4
  require "gloss"
5
5
 
6
- Gloss::CLI.new(ARGV).run
6
+ begin
7
+ Gloss::CLI.new(ARGV).run
8
+ rescue SystemExit
9
+ # raised by `abort` or `exit`; no op
10
+ rescue => e
11
+ Gloss.logger.fatal <<~MSG
12
+ Unexpected error: #{e.class.name}
13
+ Message: #{e.message}
14
+ Trace:
15
+ #{e.backtrace.join("\n")}
16
+
17
+ This is probably a bug and may warrant a bug report at https://github.com/johansenja/gloss/issues
18
+ MSG
19
+ exit 1
20
+ end
@@ -235,12 +235,6 @@ module Crystal
235
235
  end
236
236
  end
237
237
 
238
- class MacroExpression < ASTNode
239
- def to_rb
240
- Rb::AST::EmptyNode.new(self.class.name)
241
- end
242
- end
243
-
244
238
  class MacroIf < ASTNode
245
239
  def to_rb
246
240
  Rb::AST::MacroIf.new(@cond.to_rb, @then.to_rb, @else.to_rb)
@@ -11,7 +11,6 @@ def parse_string(self : CrRuby::VALUE, str : CrRuby::VALUE)
11
11
  output = begin
12
12
  Gloss.parse_string(string)
13
13
  rescue e : Crystal::SyntaxException
14
- pp e.backtrace
15
14
  e.to_s
16
15
  end
17
16
 
data/lib/gloss.rb CHANGED
@@ -17,6 +17,7 @@ require "gloss/source"
17
17
  require "gloss/scope"
18
18
  require "gloss/builder"
19
19
  require "gloss/errors"
20
+ require "gloss/logger"
20
21
 
21
22
  require "gls" unless ENV["CI"] # a bit of a hack for now
22
23
 
data/lib/gloss/builder.rb CHANGED
@@ -107,7 +107,14 @@ case node.[](:"type")
107
107
  src.write_ln("end")
108
108
  when "DefNode"
109
109
  args = render_args(node)
110
- src.write_ln("def #{node.[](:"name")}#{args.[](:"representation")}")
110
+ receiver = (if node.[](:"receiver")
111
+ visit_node(node.[](:"receiver"))
112
+ else
113
+ nil
114
+ end)
115
+ src.write_ln("def #{(if receiver
116
+ "#{receiver}."
117
+ end)}#{node.[](:"name")}#{args.[](:"representation")}")
111
118
  return_type = (if node.[](:"return_type")
112
119
  RBS::Types::ClassInstance.new(name: RBS::TypeName.new(name: eval(visit_node(node.[](:"return_type")))
113
120
  .to_s
@@ -121,7 +128,11 @@ case node.[](:"type")
121
128
  nil
122
129
  end), location: node.[](:"location"))]
123
130
  method_definition = RBS::AST::Members::MethodDefinition.new(name: node.[](:"name")
124
- .to_sym, kind: :"instance", types: method_types, annotations: EMPTY_ARRAY, location: node.[](:"location"), comment: node.[](:"comment"), overload: false)
131
+ .to_sym, kind: (if receiver
132
+ :"class"
133
+ else
134
+ :"instance"
135
+ end), types: method_types, annotations: EMPTY_ARRAY, location: node.[](:"location"), comment: node.[](:"comment"), overload: false)
125
136
  (if @current_scope
126
137
  @current_scope.members
127
138
  .<<(method_definition)
@@ -197,7 +208,7 @@ EMPTY_ARRAY }
197
208
  else
198
209
  ".."
199
210
  end)
200
- src.write("(", visit_node(node.[](:"from")), dots, visit_node(node.[](:"to")), ")")
211
+ src.write("(", "(", visit_node(node.[](:"from")), ")", dots, "(", visit_node(node.[](:"to")), ")", ")")
201
212
  when "LiteralNode"
202
213
  src.write(node.[](:"value"))
203
214
  when "ArrayLiteral"
@@ -216,7 +227,7 @@ EMPTY_ARRAY }
216
227
  str.<<(case c.[](:"type")
217
228
  when "LiteralNode"
218
229
  c.[](:"value")
219
- .[]((1...-1))
230
+ .[](((1)...(-1)))
220
231
  else
221
232
  ["\#{", visit_node(c)
222
233
  .strip, "}"].join
@@ -278,11 +289,12 @@ EMPTY_ARRAY }
278
289
  key = case k
279
290
  when String
280
291
  k.to_sym
292
+ .inspect
281
293
  else
282
294
  visit_node(k)
283
295
  end
284
296
  value = visit_node(v)
285
- "#{key.inspect} => #{value}" }
297
+ "#{key} => #{value}" }
286
298
  src.write("{#{contents.join(",\n")}}")
287
299
  (if node.[](:"frozen")
288
300
  src.write(".freeze")
data/lib/gloss/cli.rb CHANGED
@@ -11,10 +11,24 @@ module Gloss
11
11
  end
12
12
  def run()
13
13
  command = @argv.first
14
- files = @argv.[]((1..-1))
14
+ files = @argv.[](((1)..(-1)))
15
15
  err_msg = catch(:"error") { ||
16
16
  case command
17
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
+ }
18
32
  Watcher.new(files)
19
33
  .watch
20
34
  when "build"
@@ -24,7 +38,8 @@ case command
24
38
  files
25
39
  end)
26
40
  .each() { |fp|
27
- puts("=====> Building #{fp}")
41
+ Gloss.logger
42
+ .info("Building #{fp}")
28
43
  content = File.read(fp)
29
44
  tree_hash = Parser.new(content)
30
45
  .run
@@ -32,7 +47,8 @@ case command
32
47
  rb_output = Builder.new(tree_hash, type_checker)
33
48
  .run
34
49
  type_checker.run(rb_output)
35
- puts("=====> Writing #{fp}")
50
+ Gloss.logger
51
+ .info("Writing #{fp}")
36
52
  Writer.new(rb_output, fp)
37
53
  .run
38
54
  }
@@ -51,7 +67,8 @@ case command
51
67
  end
52
68
  nil }
53
69
  (if err_msg
54
- abort(err_msg)
70
+ Gloss.logger.fatal(err_msg)
71
+ exit(1)
55
72
  end)
56
73
  end
57
74
  end
@@ -18,7 +18,8 @@ module Gloss
18
18
  .transform_keys(&:"to_s")
19
19
  .to_yaml)
20
20
  }
21
- puts("Created #{CONFIG_PATH} with default preferences")
21
+ Gloss.logger
22
+ .info("Created #{CONFIG_PATH} with default preferences")
22
23
  end
23
24
  end
24
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
data/lib/gloss/parser.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  ##### This file was generated by Gloss; any changes made here will be overwritten.
4
4
  ##### See src/ to make changes
5
5
 
6
- module Gloss
6
+ module Gloss
7
7
  class Parser
8
8
  def initialize(str)
9
9
  @str = str
@@ -13,7 +13,22 @@
13
13
  begin
14
14
  JSON.parse(tree_json, symbolize_names: true)
15
15
  rescue JSON::ParserError
16
- 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)
17
32
  end
18
33
  end
19
34
  end
@@ -3,7 +3,8 @@
3
3
  ##### This file was generated by Gloss; any changes made here will be overwritten.
4
4
  ##### See src/ to make changes
5
5
 
6
- module Gloss
6
+ require "pry-byebug"
7
+ module Gloss
7
8
  class TypeChecker
8
9
  Project = Struct.new(:"targets")
9
10
  attr_reader(:"steep_target", :"top_level_decls")
@@ -15,30 +16,40 @@
15
16
  @top_level_decls = {}
16
17
  end
17
18
  def run(rb_str)
18
- unless check_types(rb_str)
19
- raise(Errors::TypeError, @steep_target.errors
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
20
28
  .map() { |e|
21
29
  case e
22
- when Steep::Errors::NoMethod
30
+ when Steep::Diagnostic::Ruby::NoMethod
23
31
  "Unknown method :#{e.method}, location: #{e.type
24
32
  .location
25
33
  .inspect}"
26
- when Steep::Errors::MethodBodyTypeMismatch
34
+ when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
27
35
  "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
28
- when Steep::Errors::IncompatibleArguments
29
- " Invalid argmuents - method type: #{e.method_type}\n method name: #{e.method_type
36
+ when Steep::Diagnostic::Ruby::IncompatibleArguments
37
+ "Invalid argmuents - method type: #{e.method_type}\nmethod name: #{e.method_type
30
38
  .method_decls
31
39
  .first
32
40
  .method_name}"
33
- when Steep::Errors::ReturnTypeMismatch
41
+ when Steep::Diagnostic::Ruby::ReturnTypeMismatch
34
42
  "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
35
- when Steep::Errors::IncompatibleAssignment
43
+ when Steep::Diagnostic::Ruby::IncompatibleAssignment
36
44
  "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
45
+ when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
46
+ "Unexpected block given"
37
47
  else
38
48
  e.inspect
39
49
  end
40
50
  }
41
- .join("\n"))
51
+ .join("\n")
52
+ throw(:"error", errors)
42
53
  end
43
54
  true
44
55
  end
@@ -58,6 +69,25 @@ true
58
69
  env = env.resolve_type_names
59
70
  @steep_target.instance_variable_set("@environment", env)
60
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
+ }
61
91
  @steep_target.status
62
92
  .is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
63
93
  .empty?
data/lib/gloss/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  ##### See src/ to make changes
5
5
 
6
6
  module Gloss
7
- VERSION = "0.0.5"
7
+ VERSION = "0.0.6"
8
8
  end
data/lib/gloss/watcher.rb CHANGED
@@ -3,50 +3,72 @@
3
3
  ##### This file was generated by Gloss; any changes made here will be overwritten.
4
4
  ##### See src/ to make changes
5
5
 
6
- require "listen"
6
+ require "listen"
7
7
  module Gloss
8
8
  class Watcher
9
9
  def initialize(paths)
10
10
  @paths = paths
11
11
  (if @paths.empty?
12
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)}/
13
30
  end)
14
31
  end
15
32
  def watch()
16
- puts("=====> Now listening for changes in #{@paths.join(", ")}")
17
- listener = Listen.to(*@paths, latency: 2) { |modified, added, removed|
33
+ Gloss.logger
34
+ .info("Now listening for changes in #{@paths.join(", ")}")
35
+ listener = Listen.to(*@paths, latency: 2, only: @only) { |modified, added, removed|
18
36
  modified.+(added)
19
37
  .each() { |f|
20
- unless f.end_with?(".gl")
21
- next
22
- end
23
- puts("====> Rewriting #{f}")
38
+ Gloss.logger
39
+ .info("Rewriting #{f}")
24
40
  content = File.read(f)
25
- Writer.new(Builder.new(Parser.new(content)
41
+ err = catch(:"error") { ||
42
+ Writer.new(Builder.new(Parser.new(content)
26
43
  .run)
27
44
  .run, f)
28
45
  .run
29
- puts("====> Done")
46
+ nil }
47
+ (if err
48
+ Gloss.logger
49
+ .error(err)
50
+ else
51
+ Gloss.logger
52
+ .info("Done")
53
+ end)
30
54
  }
31
55
  removed.each() { |f|
32
- unless f.end_with?(".gl")
33
- next
34
- end
35
56
  out_path = Utils.src_path_to_output_path(f)
36
- puts("====> Removing #{out_path}")
57
+ Gloss.logger
58
+ .info("Removing #{out_path}")
37
59
  (if File.exist?(out_path)
38
60
  File.delete(out_path)
39
61
  end)
40
- puts("====> Done")
62
+ Gloss.logger
63
+ .info("Done")
41
64
  }
42
65
  }
43
- listener.start
44
66
  begin
45
- loop() { ||
46
- sleep(10)
47
- }
67
+ listener.start
68
+ sleep
48
69
  rescue Interrupt
49
- puts("=====> Interrupt signal received, shutting down")
70
+ Gloss.logger
71
+ .info("Interrupt signal received, shutting down")
50
72
  exit(0)
51
73
  end
52
74
  end
@@ -113,7 +113,8 @@ module Gloss
113
113
  src.write_ln "end"
114
114
  when "DefNode"
115
115
  args = render_args(node)
116
- src.write_ln "def #{node[:name]}#{args[:representation]}"
116
+ receiver = node[:receiver] ? visit_node(node[:receiver]) : nil
117
+ src.write_ln "def #{"#{receiver}." if receiver}#{node[:name]}#{args[:representation]}"
117
118
 
118
119
  return_type = if node[:return_type]
119
120
  RBS::Types::ClassInstance.new(
@@ -162,7 +163,7 @@ module Gloss
162
163
  ]
163
164
  method_definition = RBS::AST::Members::MethodDefinition.new(
164
165
  name: node[:name].to_sym,
165
- kind: :instance,
166
+ kind: receiver ? :class : :instance,
166
167
  types: method_types,
167
168
  annotations: EMPTY_ARRAY, # TODO
168
169
  location: node[:location],
@@ -215,9 +216,10 @@ module Gloss
215
216
  when "RangeLiteral"
216
217
  dots = node[:exclusive] ? "..." : ".."
217
218
 
218
- # parentheses help the compatibility with precendence of operators in some situations
219
+ # parentheses around the whole thing help the compatibility with precendence of operators in some situations
219
220
  # eg. (1..3).cover? 2 vs. 1..3.cover? 2
220
- src.write "(", visit_node(node[:from]), dots, visit_node(node[:to]), ")"
221
+ # parentheses around either number help with things like arithemtic ((x-1)..(y+5))
222
+ src.write "(", "(", visit_node(node[:from]), ")", dots, "(", visit_node(node[:to]), ")", ")"
221
223
 
222
224
  when "LiteralNode"
223
225
 
@@ -296,12 +298,12 @@ module Gloss
296
298
  contents = node[:elements].map do |k, v|
297
299
  key = case k
298
300
  when String
299
- k.to_sym
301
+ k.to_sym.inspect
300
302
  else
301
303
  visit_node k
302
304
  end
303
305
  value = visit_node v
304
- "#{key.inspect} => #{value}"
306
+ "#{key} => #{value}"
305
307
  end
306
308
 
307
309
  src.write "{#{contents.join(",\n")}}"
data/src/lib/gloss/cli.gl CHANGED
@@ -13,17 +13,25 @@ module Gloss
13
13
  err_msg = catch :error do
14
14
  case command
15
15
  when "watch"
16
+ files = files.map do |f|
17
+ path = Pathname.new(f).absolute? ? f : File.join(Dir.pwd, f)
18
+ if Pathname.new(path).exist?
19
+ path
20
+ else
21
+ throw :error, "Pathname #{f} does not exist"
22
+ end
23
+ end
16
24
  Watcher.new(files).watch
17
25
  when "build"
18
26
  (files.empty? ? Dir.glob("#{Config.src_dir}/**/*.gl") : files).each do |fp|
19
- puts "=====> Building #{fp}"
27
+ Gloss.logger.info "Building #{fp}"
20
28
  content = File.read(fp)
21
29
  tree_hash = Parser.new(content).run
22
30
  type_checker = TypeChecker.new
23
31
  rb_output = Builder.new(tree_hash, type_checker).run
24
32
  type_checker.run(rb_output)
25
33
 
26
- puts "=====> Writing #{fp}"
34
+ Gloss.logger.info "Writing #{fp}"
27
35
  Writer.new(rb_output, fp).run
28
36
  end
29
37
  when "init"
@@ -38,7 +46,10 @@ module Gloss
38
46
  nil
39
47
  end
40
48
 
41
- abort err_msg if err_msg
49
+ if err_msg
50
+ Gloss.logger.fatal err_msg
51
+ exit 1
52
+ end
42
53
  end
43
54
  end
44
55
  end
@@ -14,7 +14,7 @@ module Gloss
14
14
  file.puts Config.default_config.transform_keys(&:to_s).to_yaml
15
15
  end
16
16
 
17
- puts "Created #{CONFIG_PATH} with default preferences"
17
+ Gloss.logger.info "Created #{CONFIG_PATH} with default preferences"
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,26 @@
1
+ module Gloss
2
+ def self.logger
3
+ if @logger
4
+ @logger
5
+ else
6
+ env_log_level = ENV.fetch("LOG_LEVEL") { "INFO" }
7
+ real_log_level = {
8
+ "UNKNOWN" => Logger::UNKNOWN,
9
+ "FATAL" => Logger::FATAL,
10
+ "ERROR" => Logger::ERROR,
11
+ "WARN" => Logger::WARN,
12
+ "INFO" => Logger::INFO,
13
+ "DEBUG" => Logger::DEBUG,
14
+ "NIL" => nil,
15
+ nil => nil,
16
+ "" => nil
17
+ }.fetch env_log_level
18
+ @logger = Logger.new(real_log_level ? STDOUT : nil)
19
+ formatter = Logger::Formatter.new
20
+ @logger.formatter = proc do |severity, datetime, progname, msg|
21
+ formatter.call(severity, datetime, progname, msg)
22
+ end
23
+ @logger
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Gloss
4
2
  class Parser
5
3
  def initialize(@str : String)
@@ -10,9 +8,23 @@ module Gloss
10
8
  begin
11
9
  JSON.parse tree_json, symbolize_names: true
12
10
  rescue JSON::ParserError
13
- # if parsing fails then tree is invalid and most likely an error message from the parser in
14
- # crystal
15
- raise Errors::ParserError, tree_json
11
+ error_message = tree_json
12
+ error_message.match /.+\s:(\d+)$/
13
+ if $1
14
+ line_number = $1.to_i
15
+ # line numbers start at 1, but array index starts at 0; so this still gives one line
16
+ # either side of the offending line
17
+ context = @str.lines[(line_number - 2)..(line_number)].map.with_index { |line, index|
18
+ "#{index - 1 + line_number}| #{line}"
19
+ }.join
20
+ error_message = <<~MSG
21
+ #{context.rstrip}
22
+
23
+ #{error_message}
24
+
25
+ MSG
26
+ end
27
+ throw :error, error_message
16
28
  end
17
29
  end
18
30
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "pry-byebug"
2
3
 
3
4
  module Gloss
4
5
  class TypeChecker
@@ -20,27 +21,37 @@ module Gloss
20
21
  end
21
22
 
22
23
  def run(rb_str)
23
- unless check_types(rb_str)
24
- raise Errors::TypeError,
25
- @steep_target.errors.map { |e|
26
- case e
27
- when Steep::Errors::NoMethod
28
- "Unknown method :#{e.method}, location: #{e.type.location.inspect}"
29
- when Steep::Errors::MethodBodyTypeMismatch
30
- "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
31
- when Steep::Errors::IncompatibleArguments
32
- <<-ERR
33
- Invalid argmuents - method type: #{e.method_type}
34
- method name: #{e.method_type.method_decls.first.method_name}
35
- ERR
36
- when Steep::Errors::ReturnTypeMismatch
37
- "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
38
- when Steep::Errors::IncompatibleAssignment
39
- "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
40
- else
41
- e.inspect
42
- end
43
- }.join("\n")
24
+ begin
25
+ valid_types = check_types rb_str
26
+ rescue ParseError => e
27
+ throw :error, ""
28
+ rescue => e
29
+ throw :error, "Type checking Error: #{e.message} (#{e.class})"
30
+ end
31
+
32
+ unless valid_types
33
+ errors = @steep_target.errors.map { |e|
34
+ case e
35
+ when Steep::Diagnostic::Ruby::NoMethod
36
+ "Unknown method :#{e.method}, location: #{e.type.location.inspect}"
37
+ when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
38
+ "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
39
+ when Steep::Diagnostic::Ruby::IncompatibleArguments
40
+ <<-ERR
41
+ Invalid argmuents - method type: #{e.method_type}
42
+ method name: #{e.method_type.method_decls.first.method_name}
43
+ ERR
44
+ when Steep::Diagnostic::Ruby::ReturnTypeMismatch
45
+ "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
46
+ when Steep::Diagnostic::Ruby::IncompatibleAssignment
47
+ "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
48
+ when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
49
+ "Unexpected block given"
50
+ else
51
+ e.inspect
52
+ end
53
+ }.join("\n")
54
+ throw :error, errors
44
55
  end
45
56
 
46
57
  true
@@ -65,6 +76,23 @@ module Gloss
65
76
 
66
77
  @steep_target.type_check
67
78
 
79
+ if @steep_target.status.is_a? Steep::Project::Target::SignatureErrorStatus
80
+ throw :error, @steep_target.status.errors.map { |e|
81
+ <<~MSG
82
+ SignatureSyntaxError:
83
+ Location: #{e.location}
84
+ Message: "#{e.exception.error_value.value}"
85
+ MSG
86
+ }.join("\n")
87
+ end
88
+
89
+ @steep_target.source_files.each do |path, f|
90
+ if f.status.is_a? Steep::Project::SourceFile::ParseErrorStatus
91
+ e = f.status.error
92
+ throw :error, "#{e.class}: #{e.message}"
93
+ end
94
+ end
95
+
68
96
  @steep_target.status.is_a?(Steep::Project::Target::TypeCheckStatus) &&
69
97
  @steep_target.no_error? &&
70
98
  @steep_target.errors.empty?
@@ -1,3 +1,3 @@
1
1
  module Gloss
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -5,42 +5,61 @@ require "listen"
5
5
  module Gloss
6
6
  class Watcher
7
7
  def initialize(@paths : Array[String])
8
- @paths = [File.join(Dir.pwd, Config.src_dir)] if @paths.empty?
8
+ if @paths.empty?
9
+ @paths = [File.join(Dir.pwd, Config.src_dir)]
10
+ @only = /\.gl$/
11
+ else
12
+ file_names = Array.new
13
+ paths = Array.new
14
+ @paths.each do |pa|
15
+ pn = Pathname.new(pa)
16
+ paths << pn.parent.to_s
17
+ file_names << (pn.file? ? pn.basename.to_s : pa)
18
+ end
19
+ @paths = paths.uniq
20
+ @only = /#{Regexp.union(file_names)}/
21
+ end
9
22
  end
10
23
 
11
24
  def watch
12
- puts "=====> Now listening for changes in #{@paths.join(', ')}"
13
- listener = Listen.to(*@paths, latency: 2) do |modified, added, removed|
25
+ Gloss.logger.info "Now listening for changes in #{@paths.join(', ')}"
26
+ listener = Listen.to(
27
+ *@paths,
28
+ latency: 2,
29
+ only: @only
30
+ ) do |modified, added, removed|
14
31
  (modified + added).each do |f|
15
- next unless f.end_with? ".gl"
16
-
17
- puts "====> Rewriting #{f}"
32
+ Gloss.logger.info "Rewriting #{f}"
18
33
  content = File.read(f)
19
- Writer.new(
20
- Builder.new(
21
- Parser.new(
22
- content
23
- ).run
24
- ).run, f
25
- ).run
26
-
27
- puts "====> Done"
34
+ err = catch :error do
35
+ Writer.new(
36
+ Builder.new(
37
+ Parser.new(
38
+ content
39
+ ).run
40
+ ).run, f
41
+ ).run
42
+ nil
43
+ end
44
+ if err
45
+ Gloss.logger.error err
46
+ else
47
+ Gloss.logger.info "Done"
48
+ end
28
49
  end
29
50
  removed.each do |f|
30
- next unless f.end_with? ".gl"
31
-
32
51
  out_path = Utils.src_path_to_output_path(f)
33
- puts "====> Removing #{out_path}"
52
+ Gloss.logger.info "Removing #{out_path}"
34
53
  File.delete out_path if File.exist? out_path
35
54
 
36
- puts "====> Done"
55
+ Gloss.logger.info "Done"
37
56
  end
38
57
  end
39
- listener.start
40
58
  begin
41
- loop { sleep 10 }
59
+ listener.start
60
+ sleep
42
61
  rescue Interrupt
43
- puts "=====> Interrupt signal received, shutting down"
62
+ Gloss.logger.info "Interrupt signal received, shutting down"
44
63
  exit 0
45
64
  end
46
65
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - johansenja
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-31 00:00:00.000000000 Z
11
+ date: 2021-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_blank
@@ -131,8 +131,9 @@ extensions:
131
131
  - ext/gloss/extconf.rb
132
132
  extra_rdoc_files: []
133
133
  files:
134
- - ".github/workflows/crystal.yml"
135
- - ".github/workflows/ruby.yml"
134
+ - ".gitattributes"
135
+ - ".github/workflows/crystal_specs.yml"
136
+ - ".github/workflows/ruby_specs.yml"
136
137
  - ".gitignore"
137
138
  - ".gloss.yml"
138
139
  - ".rspec"
@@ -163,6 +164,7 @@ files:
163
164
  - lib/gloss/config.rb
164
165
  - lib/gloss/errors.rb
165
166
  - lib/gloss/initializer.rb
167
+ - lib/gloss/logger.rb
166
168
  - lib/gloss/parser.rb
167
169
  - lib/gloss/scope.rb
168
170
  - lib/gloss/source.rb
@@ -177,6 +179,7 @@ files:
177
179
  - src/lib/gloss/config.gl
178
180
  - src/lib/gloss/errors.gl
179
181
  - src/lib/gloss/initializer.gl
182
+ - src/lib/gloss/logger.gl
180
183
  - src/lib/gloss/parser.gl
181
184
  - src/lib/gloss/scope.gl
182
185
  - src/lib/gloss/source.gl