diff --git a/Makefile b/Makefile index f54da47a..ad5ee4f5 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,10 @@ example.deps: PHONY SAVI # Generate Savi and Crystal source code from CapnProto definitions. gen.capnp: PHONY self-hosted.deps $(BUILD)/capnpc-savi $(BUILD)/capnpc-crystal + capnp compile \ + -I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \ + self-hosted/src/SaviProto/SaviProto.Artifact.capnp --output=- \ + | $(BUILD)/capnpc-savi > self-hosted/src/SaviProto/SaviProto.Artifact.capnp.savi capnp compile \ -I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \ self-hosted/src/SaviProto/SaviProto.AST.capnp --output=- \ @@ -111,6 +115,10 @@ gen.capnp: PHONY self-hosted.deps $(BUILD)/capnpc-savi $(BUILD)/capnpc-crystal -I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \ self-hosted/src/SaviProto/SaviProto.AST.capnp --output=- \ | $(BUILD)/capnpc-crystal > self-hosted/src/SaviProto/SaviProto.AST.capnp.cr + capnp compile \ + -I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \ + self-hosted/src/SaviProto/SaviProto.Artifact.capnp --output=- \ + | $(BUILD)/capnpc-crystal > self-hosted/src/SaviProto/SaviProto.Artifact.capnp.cr capnp compile \ -I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \ self-hosted/src/SaviProto/SaviProto.Source.capnp --output=- \ @@ -118,13 +126,19 @@ gen.capnp: PHONY self-hosted.deps $(BUILD)/capnpc-savi $(BUILD)/capnpc-crystal gen.capnp.check: gen.capnp git diff --exit-code self-hosted/src/SaviProto -# Update deps for the Self-hosted Savi subprograms. +# Update deps for the Self-hosted Savi compiler subprograms. self-hosted.deps: PHONY SAVI + echo && $(SAVI) deps update --cd self-hosted --for savi-lang-broker + echo && $(SAVI) deps update --cd self-hosted --for savi-lang-plumber echo && $(SAVI) deps update --cd self-hosted --for savi-lang-parse -# Create the self-hosted Savi parse subprogram. -self-hosted/bin/savi-lang-parse: $(SAVI) $(shell find self-hosted/src/savi-lang-parse self-hosted/src/SaviProto -name '*.savi') - echo && $(SAVI) --cd self-hosted savi-lang-parse --print-perf --backtrace +# Create the self-hosted Savi compiler subprograms. +self-hosted/bin/savi-lang-broker: $(SAVI) $(shell find self-hosted/src/savi-lang-broker self-hosted/src/SaviWorker self-hosted/src/SaviProto -name '*.savi') + echo && $(SAVI) build --cd self-hosted savi-lang-broker --print-perf --backtrace +self-hosted/bin/savi-lang-plumber: $(SAVI) $(shell find self-hosted/src/savi-lang-plumber self-hosted/src/SaviWorker self-hosted/src/SaviProto -name '*.savi') + echo && $(SAVI) build --cd self-hosted savi-lang-plumber --print-perf --backtrace +self-hosted/bin/savi-lang-parse: $(SAVI) $(shell find self-hosted/src/savi-lang-parse self-hosted/src/SaviWorker self-hosted/src/SaviProto -name '*.savi') + echo && $(SAVI) build --cd self-hosted savi-lang-parse --print-perf --backtrace # Run spec scripts for self-hosted Savi subprograms. spec.self-hosted: PHONY self-hosted/bin/$(name) diff --git a/self-hosted/manifest.savi b/self-hosted/manifest.savi index 430a9029..f7cbbd64 100644 --- a/self-hosted/manifest.savi +++ b/self-hosted/manifest.savi @@ -1,6 +1,8 @@ -:manifest bin "savi-lang-parse" - :sources "src/savi-lang-parse/*.savi" - :sources "src/SaviProto/*.savi" +:manifest lib SaviWorker + :sources "src/SaviWorker/*.savi" + :sources "src/SaviProto/SaviProto.Source.capnp.savi" + :sources "src/SaviProto/SaviProto.Artifact.capnp.savi" + :sources "src/SaviProto/SaviProto.Artifact.Path.savi" :dependency CapnProto v0 :from "github:jemc-savi/CapnProto" @@ -9,25 +11,69 @@ :dependency Map v0 :from "github:savi-lang/Map" - :dependency PEG v0 - :from "github:savi-lang/PEG" - :dependency Time v0 :from "github:savi-lang/Time" - :dependency StdIn v0 - :from "github:savi-lang/StdIn" - :depends on ByteStream - :depends on IO - :depends on OSError + :dependency Timer v0 + :from "github:savi-lang/Timer" + :depends on Time + + :dependency CLI v0 + :from "github:savi-lang/CLI" + :depends on Map + + :dependency Logger v0 + :from "github:savi-lang/Logger" + :depends on Time + + :dependency ByteStream v0 + :from "github:savi-lang/ByteStream" :dependency IO v0 :from "github:savi-lang/IO" :depends on ByteStream :depends on OSError - :transitive dependency ByteStream v0 - :from "github:savi-lang/ByteStream" + :dependency TCP v0 + :from "github:savi-lang/TCP" + :depends on ByteStream + :depends on IO + :depends on OSError + :depends on IPAddress + + :dependency StdIn v0 + :from "github:savi-lang/StdIn" + :depends on ByteStream + :depends on IO + :depends on OSError :transitive dependency OSError v0 :from "github:savi-lang/OSError" + + :transitive dependency IPAddress v0 + :from "github:savi-lang/IPAddress" + + :dependency File v0 + :from "github:savi-lang/File" + :depends on OSError + :depends on Time + :depends on IO + :depends on ByteStream + :depends on Map + +:manifest bin "savi-lang-broker" + :sources "src/savi-lang-broker/*.savi" + :copies SaviWorker + +:manifest bin "savi-lang-plumber" + :sources "src/savi-lang-plumber/*.savi" + :sources "src/SaviProto/SaviProto.AST.capnp.savi" + :copies SaviWorker + +:manifest bin "savi-lang-parse" + :sources "src/savi-lang-parse/*.savi" + :sources "src/SaviProto/SaviProto.AST.capnp.savi" + :copies SaviWorker + + :dependency PEG v0 + :from "github:savi-lang/PEG" diff --git a/self-hosted/spec/savi-lang-parse.sh b/self-hosted/spec/savi-lang-parse.sh index 2b96333c..74dce3b7 100755 --- a/self-hosted/spec/savi-lang-parse.sh +++ b/self-hosted/spec/savi-lang-parse.sh @@ -5,6 +5,6 @@ cd -- "$(dirname -- "$0")" set -e find savi-lang-parse -name '*.savi' | xargs -I '{}' \ - sh -c 'cat {} | ../bin/savi-lang-parse > {}.ast.yaml' + sh -c 'cat {} | ../bin/savi-lang-parse --test-mode > {}.ast.yaml' git diff --exit-code savi-lang-parse diff --git a/self-hosted/src/SaviProto/SaviProto.AST.capnp.cr b/self-hosted/src/SaviProto/SaviProto.AST.capnp.cr new file mode 100644 index 00000000..c2a8bdc6 --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.AST.capnp.cr @@ -0,0 +1,665 @@ + ### + # NOTE: This file was auto-generated from a Cap'n Proto file" + # using the `capnp` compiler with the `--output=cr` option." + + +struct SaviProto::AST + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def is_none : Bool + @p.check_union(0x0, 0) + end + def none! + @p.assert_union!(0x0, 0) + nil + end + + def is_character : Bool + @p.check_union(0x0, 1) + end + def character! + @p.assert_union!(0x0, 1) + @p.u64(0x8) + end + + def is_positive_integer : Bool + @p.check_union(0x0, 2) + end + def positive_integer! + @p.assert_union!(0x0, 2) + @p.u64(0x8) + end + + def is_negative_integer : Bool + @p.check_union(0x0, 3) + end + def negative_integer! + @p.assert_union!(0x0, 3) + @p.u64(0x8) + end + + def is_floating_point : Bool + @p.check_union(0x0, 4) + end + def floating_point! + @p.assert_union!(0x0, 4) + @p.f64(0x8) + end + + def is_name : Bool + @p.check_union(0x0, 5) + end + def name! + @p.assert_union!(0x0, 5) + @p.text(1) + end + + def is_string : Bool + @p.check_union(0x0, 6) + end + def string! + @p.assert_union!(0x0, 6) + @p.text(1) + end + + def is_string_with_prefix : Bool + @p.check_union(0x0, 7) + end + def string_with_prefix! + @p.assert_union!(0x0, 7) + SaviProto::AST::AS_stringWithPrefix.read_from_pointer(@p) + end + + def is_string_compose : Bool + @p.check_union(0x0, 8) + end + def string_compose! + @p.assert_union!(0x0, 8) + SaviProto::AST::AS_stringCompose.read_from_pointer(@p) + end + + def is_prefix : Bool + @p.check_union(0x0, 9) + end + def prefix! + @p.assert_union!(0x0, 9) + SaviProto::AST::AS_prefix.read_from_pointer(@p) + end + + def is_qualify : Bool + @p.check_union(0x0, 10) + end + def qualify! + @p.assert_union!(0x0, 10) + SaviProto::AST::AS_qualify.read_from_pointer(@p) + end + + def is_group : Bool + @p.check_union(0x0, 11) + end + def group! + @p.assert_union!(0x0, 11) + SaviProto::AST::Group.read_from_pointer(@p.struct(1)) + end + + def is_relate : Bool + @p.check_union(0x0, 12) + end + def relate! + @p.assert_union!(0x0, 12) + SaviProto::AST::AS_relate.read_from_pointer(@p) + end + + def is_field_read : Bool + @p.check_union(0x0, 13) + end + def field_read! + @p.assert_union!(0x0, 13) + SaviProto::AST::AS_fieldRead.read_from_pointer(@p) + end + + def is_field_write : Bool + @p.check_union(0x0, 14) + end + def field_write! + @p.assert_union!(0x0, 14) + SaviProto::AST::AS_fieldWrite.read_from_pointer(@p) + end + + def is_field_displace : Bool + @p.check_union(0x0, 15) + end + def field_displace! + @p.assert_union!(0x0, 15) + SaviProto::AST::AS_fieldDisplace.read_from_pointer(@p) + end + + def is_call : Bool + @p.check_union(0x0, 16) + end + def call! + @p.assert_union!(0x0, 16) + SaviProto::AST::Call.read_from_pointer(@p.struct(1)) + end + + def is_choice : Bool + @p.check_union(0x0, 17) + end + def choice! + @p.assert_union!(0x0, 17) + SaviProto::AST::AS_choice.read_from_pointer(@p) + end + + def is_loop : Bool + @p.check_union(0x0, 18) + end + def loop! + @p.assert_union!(0x0, 18) + SaviProto::AST::Loop.read_from_pointer(@p.struct(1)) + end + + def is_try : Bool + @p.check_union(0x0, 19) + end + def try! + @p.assert_union!(0x0, 19) + SaviProto::AST::Try.read_from_pointer(@p.struct(1)) + end + + def is_jump : Bool + @p.check_union(0x0, 20) + end + def jump! + @p.assert_union!(0x0, 20) + SaviProto::AST::AS_jump.read_from_pointer(@p) + end + + def is_yield : Bool + @p.check_union(0x0, 21) + end + def yield! + @p.assert_union!(0x0, 21) + SaviProto::AST::AS_yield.read_from_pointer(@p) + end +end + +struct SaviProto::AST::AS_stringWithPrefix + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def prefix + SaviProto::AST::Name.read_from_pointer(@p.struct(1)) + end + + def string + @p.text(2) + end +end + +struct SaviProto::AST::AS_stringCompose + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def prefix + SaviProto::AST::Name.read_from_pointer(@p.struct(1)) + end + + def terms + CapnProto::List(SaviProto::AST).read_from_pointer(@p.list(2)) + end +end + +struct SaviProto::AST::AS_prefix + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def op + SaviProto::AST::Name.read_from_pointer(@p.struct(1)) + end + + def term + SaviProto::AST.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::AS_qualify + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def term + SaviProto::AST.read_from_pointer(@p.struct(1)) + end + + def group + SaviProto::AST::Group.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::AS_relate + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def op + SaviProto::AST::Name.read_from_pointer(@p.struct(1)) + end + + def terms + SaviProto::AST::Pair.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::AS_fieldRead + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def field + @p.text(1) + end +end + +struct SaviProto::AST::AS_fieldWrite + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def field + @p.text(1) + end + + def value + SaviProto::AST.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::AS_fieldDisplace + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def field + @p.text(1) + end + + def value + SaviProto::AST.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::AS_choice + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def branches + CapnProto::List(SaviProto::AST::ChoiceBranch).read_from_pointer(@p.list(1)) + end +end + +struct SaviProto::AST::AS_jump + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def term + SaviProto::AST.read_from_pointer(@p.struct(1)) + end + + def kind + SaviProto::AST::JumpKind.new(@p.u16(4)) + end +end + +struct SaviProto::AST::AS_yield + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def terms + CapnProto::List(SaviProto::AST).read_from_pointer(@p.list(1)) + end +end + +struct SaviProto::AST::Annotation + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def target + @p.u64(0x0) + end + + def value + @p.text(1) + end +end + +struct SaviProto::AST::Name + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def value + @p.text(1) + end +end + +struct SaviProto::AST::Pair + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def left + SaviProto::AST.read_from_pointer(@p.struct(1)) + end + + def right + SaviProto::AST.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::AST::Group + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def style + SaviProto::AST::Group::Style.new(@p.u16(0)) + end + + def terms + CapnProto::List(SaviProto::AST).read_from_pointer(@p.list(1)) + end + + def has_exclamation + @p.bool(0x2, 1) + end +end + +enum SaviProto::AST::Group::Style + Root = 0 + Paren = 1 + Pipe = 2 + Square = 3 + Curly = 4 + Space = 5 +end + +struct SaviProto::AST::Call + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 4_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def receiver + SaviProto::AST.read_from_pointer(@p.struct(0)) + end + + def name + SaviProto::AST::Name.read_from_pointer(@p.struct(1)) + end + + def args + CapnProto::List(SaviProto::AST).read_from_pointer(@p.list(2)) + end + + def yield + SaviProto::AST::CallYield.read_from_pointer(@p.struct(3)) + end +end + +struct SaviProto::AST::CallYield + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def params + SaviProto::AST::Group.read_from_pointer(@p.struct(0)) + end + + def block + SaviProto::AST::Group.read_from_pointer(@p.struct(1)) + end +end + +struct SaviProto::AST::ChoiceBranch + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def cond + SaviProto::AST.read_from_pointer(@p.struct(0)) + end + + def body + SaviProto::AST.read_from_pointer(@p.struct(1)) + end +end + +struct SaviProto::AST::Loop + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 4_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def initial_cond + SaviProto::AST.read_from_pointer(@p.struct(0)) + end + + def body + SaviProto::AST.read_from_pointer(@p.struct(1)) + end + + def repeat_cond + SaviProto::AST.read_from_pointer(@p.struct(2)) + end + + def else_body + SaviProto::AST.read_from_pointer(@p.struct(3)) + end +end + +struct SaviProto::AST::Try + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def body + SaviProto::AST.read_from_pointer(@p.struct(0)) + end + + def else_body + SaviProto::AST.read_from_pointer(@p.struct(1)) + end + + def allow_non_partial_body + @p.bool(0x0, 1) + end +end + +enum SaviProto::AST::JumpKind + Error = 0 + Return = 1 + Break = 2 + Next = 3 +end + +struct SaviProto::AST::Declare + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 4_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def terms + CapnProto::List(SaviProto::AST).read_from_pointer(@p.list(0)) + end + + def main_annotation + @p.text(1) + end + + def body_annotations + CapnProto::List(SaviProto::AST::Annotation).read_from_pointer(@p.list(2)) + end + + def body + SaviProto::AST::Group.read_from_pointer(@p.struct(3)) + end +end + +struct SaviProto::AST::Document + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def source + SaviProto::Source.read_from_pointer(@p.struct(0)) + end + + def declares + CapnProto::List(SaviProto::AST::Declare).read_from_pointer(@p.list(1)) + end + + def bodies + CapnProto::List(SaviProto::AST::Group).read_from_pointer(@p.list(2)) + end +end \ No newline at end of file diff --git a/self-hosted/src/SaviProto/SaviProto.Artifact.Path.savi b/self-hosted/src/SaviProto/SaviProto.Artifact.Path.savi new file mode 100644 index 00000000..45cf8e6e --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.Artifact.Path.savi @@ -0,0 +1,31 @@ +:module SaviProto.Artifact.Path + :fun read_root(root Env.Root) + File.Path.ReadOnly.new(root, "/tmp/savi-cache") // TODO: configurable + + :fun write_root(root Env.Root) + File.Path.WriteOnly.new(root, "/tmp/savi-cache") // TODO: configurable + + :fun read_from(root File.Path.Readable, name SaviProto.Artifact) + root.subpath(@_for(name)) + + :fun write_to(root File.Path.Writable, name SaviProto.Artifact) + root.subpath(@_for(name)) + + :fun _for(artifact SaviProto.Artifact, out = String.new(0x200)) String'ref + @_for_name(artifact.name, out) + out << "+\(artifact.hash).savi-\(artifact.name.kind)" + out + + :fun _for_name(name SaviProto.Artifact.Name, out String'ref) None + try (p = name.package!, return @_for_package(p, out)) + try (s = name.source!, return @_for_source(s, out)) + out << "UNSUPPORTED-ARTIFACT-NAME-TYPE" + + :fun _for_package(name SaviProto.Artifact.Name.Package, out String'ref) None + out << "\(name.key)" + + :fun _for_source(name SaviProto.Artifact.Name.Source, out String'ref) None + prior_size = out.size + @_for_package(name.package, out) + if out.size != prior_size out << "-" + out << "\(name.key)" diff --git a/self-hosted/src/SaviProto/SaviProto.Artifact.capnp b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp new file mode 100644 index 00000000..7ae49ffa --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp @@ -0,0 +1,42 @@ +@0xf053415649415254; # "\xf0" + "SAVIART" + +using Savi = import "/CapnProto.Savi.Meta.capnp"; +$Savi.namespace("SaviProto"); + +using Source = import "SaviProto.Source.capnp".Source; + +struct Artifact { + name @0 :Artifact.Name; + hash @1 :UInt64; + + struct Name { + kind @0 :Text; + + union { + package @1 :Artifact.Name.Package; + source @2 :Artifact.Name.Source; + } + + struct Package { + key @0 :Text; + path @1 :Text; + } + + struct Source { + package @0 :Artifact.Name.Package; + key @1 :Text; + path @2 :Text; + } + } + + struct Invoke { + id @0 :UInt64; + inputs @1 :List(Artifact); + + struct Result { + id @0 :UInt64; + outputs @1 :List(Artifact); + errors @2 :List(Source.Error); + } + } +} diff --git a/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.cr b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.cr new file mode 100644 index 00000000..14a64692 --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.cr @@ -0,0 +1,138 @@ + ### + # NOTE: This file was auto-generated from a Cap'n Proto file" + # using the `capnp` compiler with the `--output=cr` option." + + +struct SaviProto::Artifact + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 1_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def name + SaviProto::Artifact::Name.read_from_pointer(@p.struct(0)) + end + + def hash + @p.u64(0x0) + end +end + +struct SaviProto::Artifact::Name + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def kind + @p.text(0) + end + + def is_package : Bool + @p.check_union(0x0, 0) + end + def package! + @p.assert_union!(0x0, 0) + SaviProto::Artifact::Name::Package.read_from_pointer(@p.struct(1)) + end + + def is_source : Bool + @p.check_union(0x0, 1) + end + def source! + @p.assert_union!(0x0, 1) + SaviProto::Artifact::Name::Source.read_from_pointer(@p.struct(1)) + end +end + +struct SaviProto::Artifact::Name::Package + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def key + @p.text(0) + end + + def path + @p.text(1) + end +end + +struct SaviProto::Artifact::Name::Source + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def package + SaviProto::Artifact::Name::Package.read_from_pointer(@p.struct(0)) + end + + def key + @p.text(1) + end + + def path + @p.text(2) + end +end + +struct SaviProto::Artifact::Invoke + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 1_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def id + @p.u64(0x0) + end + + def inputs + CapnProto::List(SaviProto::Artifact).read_from_pointer(@p.list(0)) + end +end + +struct SaviProto::Artifact::Invoke::Result + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def id + @p.u64(0x0) + end + + def outputs + CapnProto::List(SaviProto::Artifact).read_from_pointer(@p.list(0)) + end + + def errors + CapnProto::List(SaviProto::Source::Error).read_from_pointer(@p.list(1)) + end +end \ No newline at end of file diff --git a/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.savi b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.savi new file mode 100644 index 00000000..9943e644 --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.Artifact.capnp.savi @@ -0,0 +1,352 @@ +/// +// NOTE: This file was auto-generated from a Cap'n Proto file +// using the `capnp` compiler with the `--output=savi` option. + +:struct box SaviProto.Artifact + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 1 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("name", @name_if_set!) + try trace.property("hash", @hash_if_set!) + ) + + :fun name: SaviProto.Artifact.Name.read_from_pointer(@_p.struct(0)) + :fun name_if_set!: SaviProto.Artifact.Name.read_from_pointer(@_p.struct_if_set!(0)) + + :fun hash: @_p.u64(0x0) + :fun hash_if_set!: @_p.u64_if_set!(0x0) + +:struct box SaviProto.Artifact.Name + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("kind", @kind_if_set!) + try trace.property("package", @package!) + try trace.property("source", @source!) + ) + + :fun kind: @_p.text(0) + :fun kind_if_set!: @_p.text_if_set!(0) + + :fun is_package: @_p.check_union(0x0, 0) + :fun package!: @_p.assert_union!(0x0, 0), SaviProto.Artifact.Name.Package.read_from_pointer(@_p.struct(1)) + :fun package_if_set!: @_p.assert_union!(0x0, 0), SaviProto.Artifact.Name.Package.read_from_pointer(@_p.struct_if_set!(1)) + + :fun is_source: @_p.check_union(0x0, 1) + :fun source!: @_p.assert_union!(0x0, 1), SaviProto.Artifact.Name.Source.read_from_pointer(@_p.struct(1)) + :fun source_if_set!: @_p.assert_union!(0x0, 1), SaviProto.Artifact.Name.Source.read_from_pointer(@_p.struct_if_set!(1)) + +:struct box SaviProto.Artifact.Name.Package + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("key", @key_if_set!) + try trace.property("path", @path_if_set!) + ) + + :fun key: @_p.text(0) + :fun key_if_set!: @_p.text_if_set!(0) + + :fun path: @_p.text(1) + :fun path_if_set!: @_p.text_if_set!(1) + +:struct box SaviProto.Artifact.Name.Source + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 3 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("package", @package_if_set!) + try trace.property("key", @key_if_set!) + try trace.property("path", @path_if_set!) + ) + + :fun package: SaviProto.Artifact.Name.Package.read_from_pointer(@_p.struct(0)) + :fun package_if_set!: SaviProto.Artifact.Name.Package.read_from_pointer(@_p.struct_if_set!(0)) + + :fun key: @_p.text(1) + :fun key_if_set!: @_p.text_if_set!(1) + + :fun path: @_p.text(2) + :fun path_if_set!: @_p.text_if_set!(2) + +:struct box SaviProto.Artifact.Invoke + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 1 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("id", @id_if_set!) + try trace.property("inputs", @inputs_if_set!) + ) + + :fun id: @_p.u64(0x0) + :fun id_if_set!: @_p.u64_if_set!(0x0) + + :fun inputs: CapnProto.List(SaviProto.Artifact).read_from_pointer(@_p.list(0)) + :fun inputs_if_set!: CapnProto.List(SaviProto.Artifact).read_from_pointer(@_p.list_if_set!(0)) + +:struct box SaviProto.Artifact.Invoke.Result + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("id", @id_if_set!) + try trace.property("outputs", @outputs_if_set!) + try trace.property("errors", @errors_if_set!) + ) + + :fun id: @_p.u64(0x0) + :fun id_if_set!: @_p.u64_if_set!(0x0) + + :fun outputs: CapnProto.List(SaviProto.Artifact).read_from_pointer(@_p.list(0)) + :fun outputs_if_set!: CapnProto.List(SaviProto.Artifact).read_from_pointer(@_p.list_if_set!(0)) + + :fun errors: CapnProto.List(SaviProto.Source.Error).read_from_pointer(@_p.list(1)) + :fun errors_if_set!: CapnProto.List(SaviProto.Source.Error).read_from_pointer(@_p.list_if_set!(1)) + +:struct SaviProto.Artifact.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 1 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact) None + try (other_name = other.name_if_set!, @name.copy_data_from(other_name)) + try @_p.set_u64(0x0, other.hash_if_set!, 0) + + :fun ref name: SaviProto.Artifact.Name.Builder.from_pointer(@_p.struct(0, 1, 2)) + :fun ref name_if_set!: SaviProto.Artifact.Name.Builder.from_pointer(@_p.struct_if_set!(0, 1, 2)) + :fun ref set_name_to_point_to_existing(existing SaviProto.Artifact.Name.Builder): SaviProto.Artifact.Name.Builder.from_pointer(@_p.set_struct_to_point_to_existing(0, existing._p)) + + :fun hash: @_p.u64(0x0) + :fun hash_if_set!: @_p.u64_if_set!(0x0) + :fun ref "hash="(new_value): @_p.set_u64(0x0, new_value, 0) + +:struct SaviProto.Artifact.Name.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.Name.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact.Name) None + try @_p.set_text(0, "\(other.kind_if_set!)", "") + try (other_package = other.package!, @init_package.copy_data_from(other_package)) + try (other_source = other.source!, @init_source.copy_data_from(other_source)) + + :fun ref kind: @_p.text(0) + :fun ref kind_if_set!: @_p.text_if_set!(0) + :fun ref "kind="(new_value): @_p.set_text(0, new_value, "") + + :fun is_package: @_p.check_union(0x0, 0) + :fun ref package!: @_p.assert_union!(0x0, 0), SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.struct(1, 0, 2)) + :fun ref package_if_set!: @_p.assert_union!(0x0, 0), SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.struct_if_set!(1, 0, 2)) + :fun ref init_package + @_p.clear_pointer(1) // package + @_p.mark_union(0x0, 0) + SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.struct(1, 0, 2)) + + :fun is_source: @_p.check_union(0x0, 1) + :fun ref source!: @_p.assert_union!(0x0, 1), SaviProto.Artifact.Name.Source.Builder.from_pointer(@_p.struct(1, 0, 3)) + :fun ref source_if_set!: @_p.assert_union!(0x0, 1), SaviProto.Artifact.Name.Source.Builder.from_pointer(@_p.struct_if_set!(1, 0, 3)) + :fun ref init_source + @_p.clear_pointer(1) // source + @_p.mark_union(0x0, 1) + SaviProto.Artifact.Name.Source.Builder.from_pointer(@_p.struct(1, 0, 3)) + +:struct SaviProto.Artifact.Name.Package.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.Name.Package.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact.Name.Package) None + try @_p.set_text(0, "\(other.key_if_set!)", "") + try @_p.set_text(1, "\(other.path_if_set!)", "") + + :fun ref key: @_p.text(0) + :fun ref key_if_set!: @_p.text_if_set!(0) + :fun ref "key="(new_value): @_p.set_text(0, new_value, "") + + :fun ref path: @_p.text(1) + :fun ref path_if_set!: @_p.text_if_set!(1) + :fun ref "path="(new_value): @_p.set_text(1, new_value, "") + +:struct SaviProto.Artifact.Name.Source.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.Name.Source.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 3 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact.Name.Source) None + try (other_package = other.package_if_set!, @package.copy_data_from(other_package)) + try @_p.set_text(1, "\(other.key_if_set!)", "") + try @_p.set_text(2, "\(other.path_if_set!)", "") + + :fun ref package: SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.struct(0, 0, 2)) + :fun ref package_if_set!: SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.struct_if_set!(0, 0, 2)) + :fun ref set_package_to_point_to_existing(existing SaviProto.Artifact.Name.Package.Builder): SaviProto.Artifact.Name.Package.Builder.from_pointer(@_p.set_struct_to_point_to_existing(0, existing._p)) + + :fun ref key: @_p.text(1) + :fun ref key_if_set!: @_p.text_if_set!(1) + :fun ref "key="(new_value): @_p.set_text(1, new_value, "") + + :fun ref path: @_p.text(2) + :fun ref path_if_set!: @_p.text_if_set!(2) + :fun ref "path="(new_value): @_p.set_text(2, new_value, "") + +:struct SaviProto.Artifact.Invoke.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.Invoke.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 1 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact.Invoke) None + try @_p.set_u64(0x0, other.id_if_set!, 0) + try @init_inputs_and_copy_data_from(other.inputs_if_set!) + + :fun id: @_p.u64(0x0) + :fun id_if_set!: @_p.u64_if_set!(0x0) + :fun ref "id="(new_value): @_p.set_u64(0x0, new_value, 0) + + :fun ref inputs: CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.list(0)) + :fun ref inputs_if_set!: CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.list_if_set!(0)) + :fun ref init_inputs(new_count) + CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.init_list(0, 1, 1, new_count)) + :fun ref init_inputs_and_copy_data_from(existing CapnProto.List(SaviProto.Artifact)) + list = CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.init_list(0, 1, 1, existing.size)) + existing.each_with_index -> (existing_item, index | + new_item = try (list[index]! | next) + new_item.copy_data_from(existing_item) + ) + list + :fun ref trim_inputs(new_start, new_finish) + CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.trim_list(0, 1, 1, new_start, new_finish)) + +:struct SaviProto.Artifact.Invoke.Result.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Artifact.Invoke.Result.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 1 + :const capn_proto_pointer_count U16: 2 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Artifact.Invoke.Result) None + try @_p.set_u64(0x0, other.id_if_set!, 0) + try @init_outputs_and_copy_data_from(other.outputs_if_set!) + try @init_errors_and_copy_data_from(other.errors_if_set!) + + :fun id: @_p.u64(0x0) + :fun id_if_set!: @_p.u64_if_set!(0x0) + :fun ref "id="(new_value): @_p.set_u64(0x0, new_value, 0) + + :fun ref outputs: CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.list(0)) + :fun ref outputs_if_set!: CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.list_if_set!(0)) + :fun ref init_outputs(new_count) + CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.init_list(0, 1, 1, new_count)) + :fun ref init_outputs_and_copy_data_from(existing CapnProto.List(SaviProto.Artifact)) + list = CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.init_list(0, 1, 1, existing.size)) + existing.each_with_index -> (existing_item, index | + new_item = try (list[index]! | next) + new_item.copy_data_from(existing_item) + ) + list + :fun ref trim_outputs(new_start, new_finish) + CapnProto.List.Builder(SaviProto.Artifact.Builder).from_pointer(@_p.trim_list(0, 1, 1, new_start, new_finish)) + + :fun ref errors: CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.list(1)) + :fun ref errors_if_set!: CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.list_if_set!(1)) + :fun ref init_errors(new_count) + CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.init_list(1, 0, 3, new_count)) + :fun ref init_errors_and_copy_data_from(existing CapnProto.List(SaviProto.Source.Error)) + list = CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.init_list(1, 0, 3, existing.size)) + existing.each_with_index -> (existing_item, index | + new_item = try (list[index]! | next) + new_item.copy_data_from(existing_item) + ) + list + :fun ref trim_errors(new_start, new_finish) + CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.trim_list(1, 0, 3, new_start, new_finish)) diff --git a/self-hosted/src/SaviProto/SaviProto.Source.capnp b/self-hosted/src/SaviProto/SaviProto.Source.capnp index 369f2c3a..0895f329 100644 --- a/self-hosted/src/SaviProto/SaviProto.Source.capnp +++ b/self-hosted/src/SaviProto/SaviProto.Source.capnp @@ -21,4 +21,10 @@ struct Source { absoluteManifestDirectoryPath @0 :Text; name @1 :Text; } + + struct Error { + position @0 :Position; + message @1 :Text; + extraInfo @2 :List(Source.Error); + } } diff --git a/self-hosted/src/SaviProto/SaviProto.Source.capnp.cr b/self-hosted/src/SaviProto/SaviProto.Source.capnp.cr new file mode 100644 index 00000000..f336d76f --- /dev/null +++ b/self-hosted/src/SaviProto/SaviProto.Source.capnp.cr @@ -0,0 +1,104 @@ + ### + # NOTE: This file was auto-generated from a Cap'n Proto file" + # using the `capnp` compiler with the `--output=cr` option." + + +struct SaviProto::Source + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 1_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def absolute_file_path + @p.text(0) + end + + def content_for_non_file + @p.text(1) + end + + def content_hash64 + @p.u64(0x0) + end + + def package + SaviProto::Source::Package.read_from_pointer(@p.struct(2)) + end +end + +struct SaviProto::Source::Position + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 2_u16 + CAPN_PROTO_POINTER_COUNT = 1_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def source + SaviProto::Source.read_from_pointer(@p.struct(0)) + end + + def offset + @p.u32(0x0) + end + + def size + @p.u32(0x4) + end + + def row + @p.u32(0x8) + end + + def column + @p.u32(0xc) + end +end + +struct SaviProto::Source::Package + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 2_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def absolute_manifest_directory_path + @p.text(0) + end + + def name + @p.text(1) + end +end + +struct SaviProto::Source::Error + def initialize(@p : CapnProto::Pointer::Struct) + end + private def self.new; end + def self.read_from_pointer(p); obj = allocate; obj.initialize(p); obj; end + + CAPN_PROTO_DATA_WORD_COUNT = 0_u16 + CAPN_PROTO_POINTER_COUNT = 3_u16 + def capn_proto_address : UInt64; @p.capn_proto_address; end + + def position + SaviProto::Source::Position.read_from_pointer(@p.struct(0)) + end + + def message + @p.text(1) + end + + def extra_info + CapnProto::List(SaviProto::Source::Error).read_from_pointer(@p.list(2)) + end +end \ No newline at end of file diff --git a/self-hosted/src/SaviProto/SaviProto.Source.capnp.savi b/self-hosted/src/SaviProto/SaviProto.Source.capnp.savi index 8ab8340e..83a01a38 100644 --- a/self-hosted/src/SaviProto/SaviProto.Source.capnp.savi +++ b/self-hosted/src/SaviProto/SaviProto.Source.capnp.savi @@ -88,6 +88,32 @@ :fun name: @_p.text(1) :fun name_if_set!: @_p.text_if_set!(1) +:struct box SaviProto.Source.Error + :let _p CapnProto.Pointer.Struct + :new box read_from_pointer(@_p) + :new val read_val_from_pointer(p CapnProto.Pointer.Struct'val): @_p = p + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 3 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + trace.object(@_p.absolute_address) -> ( + try trace.property("position", @position_if_set!) + try trace.property("message", @message_if_set!) + try trace.property("extra_info", @extra_info_if_set!) + ) + + :fun position: SaviProto.Source.Position.read_from_pointer(@_p.struct(0)) + :fun position_if_set!: SaviProto.Source.Position.read_from_pointer(@_p.struct_if_set!(0)) + + :fun message: @_p.text(1) + :fun message_if_set!: @_p.text_if_set!(1) + + :fun extra_info: CapnProto.List(SaviProto.Source.Error).read_from_pointer(@_p.list(2)) + :fun extra_info_if_set!: CapnProto.List(SaviProto.Source.Error).read_from_pointer(@_p.list_if_set!(2)) + :struct SaviProto.Source.Builder :let _p CapnProto.Pointer.Struct.Builder :new from_pointer(@_p) @@ -187,3 +213,43 @@ :fun ref name: @_p.text(1) :fun ref name_if_set!: @_p.text_if_set!(1) :fun ref "name="(new_value): @_p.set_text(1, new_value, "") + +:struct SaviProto.Source.Error.Builder + :let _p CapnProto.Pointer.Struct.Builder + :new from_pointer(@_p) + :fun as_reader: SaviProto.Source.Error.read_from_pointer(@_p.as_reader) + + :const capn_proto_data_word_count U16: 0 + :const capn_proto_pointer_count U16: 3 + :fun capn_proto_address U64: @_p.capn_proto_address + + :is TraceData + :fun trace_data(trace TraceData.Observer) + @as_reader.trace_data(trace) + + :fun ref copy_data_from(other SaviProto.Source.Error) None + try (other_position = other.position_if_set!, @position.copy_data_from(other_position)) + try @_p.set_text(1, "\(other.message_if_set!)", "") + try @init_extra_info_and_copy_data_from(other.extra_info_if_set!) + + :fun ref position: SaviProto.Source.Position.Builder.from_pointer(@_p.struct(0, 2, 1)) + :fun ref position_if_set!: SaviProto.Source.Position.Builder.from_pointer(@_p.struct_if_set!(0, 2, 1)) + :fun ref set_position_to_point_to_existing(existing SaviProto.Source.Position.Builder): SaviProto.Source.Position.Builder.from_pointer(@_p.set_struct_to_point_to_existing(0, existing._p)) + + :fun ref message: @_p.text(1) + :fun ref message_if_set!: @_p.text_if_set!(1) + :fun ref "message="(new_value): @_p.set_text(1, new_value, "") + + :fun ref extra_info: CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.list(2)) + :fun ref extra_info_if_set!: CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.list_if_set!(2)) + :fun ref init_extra_info(new_count) + CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.init_list(2, 0, 3, new_count)) + :fun ref init_extra_info_and_copy_data_from(existing CapnProto.List(SaviProto.Source.Error)) + list = CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.init_list(2, 0, 3, existing.size)) + existing.each_with_index -> (existing_item, index | + new_item = try (list[index]! | next) + new_item.copy_data_from(existing_item) + ) + list + :fun ref trim_extra_info(new_start, new_finish) + CapnProto.List.Builder(SaviProto.Source.Error.Builder).from_pointer(@_p.trim_list(2, 0, 3, new_start, new_finish)) diff --git a/self-hosted/src/SaviWorker/SaviWorker.TCP.savi b/self-hosted/src/SaviWorker/SaviWorker.TCP.savi new file mode 100644 index 00000000..a0f5dd5e --- /dev/null +++ b/self-hosted/src/SaviWorker/SaviWorker.TCP.savi @@ -0,0 +1,180 @@ +:trait tag SaviWorker.TCP.Connection.Registry + :be register(kind String, worker SaviWorker.TCP.Connection) + :be deregister(kind String, worker SaviWorker.TCP.Connection) + +:actor SaviWorker.TCP.Listener + :is IO.Actor(IO.Action) + + :const default_port I64: 43434 // TODO: register with IANA + + :let registry SaviWorker.TCP.Connection.Registry + :let log _Log + :let io TCP.Listen.Engine + :let workers: Map(String, Array(SaviWorker.TCP.Connection)).new + + :new (@registry, @log, ticket) + @io = TCP.Listen.Engine.new(@, --ticket) + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Opened | + @log.info -> ( + port = try @io.listen_port_number! + "Listening on port: \(port)" + ) + | IO.Action.OpenFailed | + @log.error -> ( + "Failed to listen: \(@io.listen_error)" + ) + | IO.Action.Read | + @io.pending_connections -> (ticket | + SaviWorker.TCP.Connection.new(@registry, @log, --ticket) + ) + | IO.Action.Closed | + @log.info -> ("Stopped listening") + ) + @ + +:actor SaviWorker.TCP.Connection + :is IO.Actor(IO.Action) + + :let registry SaviWorker.TCP.Connection.Registry + :let log _Log + :let io TCP.Engine + :var _ping_number U64: 0 + :var _pong_number U64: 0 + :const ping_miss_max U64: 3 + + :new (@registry, @log, ticket) + @io = TCP.Engine.accept(@, --ticket) + @log.info -> ("Accepted connection from worker") + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Read | + try ( + while True ( + @handle_message(@io.read_stream.extract_frame_u32_be!) + ) + ) + | IO.Action.Closed | + @log.info -> ("Closed connection from worker") + ) + @ + + :fun ref send_message(message Bytes) + @io.write_stream.push_native_u32(message.size.u32.native_to_be) + @io.write_stream << message + try @io.flush! // TODO: do we need to handle failure here in some way? + + :var kind: "" + :fun ref handle_message(message Bytes) + case ( + | @kind.is_empty | + @kind = String.from_bytes(message) + @registry.register(@kind, @) + | message == b"PONG" | + @_pong_number = @_ping_number + | + segments = CapnProto.Segments.new + segment = CapnProto.Segment.new_from_bytes(segments, message.clone) // TODO: no clone + + try ( + m = CapnProto.Message(SaviProto.Artifact.Invoke.Result) + .from_val_segments!(segments.take_val_segments) + @log.debug -> ("Received: \(Inspect[m.root])") + | + @log.error -> ("Failed to parse invoke result") + ) + ) + + :be invoke(message CapnProto.Message(SaviProto.Artifact.Invoke)'val) + id = message.root.id + @log.info -> ("Invoking with id: \(id)") + + try ( + @send_message(message.val_buffers.first!) // TODO: not just the first segment + | + @log.error -> ("Failed to get invoke message buffer") + ) + + :be ping(number U64) + @send_message(b"PING") + + @_ping_number = number + if @_pong_number.is_zero ( + @_pong_number = @_ping_number + return + ) + + if @_ping_number - @_pong_number > @ping_miss_max ( + @log.warn -> ("Ping timeout") + @registry.deregister(@kind, @) + ) + + :be close + @io.close + @log.info -> ("Closed worker connection") + +:actor SaviWorker.TCP + :is IO.Actor(IO.Action) + :is SaviWorker.ResultSink + + :let worker SaviWorker + :let log _Log + :let io TCP.Engine + + :new (@worker, @log, ticket) + @worker.set_result_sink(@) + @io = TCP.Engine.new(@, --ticket) + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Opened | + @log.info -> ("Connecting as worker with kind: \(@worker.kind)") + @send_message(@worker.kind.as_bytes) + | IO.Action.OpenFailed | + @log.error -> ("Failed to connect") + | IO.Action.Read | + try ( + while True ( + @handle_message(@io.read_stream.extract_frame_u32_be!) + ) + ) + | IO.Action.Closed | + @log.info -> ("Disconnected") + ) + @ + + :fun ref send_message(message Bytes) + @io.write_stream.push_native_u32(message.size.u32.native_to_be) + @io.write_stream << message + try @io.flush! // TODO: do we need to handle failure here in some way? + + :fun ref handle_message(bytes Bytes'iso) + case ( + | bytes == b"PING" | + @send_message(b"PONG") + | + invoke = try ( + segments = CapnProto.Segments.new + segment = CapnProto.Segment.new_from_bytes(segments, --bytes) + @worker.work( + CapnProto.Message(SaviProto.Artifact.Invoke) + .from_val_segments!(segments.take_val_segments) + ) + | + @log.error -> ("Failed to parse invoke message") + return + ) + ) + + :be work_result(message CapnProto.Message(SaviProto.Artifact.Invoke.Result)'val) + try ( + @log.debug -> ("Sending: \(Inspect[message.root])") + @send_message(message.val_buffers.first!) // TODO: not just the first segment + @log.info -> ("Finished processing invoke") + | + @log.error -> ("Failed to get result message buffer") + return + ) diff --git a/self-hosted/src/SaviWorker/SaviWorker.savi b/self-hosted/src/SaviWorker/SaviWorker.savi new file mode 100644 index 00000000..65ee7323 --- /dev/null +++ b/self-hosted/src/SaviWorker/SaviWorker.savi @@ -0,0 +1,7 @@ +:trait tag SaviWorker + :fun non kind String + :be set_result_sink(sink SaviWorker.ResultSink) + :be work(invoke CapnProto.Message(SaviProto.Artifact.Invoke)'val) + +:trait tag SaviWorker.ResultSink + :be work_result(result CapnProto.Message(SaviProto.Artifact.Invoke.Result)'val) \ No newline at end of file diff --git a/self-hosted/src/SaviWorker/_Log.savi b/self-hosted/src/SaviWorker/_Log.savi new file mode 100644 index 00000000..02040d4c --- /dev/null +++ b/self-hosted/src/SaviWorker/_Log.savi @@ -0,0 +1,3 @@ +:alias _Log: Logger( + Logger.Formatter.StringWithLevelAndTimestamp +) diff --git a/self-hosted/src/savi-lang-broker/Main.savi b/self-hosted/src/savi-lang-broker/Main.savi new file mode 100644 index 00000000..ce9fa5e3 --- /dev/null +++ b/self-hosted/src/savi-lang-broker/Main.savi @@ -0,0 +1,97 @@ +:class _CLI + :var port: SaviWorker.TCP.Listener.default_port + :var trigger_port: _Trigger.TCP.Listener.default_port + + :is CLI.Option.Data + :fun non define_cli_options(defs CLI.Option.Defs) None + defs.i64("port", 'p') + defs.i64("trigger-port", 'P') + + :fun ref trace_data_mutable(trace TraceData.Mutator) None + trace.object(identity_digest_of @) -> (key | + case key == ( + | "port" | trace.replace_i64(@port) -> (v | @port = v) + | "trigger-port" | trace.replace_i64(@trigger_port) -> (v | @trigger_port = v) + ) + ) + +:actor Main + :let env Env + :new (@env) + cli = try ( + CLI.Parser(_CLI).parse!(_CLI.new, @env.args) + | error | + @env.err.print(error.message) + @env.exit_code = 1 + return + ) + log = _Log.new(@env.err, Logger.Level.Debug) + tcp = TCP.auth(@env.root) + + broker = SaviBroker.new(log) + SaviWorker.TCP.Listener.new( + broker, log, tcp.listen.on("0.0.0.0", "\(cli.options.port)") + ) + _Trigger.TCP.Listener.new( + broker, log, tcp.listen.on("0.0.0.0", "\(cli.options.trigger_port)") + ) + + +:actor _Trigger.TCP.Listener + :is IO.Actor(IO.Action) + + :const default_port I64: 23232 // TODO: register with IANA + + :let broker SaviBroker + :let log _Log + :let io TCP.Listen.Engine + + :new (@broker, @log, ticket) + @io = TCP.Listen.Engine.new(@, --ticket) + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Opened | + @log.info -> ( + port = try @io.listen_port_number! + "Listening for triggers on port: \(port)" + ) + | IO.Action.OpenFailed | + @log.error -> ( + "Failed to listen for triggers: \(@io.listen_error)" + ) + | IO.Action.Read | + @io.pending_connections -> (ticket | + _Trigger.TCP.Connection.new(@broker, @log, --ticket) + ) + | IO.Action.Closed | + @log.info -> ("Stopped listening for triggers") + ) + @ + +:actor _Trigger.TCP.Connection + :is IO.Actor(IO.Action) + + :let broker SaviBroker + :let log _Log + :let io TCP.Engine + + :new (@broker, @log, ticket) + @io = TCP.Engine.accept(@, --ticket) + @log.info -> ("Accepted connection from trigger client") + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Read | + try ( + while True ( + @handle_message(@io.read_stream.extract_line!) + ) + ) + | IO.Action.Closed | + @log.info -> ("Closed connection from worker") + ) + @ + + :fun ref handle_message(message Bytes) + @broker.trigger_manifest_parse(message.as_string) diff --git a/self-hosted/src/savi-lang-broker/SaviBroker.savi b/self-hosted/src/savi-lang-broker/SaviBroker.savi new file mode 100644 index 00000000..4a208284 --- /dev/null +++ b/self-hosted/src/savi-lang-broker/SaviBroker.savi @@ -0,0 +1,99 @@ +:actor SaviBroker + :is SaviWorker.TCP.Connection.Registry + + :const ping_interval_seconds U64: 1 + :let log _Log + :let _ping_timer _PingTimer + :let _workers: Map(String, SaviBroker.WorkerPool).new + :var _next_id U64: 0 + + :new (@log) + @_ping_timer = _PingTimer.new(@, @ping_interval_seconds) + + :be register(kind String, conn SaviWorker.TCP.Connection) + pool = @_workers.get_or_insert(kind) -> (SaviBroker.WorkerPool.new) + pool.register(kind, conn) + @log.info -> ("Registered a worker of kind: \(kind)") + + :be deregister(kind String, conn SaviWorker.TCP.Connection) + try ( + pool = @_workers[kind]! + pool.deregister(kind, conn) + @log.info -> ("Deregistered a worker of kind: \(kind)") + ) + + :be ping(number U64) + @_workers.each_value -> (pool | + pool.workers.each -> (worker | + worker.conn.ping(number) + ) + ) + + :be invoke(kind String, message CapnProto.Message(SaviProto.Artifact.Invoke)'val) + pool = @_workers.get_or_insert(kind) -> (SaviBroker.WorkerPool.new) + pool.invoke(message) + + :be trigger_manifest_parse(path String) + message = CapnProto.Message.Builder(SaviProto.Artifact.Invoke.Builder).new(0x4000) + message.root.id = (@_next_id += 1) + + inputs = message.root.init_inputs(1) + try ( + input = inputs[0]! + input.name.kind = "trigger-parse" + + source = input.name.init_source + source.key = "manifest" + source.path = path + ) + + try ( + @invoke("parse" + CapnProto.Message(SaviProto.Artifact.Invoke).from_val_segments!( + message.take_val_segments + ) + ) + | + @log.error -> ("Failed to convert invoke to val message") + ) + +:class SaviBroker.WorkerPool + :let workers Array(SaviBroker.WorkerEntry)'ref: [] + :let queued Array(CapnProto.Message(SaviProto.Artifact.Invoke)'val)'ref: [] + :var _round_robin_index USize: 0 + + :fun ref register(kind String, conn SaviWorker.TCP.Connection) + @workers << SaviBroker.WorkerEntry.new(kind, conn) + @_drain_queue + + :fun ref deregister(kind String, conn SaviWorker.TCP.Connection) + try @workers.delete_at!(@workers.find_index! -> (w | w.conn === conn)) + // TODO: pull out any pending messages and requeue them + conn.close + + :fun ref invoke(message CapnProto.Message(SaviProto.Artifact.Invoke)'val) None + if @workers.is_empty ( + @queued << message + return + ) + + if @_round_robin_index >= @workers.size ( + @_round_robin_index = 0 + ) + try @workers[@_round_robin_index]!.invoke(message) + @_round_robin_index += 1 + + :fun ref _drain_queue None + return if @queued.is_empty || @workers.is_empty + @queued.each -> (message | @invoke(message)) + @queued.clear + +:struct SaviBroker.WorkerEntry + :let kind String + :let conn SaviWorker.TCP.Connection + :let pending_invokes: Map(U64, CapnProto.Message(SaviProto.Artifact.Invoke)'val).new + :new (@kind, @conn) + + :fun ref invoke(message CapnProto.Message(SaviProto.Artifact.Invoke)'val) + @pending_invokes[message.root.id] = message + @conn.invoke(message) diff --git a/self-hosted/src/savi-lang-broker/_PingTimer.savi b/self-hosted/src/savi-lang-broker/_PingTimer.savi new file mode 100644 index 00000000..8389222b --- /dev/null +++ b/self-hosted/src/savi-lang-broker/_PingTimer.savi @@ -0,0 +1,17 @@ + +:trait tag _PingTimerTarget + :be ping(number U64) + +:actor _PingTimer + :is Timer.Actor + :let timer Timer.Engine + :let target _PingTimerTarget + :var number U64: 0 + + :new (@target, interval_seconds) + interval = Time.Duration.seconds(interval_seconds) + @timer = Timer.Engine.new(@, interval) + + :fun ref timer_react @ + @target.ping(@number += 1) + @ diff --git a/self-hosted/src/savi-lang-parse/Main.TestMode.savi b/self-hosted/src/savi-lang-parse/Main.TestMode.savi new file mode 100644 index 00000000..610ba9f6 --- /dev/null +++ b/self-hosted/src/savi-lang-parse/Main.TestMode.savi @@ -0,0 +1,52 @@ +:actor Main.TestMode + :is StdIn.Actor + :let env Env + :let log _Log + :let io StdIn.Engine + :let _reader: CapnProto.Segments.Reader.new + :new (@env, @log) + @io = StdIn.Engine.new(@) + StdIn.Ticket.get(@env.root.ticket_issuer, @) + + :fun ref io_react(action IO.Action) + case action == ( + | IO.Action.Closed | + @handle_code(@io.read_stream.extract_all.as_string) + ) + @ + + :fun handle_code(code String) + nanos = Time.Measure.nanoseconds -> ( + printer = PEG.Parser(_Token, String).new(_Grammar.new, _TreePrinter.new) + + builder = _TreeBuilder.new + builder.code = code + parser = PEG.Parser( + _Token + CapnProto.Message.Builder(SaviProto.AST.Document.Builder) + ).new(_Grammar.new, builder) + + try ( + Inspect.out("Parsing code: \(code)") + message = parser.parse!(code) + + if builder.error.has_any ( + @env.err.print(printer.parse!(code)) + + builder.error.each -> (info | + @env.err.print("\(info.code) at \(info.at.start)...\(info.at.end)") + ) + | + @env.out.print(Inspect.TraceData.Printer.Deterministic.print(message.root)) + // message.take_val_buffers.each -> (buffer | + // @env.out.write(buffer.as_string) + // ) + ) + | + @env.err.print( + "Parse error at byte \(parser.last_parse_byte_size)" + ) + ) + ) + + @env.err.print("It took \(nanos.f64 / 1e6) milliseconds...") diff --git a/self-hosted/src/savi-lang-parse/Main.savi b/self-hosted/src/savi-lang-parse/Main.savi index 6bc0b45e..044ee872 100644 --- a/self-hosted/src/savi-lang-parse/Main.savi +++ b/self-hosted/src/savi-lang-parse/Main.savi @@ -1,50 +1,199 @@ +:class _CLI + :var test_mode: False + :var port: SaviWorker.TCP.Listener.default_port + + :is CLI.Option.Data + :fun non define_cli_options(defs CLI.Option.Defs) None + defs.bool("test-mode", 't') + defs.i64("port", 'p') + + :fun ref trace_data_mutable(trace TraceData.Mutator) None + trace.object(identity_digest_of @) -> (key | + case key == ( + | "test-mode" | trace.replace_bool(@test_mode) -> (v | @test_mode = v) + | "port" | trace.replace_i64(@port) -> (v | @port = v) + ) + ) + :actor Main - :is StdIn.Actor :let env Env - :let io StdIn.Engine - :let _reader: CapnProto.Segments.Reader.new :new (@env) - @io = StdIn.Engine.new(@) - StdIn.Ticket.get(@env.root.ticket_issuer, @) - - :fun ref io_react(action IO.Action) - case action == ( - | IO.Action.Closed | - @handle_code(@io.read_stream.extract_all.as_string) - ) - @ - - :fun handle_code(code String) - nanos = Time.Measure.nanoseconds -> ( - printer = PEG.Parser(_Token, String).new(_Grammar.new, _TreePrinter.new) - - builder = _TreeBuilder.new - builder.code = code - parser = PEG.Parser( - _Token - CapnProto.Message.Builder(SaviProto.AST.Document.Builder) - ).new(_Grammar.new, builder) - - try ( - message = parser.parse!(code) - - if builder.error.has_any ( - @env.err.print(printer.parse!(code)) - - builder.error.each -> (info | - @env.err.print("\(info.code) at \(info.at.start)...\(info.at.end)") - ) - | - @env.out.print(Inspect.TraceData.Printer.Deterministic.print(message.root)) - // message.take_val_buffers.each -> (buffer | - // @env.out.write(buffer.as_string) - // ) - ) - | - @env.err.print( - "Parse error at byte \(parser.last_parse_byte_size)" - ) + cli = try ( + CLI.Parser(_CLI).parse!(_CLI.new, @env.args) + | error | + @env.err.print(error.message) + @env.exit_code = 1 + return + ) + log = _Log.new(@env.err, Logger.Level.Debug) + + if cli.options.test_mode ( + Main.TestMode.new(@env, log) + | + SaviWorker.TCP.new( + _Worker.new(@env, log) + log + TCP.auth(@env.root).connect.to("0.0.0.0", "\(cli.options.port)") + ) + ) + +:actor _Worker + :is SaviWorker + + :let env Env + :let log _Log + :let file_loader: File.Loader.new + :let file_dumper: File.Dumper.new + :new (@env, @log) + + :fun non kind: "parse" + :var result_sink (SaviWorker.ResultSink | None): None + :let pending_results Array( + CapnProto.Message.Builder(SaviProto.Artifact.Invoke.Result.Builder) + ): [] // TODO: This isn't enough to handle a future scenario where the file loading/dumping isn't causal + + :be set_result_sink(sink): @result_sink = sink + + :fun ref send_result None + result = try (@pending_results.shift! | + @log.error -> ("No pending result in the stack") + return + ) + + final = try ( + CapnProto.Message(SaviProto.Artifact.Invoke.Result).from_val_segments!( + result.take_val_segments ) + | + @log.error -> ("Failed to finalize result") + return + ) + + try ( + @result_sink.not!(None).work_result(final) + | + @log.error -> ("No result sink set") + ) + + :fun ref prep_result None + :yields SaviProto.Artifact.Invoke.Result.Builder + + result = try (@pending_results.first! | + @log.error -> ("No pending result in the stack") + return + ) + + yield result.root + + :fun ref send_error(message String) None + @prep_result -> (result | + result.init_errors(1) + try (result.errors[0]!.message = message) // "cannot fail" + ) + @send_result + + :fun ref send_error_bug(message String) None + @send_error(message) + @log.error -> (message) + + :be work(invoke CapnProto.Message(SaviProto.Artifact.Invoke)'val) + result = CapnProto.Message.Builder(SaviProto.Artifact.Invoke.Result.Builder).new(0x4000) + result.root.id = invoke.root.id + @pending_results << result + + if invoke.root.inputs.size != 1 ( + return @send_error("parse step expects \ + 1 input per invoke, but got \(invoke.root.inputs.size)") + ) + + input = try (invoke.root.inputs[0]! | return) + + if "\(input.name.kind)" != "trigger-parse" ( // TODO: no string interpolation + return @send_error("parse step expects \ + a trigger-parse artifact as input, but got \(input.name.kind)") + ) + + source = try (input.name.source! | + return @send_error("parse step expects a source-level artifact as input") + ) + + try ( + output = result.root.init_outputs(1)[0]! + output.name.copy_data_from(input.name) + output.name.kind = "parse" + | + @send_error_bug("Output artifact list was not initialized") + ) + + path = File.Path.ReadOnly.new(@env.root, "\(source.path)") + @log.debug -> ("Loading file from \(path.string)") + @file_loader.load_from_file(path, @) + + :is File.Loader.ResultActor + :be result_of_load_from_file( + path File.Path.Readable + result File.Result + content Bytes'iso + ) + if result != File.Result.Success ( + return @send_error("Error loading file from \(path.string): \(result)") + ) + @log.debug -> ("Loaded file from \(path.string)") + + code = (--content).as_string + builder = _TreeBuilder.new + builder.code = code + parser = PEG.Parser( + _Token + CapnProto.Message.Builder(SaviProto.AST.Document.Builder) + ).new(_Grammar.new, builder) + + message = try ( + parser.parse!(code) + | + return @send_error("TODO: show grammar error specifics during parsing") + ) + + if builder.error.has_any ( + // TODO: show more error specifics + + // builder.error.each -> (info | + // @env.err.print("\(info.code) at \(info.at.start)...\(info.at.end)") + // ) + return @send_error("TODO: show build error specifics during parsing") + ) + + buffers = try ( + CapnProto.Segments.Writer.add_header_to_val_buffers!( + message.take_val_buffers + ) + | + return @send_error_bug("Output artifact data was too large") + ) + + try ( + output = @pending_results[0]!.root.outputs[0]! + buffers.each -> (b | output.hash = output.hash.bit_xor(b.hash.u64)) // TODO: proper hash64, even on 32-bit platforms + | + return @send_error_bug("Output artifact data was not initialized") + ) + + output_path = SaviProto.Artifact.Path.write_to( + SaviProto.Artifact.Path.write_root(@env.root) + output.as_reader + ) + + @log.debug -> ("Writing parse results to \(output_path.string)") + @file_dumper.dump_to_file(output_path, buffers, @) + + :is File.Dumper.ResultActor + :be result_of_dump_to_file( + path File.Path.Writable + result File.Result + ) + @log.debug -> ("Wrote parse results to \(path.string)") + if result != File.Result.Success ( + return @send_error_bug("Error writing file to \(path.string): \(result)") ) - @env.err.print("It took \(nanos.f64 / 1e6) milliseconds...") + @send_result diff --git a/self-hosted/src/savi-lang-plumber/Main.savi b/self-hosted/src/savi-lang-plumber/Main.savi new file mode 100644 index 00000000..cf31f7bb --- /dev/null +++ b/self-hosted/src/savi-lang-plumber/Main.savi @@ -0,0 +1,80 @@ +:class _CLI + :var to_yaml: "" + + :is CLI.Option.Data + :fun non define_cli_options(defs CLI.Option.Defs) None + defs.string("to-yaml") + + :fun ref trace_data_mutable(trace TraceData.Mutator) None + trace.object(identity_digest_of @) -> (key | + case key == ( + | "to-yaml" | trace.replace_string(@to_yaml) -> (v | @to_yaml = v) + ) + ) + +:actor Main + :let env Env + :new (@env) + cli = try ( + CLI.Parser(_CLI).parse!(_CLI.new, @env.args) + | error | + @env.err.print(error.message) + @env.exit_code = 1 + return + ) + log = _Log.new(@env.err, Logger.Level.Debug) + + case ( + | cli.options.to_yaml != "" | + path = File.Path.ReadOnly.new(@env.root, cli.options.to_yaml) + ToYAML.new(log, path, @env.out) + ) + +:actor ToYAML + :let log _Log + :let path File.Path.Readable + :let out Env.OutStream + :new (@log, @path, @out) + File.Loader.new.load_from_file(@path, @) + + :is File.Loader.ResultActor + :be result_of_load_from_file( + path File.Path.Readable + result File.Result + content Bytes'iso + ) + if result != File.Result.Success ( + @log.error -> ("Failed to load file \(path.string): \(result)") + return + ) + + stream = ByteStream.Pair.new + stream.write << --content + try stream.write.flush! + + reader = CapnProto.Segments.Reader.new + try ( + res = reader.read!(stream.read) + if !res ( + @log.error -> ("Incomplete data at \(path.string)") + return + ) + | + @log.error -> ("Failed to read the data at \(path.string)") + return + ) + + try ( + case @path.ext_name == ( + | "savi-parse" | + message = CapnProto.Message(SaviProto.AST.Document) + .from_segments!(reader.take_segments) + @out.print(Inspect.TraceData.Printer.Deterministic.print(message.root)) + | + @log.error -> ("Unsupported file extension \(path.ext_name)") + return + ) + | + @log.error -> ("Error reading segments from \(path.string)") + ) +