Skip to content

Commit edb03af

Browse files
committed
WIP
1 parent d6f6d84 commit edb03af

File tree

10 files changed

+59
-19
lines changed

10 files changed

+59
-19
lines changed

main.cr

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ module Savi
7878
option "-b", "--backtrace", desc: "Show backtrace on error", type: Bool, default: false
7979
option "-r", "--release", desc: "Compile in release mode", type: Bool, default: false
8080
option "--fix", desc: "Auto-fix compile errors where possible", type: Bool, default: false
81+
option "--watch", desc: "Run continuously, watching for file changes (EXPERIMENTAL)", type: Bool, default: false
8182
option "--no-debug", desc: "Compile without debug info", type: Bool, default: false
8283
option "--llvm-ir", desc: "Write generated LLVM IR to a file", type: Bool, default: false
8384
option "--llvm-keep-fns", desc: "Don't allow LLVM to remove functions from the output", type: Bool, default: false
@@ -96,7 +97,11 @@ module Savi
9697
options.target_pass = Savi::Compiler.pass_symbol(opts.pass) if opts.pass
9798
options.manifest_name = args.name.not_nil! if args.name
9899
Dir.cd(opts.cd.not_nil!) if opts.cd
99-
Cli.run options, opts.backtrace
100+
if opts.watch
101+
Cli.run_with_watch(options, opts.backtrace)
102+
else
103+
Cli.run(options, opts.backtrace)
104+
end
100105
end
101106
end
102107
sub "build" do
@@ -302,6 +307,30 @@ module Savi
302307
end
303308
end
304309

310+
# This feature is experimental - it's currently not quite working,
311+
# due to some issues with inaccurate or incomplete caching of passes.
312+
# Also, it doesn't watch precisely the right set of files -
313+
# ideally it would watch all of the package globs that are in use,
314+
# as well as the manifest files where those packages are defined.
315+
# Once we get those issues ironed out, we should mark it as being
316+
# no longer experimental, and publicize it as a recommended way of working.
317+
def self.run_with_watch(options, backtrace = false)
318+
ctx = Savi.compiler.compile(Dir.current, options.target_pass || :run, options)
319+
finish_with_errors(ctx.errors, backtrace) if ctx.errors.any?
320+
last_compiled_at = Time.utc
321+
322+
FSWatch.watch(".", latency: 0.25, recursive: true) do |event|
323+
next unless event.created? || event.updated? || event.removed? || event.renamed?
324+
next if event.timestamp < last_compiled_at
325+
326+
ctx = Savi.compiler.compile(Dir.current, options.target_pass || :run, options)
327+
finish_with_errors(ctx.errors, backtrace) if ctx.errors.any?
328+
last_compiled_at = Time.utc
329+
end
330+
331+
sleep
332+
end
333+
305334
def self.eval(code, options, backtrace = false)
306335
_add_backtrace backtrace do
307336
dirname = "/tmp/savi-eval-#{Random::Secure.hex}"

spec/parser_spec.cr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe Savi::Parser do
1414
c * 9 / 5 + 32.0
1515
SOURCE
1616

17-
ast = Savi::Parser.parse(source)
17+
ast = Savi::Parser.parse(nil, source)
1818

