Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jemc committed Apr 17, 2022
1 parent d6f6d84 commit edb03af
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 19 deletions.
31 changes: 30 additions & 1 deletion main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module Savi
option "-b", "--backtrace", desc: "Show backtrace on error", type: Bool, default: false
option "-r", "--release", desc: "Compile in release mode", type: Bool, default: false
option "--fix", desc: "Auto-fix compile errors where possible", type: Bool, default: false
option "--watch", desc: "Run continuously, watching for file changes (EXPERIMENTAL)", type: Bool, default: false
option "--no-debug", desc: "Compile without debug info", type: Bool, default: false
option "--llvm-ir", desc: "Write generated LLVM IR to a file", type: Bool, default: false
option "--llvm-keep-fns", desc: "Don't allow LLVM to remove functions from the output", type: Bool, default: false
Expand All @@ -96,7 +97,11 @@ module Savi
options.target_pass = Savi::Compiler.pass_symbol(opts.pass) if opts.pass
options.manifest_name = args.name.not_nil! if args.name
Dir.cd(opts.cd.not_nil!) if opts.cd
Cli.run options, opts.backtrace
if opts.watch
Cli.run_with_watch(options, opts.backtrace)
else
Cli.run(options, opts.backtrace)
end
end
end
sub "build" do
Expand Down Expand Up @@ -302,6 +307,30 @@ module Savi
end
end

# This feature is experimental - it's currently not quite working,
# due to some issues with inaccurate or incomplete caching of passes.
# Also, it doesn't watch precisely the right set of files -
# ideally it would watch all of the package globs that are in use,
# as well as the manifest files where those packages are defined.
# Once we get those issues ironed out, we should mark it as being
# no longer experimental, and publicize it as a recommended way of working.
def self.run_with_watch(options, backtrace = false)
ctx = Savi.compiler.compile(Dir.current, options.target_pass || :run, options)
finish_with_errors(ctx.errors, backtrace) if ctx.errors.any?
last_compiled_at = Time.utc

FSWatch.watch(".", latency: 0.25, recursive: true) do |event|
next unless event.created? || event.updated? || event.removed? || event.renamed?
next if event.timestamp < last_compiled_at

ctx = Savi.compiler.compile(Dir.current, options.target_pass || :run, options)
finish_with_errors(ctx.errors, backtrace) if ctx.errors.any?
last_compiled_at = Time.utc
end

sleep
end

def self.eval(code, options, backtrace = false)
_add_backtrace backtrace do
dirname = "/tmp/savi-eval-#{Random::Secure.hex}"
Expand Down
14 changes: 7 additions & 7 deletions spec/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe Savi::Parser do
c * 9 / 5 + 32.0
SOURCE

ast = Savi::Parser.parse(source)
ast = Savi::Parser.parse(nil, source)

ast.to_a.pretty_inspect(74).should eq <<-AST
[:doc,
Expand Down Expand Up @@ -65,7 +65,7 @@ describe Savi::Parser do
~x
SOURCE

ast = Savi::Parser.parse(source)
ast = Savi::Parser.parse(nil, source)

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

expect_raises Savi::Error, expected do
Savi::Parser.parse(source)
Savi::Parser.parse(nil, source)
end
end

Expand All @@ -205,7 +205,7 @@ describe Savi::Parser do
>>>
SOURCE

ast = Savi::Parser.parse(source)
ast = Savi::Parser.parse(nil, source)

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

ast = Savi::Parser.parse(source)
ast = Savi::Parser.parse(nil, source)

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

ast1 = Savi::Parser.parse(Savi::Source.new_example(content))
ast2 = Savi::Parser.parse(Savi::Source.new_example(content))
ast1 = Savi::Parser.parse(nil, Savi::Source.new_example(content))
ast2 = Savi::Parser.parse(nil, Savi::Source.new_example(content))

ast1.should be ast2
end
Expand Down
2 changes: 1 addition & 1 deletion src/savi/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ class Savi::Compiler

docs = sources.compact_map do |source|
begin
Parser.parse(source)
Parser.parse(ctx, source)
rescue err : Pegmatite::Pattern::MatchError
pos = Source::Pos.point(source, err.offset)
ctx.errors << Error.build(pos, "The source code syntax is invalid near here")
Expand Down
8 changes: 5 additions & 3 deletions src/savi/compiler/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Savi::Compiler::Context
return package if package

sources = compiler.source_service.get_directory_sources(path, source_package)
docs = sources.map { |source| Parser.parse(source) }
docs = sources.map { |source| Parser.parse(self, source) }
compile_package(source_package, docs)
end

Expand All @@ -90,14 +90,14 @@ class Savi::Compiler::Context

# Otherwise go ahead and load the manifests.
sources = compiler.source_service.get_manifest_sources_at(path)
docs = sources.map { |source| Parser.parse(source) }
docs = sources.map { |source| Parser.parse(self, source) }
package = compile_package(sources.first.package, docs)
self
end

def compile_package(manifest : Packaging::Manifest)
sources = compiler.source_service.get_sources_for_manifest(self, manifest)
docs = sources.map { |source| Parser.parse(source) }
docs = sources.map { |source| Parser.parse(self, source) }
sources << Source.none if sources.empty?
compile_package(sources.first.package, docs)
end
Expand All @@ -119,6 +119,8 @@ class Savi::Compiler::Context
if (cache_result = @@cache[source_package.path]?; cache_result)
cached_docs, cached_package = cache_result
return cached_package if cached_docs == docs

puts " RERUN . compile_package #{source_package.path}" if self.options.print_perf
end

compile_package_docs(Program::Package.new(source_package), docs)
Expand Down
2 changes: 1 addition & 1 deletion src/savi/compiler/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Savi::Compiler::Macros < Savi::AST::CopyOnMutateVisitor
cached_hash, cached_func = cache_result if cache_result
return cached_func if cached_func && cached_hash == input_hash

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

yield

Expand Down
2 changes: 1 addition & 1 deletion src/savi/compiler/populate.cr
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class Savi::Compiler::Populate
cached_hash, cached_func = cache_result if cache_result
return cached_func if cached_func && cached_hash == input_hash

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

yield

Expand Down
6 changes: 3 additions & 3 deletions src/savi/compiler/reparse.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Savi::Compiler::Reparse < Savi::AST::CopyOnMutateVisitor
cached_hash, cached_func = cache_result if cache_result
return cached_func if cached_func && cached_hash == input_hash

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

yield

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

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

yield

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

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

yield

Expand Down
2 changes: 1 addition & 1 deletion src/savi/compiler/sugar.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Savi::Compiler::Sugar < Savi::AST::CopyOnMutateVisitor
cached_hash, cached_func = cache_result if cache_result
return cached_func if cached_func && cached_hash == input_hash

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

yield

Expand Down
6 changes: 6 additions & 0 deletions src/savi/ext/fswatch/event.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Add a timestamp property to all FSWatch events, so that we can track
# when the event was received by the thread that received it, rather than
# the time when we got around to first noticing it in our processing loop.
struct FSWatch::Event
property timestamp : Time = Time.utc
end
5 changes: 4 additions & 1 deletion src/savi/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ require "pegmatite"

module Savi::Parser
@@cache = {} of String => {Source, AST::Document}
def self.parse(source : Source)
def self.parse(ctx, source : Source)
if (cache_result = @@cache[source.path]?; cache_result)
cached_source, cached_ast = cache_result
return cached_ast if cached_source == source

puts " RERUN . #{self} #{source.path}" if ctx.try(&.options.print_perf)
end

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

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

.tap do |result|
Expand Down

0 comments on commit edb03af

Please sign in to comment.