1919
ast.to_a.pretty_inspect(74).should eq <<-AST
2020
[:doc,
@@ -65,7 +65,7 @@ describe Savi::Parser do
6565
~x
6666
SOURCE
6767

68-
ast = Savi::Parser.parse(source)
68+
ast = Savi::Parser.parse(nil, source)
6969

7070
# Can't use array literals here because Crystal is too slow to compile them.
7171
# See https://github.com/crystal-lang/crystal/issues/5792
@@ -181,7 +181,7 @@ describe Savi::Parser do
181181
MSG
182182

183183
expect_raises Savi::Error, expected do
184-
Savi::Parser.parse(source)
184+
Savi::Parser.parse(nil, source)
185185
end
186186
end
187187

@@ -205,7 +205,7 @@ describe Savi::Parser do
205205
>>>
206206
SOURCE
207207

208-
ast = Savi::Parser.parse(source)
208+
ast = Savi::Parser.parse(nil, source)
209209

210210
ast.to_a.should eq [:doc,
211211
[:declare, [:ident, "actor"], [:ident, "Main"]],
@@ -226,7 +226,7 @@ describe Savi::Parser do
226226
:const y U64: -1
227227
SOURCE
228228

229-
ast = Savi::Parser.parse(source)
229+
ast = Savi::Parser.parse(nil, source)
230230

231231
# Can't use array literals here because Crystal is too slow to compile them.
232232
ast.to_a.pretty_inspect(74).should eq <<-AST
@@ -245,8 +245,8 @@ describe Savi::Parser do
245245
:const greeting String: "Hello, World!"
246246
SOURCE
247247

248-
ast1 = Savi::Parser.parse(Savi::Source.new_example(content))
249-
ast2 = Savi::Parser.parse(Savi::Source.new_example(content))
248+
ast1 = Savi::Parser.parse(nil, Savi::Source.new_example(content))
249+
ast2 = Savi::Parser.parse(nil, Savi::Source.new_example(content))
250250

251251
ast1.should be ast2
252252
end

src/savi/compiler.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class Savi::Compiler
261261

262262
docs = sources.compact_map do |source|
263263
begin
264-
Parser.parse(source)
264+
Parser.parse(ctx, source)
265265
rescue err : Pegmatite::Pattern::MatchError
266266
pos = Source::Pos.point(source, err.offset)
267267
ctx.errors << Error.build(pos, "The source code syntax is invalid near here")

src/savi/compiler/context.cr

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class Savi::Compiler::Context
8080
return package if package
8181

8282
sources = compiler.source_service.get_directory_sources(path, source_package)
83-
docs = sources.map { |source| Parser.parse(source) }
83+
docs = sources.map { |source| Parser.parse(self, source) }
8484
compile_package(source_package, docs)
8585
end
8686

@@ -90,14 +90,14 @@ class Savi::Compiler::Context
9090

9191
# Otherwise go ahead and load the manifests.
9292
sources = compiler.source_service.get_manifest_sources_at(path)
93-
docs = sources.map { |source| Parser.parse(source) }
93+
docs = sources.map { |source| Parser.parse(self, source) }
9494
package = compile_package(sources.first.package, docs)
9595
self
9696
end
9797

9898
def compile_package(manifest : Packaging::Manifest)
9999
sources = compiler.source_service.get_sources_for_manifest(self, manifest)
100-
docs = sources.map { |source| Parser.parse(source) }
100+
docs = sources.map { |source| Parser.parse(self, source) }
101101
sources << Source.none if sources.empty?
102102
compile_package(sources.first.package, docs)
103103
end
@@ -119,6 +119,8 @@ class Savi::Compiler::Context
119119
if (cache_result = @@cache[source_package.path]?; cache_result)
120120
cached_docs, cached_package = cache_result
121121
return cached_package if cached_docs == docs
122+
123+
puts " RERUN . compile_package #{source_package.path}" if self.options.print_perf
122124
end
123125

124126
compile_package_docs(Program::Package.new(source_package), docs)

src/savi/compiler/macros.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Savi::Compiler::Macros < Savi::AST::CopyOnMutateVisitor
2525
cached_hash, cached_func = cache_result if cache_result
2626
return cached_func if cached_func && cached_hash == input_hash
2727

28-
puts " RERUN . #{self.class} #{f_link.show}" if cache_result && ctx.options.print_perf
28+
puts " RERUN . #{self} #{f_link.show}" if cache_result && ctx.options.print_perf
2929

3030
yield
3131

src/savi/compiler/populate.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class Savi::Compiler::Populate
191191
cached_hash, cached_func = cache_result if cache_result
192192
return cached_func if cached_func && cached_hash == input_hash
193193

194-
puts " RERUN . #{self.class} #{f_link.show}" if cache_result && ctx.options.print_perf
194+
puts " RERUN . #{self} #{f_link.show}" if cache_result && ctx.options.print_perf
195195

196196
yield
197197

src/savi/compiler/reparse.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Savi::Compiler::Reparse < Savi::AST::CopyOnMutateVisitor
2525
cached_hash, cached_func = cache_result if cache_result
2626
return cached_func if cached_func && cached_hash == input_hash
2727

28-
puts " RERUN . #{self.class} #{f_link.show}" if cache_result && ctx.options.print_perf
28+
puts " RERUN . #{self} #{f_link.show}" if cache_result && ctx.options.print_perf
2929

3030
yield
3131

@@ -42,7 +42,7 @@ class Savi::Compiler::Reparse < Savi::AST::CopyOnMutateVisitor
4242
t_cached_hash, t_cached_type = t_cache_result if t_cache_result
4343
return t_cached_type if t_cached_type && t_cached_hash == input_hash
4444

45-
puts " RERUN . #{self.class} #{t_link.show}" if t_cache_result && ctx.options.print_perf
45+
puts " RERUN . #{self} #{t_link.show}" if t_cache_result && ctx.options.print_perf
4646

4747
yield
4848

@@ -59,7 +59,7 @@ class Savi::Compiler::Reparse < Savi::AST::CopyOnMutateVisitor
5959
ta_cached_hash, ta_cached_type = ta_cache_result if ta_cache_result
6060
return ta_cached_type if ta_cached_type && ta_cached_hash == input_hash
6161

62-
puts " RERUN . #{self.class} #{t_link.show}" if ta_cache_result && ctx.options.print_perf
62+
puts " RERUN . #{self} #{t_link.show}" if ta_cache_result && ctx.options.print_perf
6363

6464
yield
6565

src/savi/compiler/sugar.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Savi::Compiler::Sugar < Savi::AST::CopyOnMutateVisitor
2020
cached_hash, cached_func = cache_result if cache_result
2121
return cached_func if cached_func && cached_hash == input_hash
2222

23-
puts " RERUN . #{self.class} #{f_link.show}" if cache_result && ctx.options.print_perf
23+
puts " RERUN . #{self} #{f_link.show}" if cache_result && ctx.options.print_perf
2424

2525
yield
2626

src/savi/ext/fswatch/event.cr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Add a timestamp property to all FSWatch events, so that we can track
2+
# when the event was received by the thread that received it, rather than
3+
# the time when we got around to first noticing it in our processing loop.
4+
struct FSWatch::Event
5+
property timestamp : Time = Time.utc
6+
end

src/savi/parser.cr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ require "pegmatite"
22

33
module Savi::Parser
44
@@cache = {} of String => {Source, AST::Document}
5-
def self.parse(source : Source)
5+
def self.parse(ctx, source : Source)
66
if (cache_result = @@cache[source.path]?; cache_result)
77
cached_source, cached_ast = cache_result
88
return cached_ast if cached_source == source
9+
10+
puts " RERUN . #{self} #{source.path}" if ctx.try(&.options.print_perf)
911
end
1012

1113
grammar =
@@ -14,6 +16,7 @@ module Savi::Parser
1416
else raise NotImplementedError.new("#{source.language} language parsing")
1517
end
1618

19+
puts " RUN . #{self} #{source.path}" if ctx.try(&.options.print_perf)
1720
Builder.build(Pegmatite.tokenize(grammar, source.content), source)
1821

1922
.tap do |result|

0 commit comments

Comments
 (0)