diff --git a/.vscode/launch.json b/.vscode/launch.json index 4d38fab9..83b34811 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "mode": "auto", "program": "${workspaceFolder}/cmd/neva", "cwd": "${workspaceFolder}", - "args": ["run", "examples/swork"] + "args": ["run", "-debug", "e2e/99_bottles_verbose/main"] }, { "name": "LSP", diff --git a/cmd/lsp/server/get_file_view.go b/cmd/lsp/server/get_file_view.go index 408f9f0c..cb260825 100644 --- a/cmd/lsp/server/get_file_view.go +++ b/cmd/lsp/server/get_file_view.go @@ -80,20 +80,20 @@ func (s *Server) GetFileView(glspCtx *glsp.Context, req GetFileViewRequest) (Get func getExtraForFile(file src.File, scope src.Scope) (map[string]map[string]src.Interface, error) { extra := map[string]map[string]src.Interface{} for entityName, entity := range file.Entities { - if entity.Kind != src.FlowEntity { + if entity.Kind != src.ComponentEntity { continue } nodesIfaces := map[string]src.Interface{} - for nodeName, node := range entity.Flow.Nodes { + for nodeName, node := range entity.Component.Nodes { nodeEntity, _, err := scope.Entity(node.EntityRef) if err != nil { return nil, err } var iface src.Interface - if nodeEntity.Kind == src.FlowEntity { - iface = nodeEntity.Flow.Interface + if nodeEntity.Kind == src.ComponentEntity { + iface = nodeEntity.Component.Interface } else if nodeEntity.Kind == src.InterfaceEntity { iface = nodeEntity.Interface } diff --git a/e2e/99_bottles_verbose/main/main.neva b/e2e/99_bottles_verbose/main/main.neva index c40efd4e..2e821de1 100644 --- a/e2e/99_bottles_verbose/main/main.neva +++ b/e2e/99_bottles_verbose/main/main.neva @@ -43,7 +43,7 @@ flow PrintFirstLine(n int) (n int) { match:else -> printf:args[0] $firstLine1 -> printf:tpl - [println:sig, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig lock:data -> :n } @@ -69,6 +69,6 @@ flow PrintSecondLine(n int) (n int) { match:else -> printf:args[0] $secondLine1 -> printf:tpl - [println:sig, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig lock:data -> :n } \ No newline at end of file diff --git a/e2e/99_bottles_with_chain/main/main.neva b/e2e/99_bottles_with_chain/main/main.neva index a2863a7a..c218a877 100644 --- a/e2e/99_bottles_with_chain/main/main.neva +++ b/e2e/99_bottles_with_chain/main/main.neva @@ -36,7 +36,7 @@ flow PrintFirstLine(n int) (n int) { ($firstLine1 -> printf:tpl) ] - [println:sig, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig lock:data -> :n } @@ -60,6 +60,6 @@ flow PrintSecondLine(n int) (n int) { ($secondLine1 -> printf:tpl) ] - [println:sig, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig lock:data -> :n } \ No newline at end of file diff --git a/e2e/cli_new/neva.yml b/e2e/cli_new/neva.yml index eb6c6159..f6f3cf29 100644 --- a/e2e/cli_new/neva.yml +++ b/e2e/cli_new/neva.yml @@ -1 +1 @@ -neva: 0.23.0 \ No newline at end of file +neva: 0.25.0 \ No newline at end of file diff --git a/e2e/compare_ints_lt_equal/main/main.neva b/e2e/compare_ints_lt_equal/main/main.neva index 66556041..d47e1a72 100644 --- a/e2e/compare_ints_lt_equal/main/main.neva +++ b/e2e/compare_ints_lt_equal/main/main.neva @@ -1,8 +1,8 @@ flow Main(start) (stop) { - nodes { Println, Lt} + nodes { Println, Lt } :start -> [ (20 -> lt:compared), (20 -> lt:actual) - ] - lt:res -> println -> :stop + ] + lt:res -> println -> :stop } diff --git a/e2e/compare_strings/main/main.neva b/e2e/compare_strings/main/main.neva index 4ce33af0..6611396e 100644 --- a/e2e/compare_strings/main/main.neva +++ b/e2e/compare_strings/main/main.neva @@ -1,11 +1,10 @@ flow Main(start) (stop) { - nodes { Println, Gt,If} + nodes { Println, Gt, If } :start -> [ ('A' -> gt:compared), ('Z' -> gt:actual) - ] - gt:res -> if - - if:then -> ('Actual is greater' -> println -> :stop) - if:else -> ('Actual is lower' -> println -> :stop) + ] + gt:res -> if + if:then -> ('Actual is greater' -> println -> :stop) + if:else -> ('Actual is lower' -> println -> :stop) } diff --git a/e2e/do_nothin_verbose/e2e_test.go b/e2e/do_nothing_verbose/e2e_test.go similarity index 100% rename from e2e/do_nothin_verbose/e2e_test.go rename to e2e/do_nothing_verbose/e2e_test.go diff --git a/e2e/do_nothin_verbose/main/main.neva b/e2e/do_nothing_verbose/main/main.neva similarity index 95% rename from e2e/do_nothin_verbose/main/main.neva rename to e2e/do_nothing_verbose/main/main.neva index f0fd297f..c38615d4 100644 --- a/e2e/do_nothin_verbose/main/main.neva +++ b/e2e/do_nothing_verbose/main/main.neva @@ -1,3 +1,3 @@ flow Main(start) (stop) { :start -> :stop -} \ No newline at end of file +} diff --git a/e2e/do_nothin_verbose/neva.yml b/e2e/do_nothing_verbose/neva.yml similarity index 100% rename from e2e/do_nothin_verbose/neva.yml rename to e2e/do_nothing_verbose/neva.yml diff --git a/e2e/for_with_range_and_if/main/main.neva b/e2e/for_with_range_and_if/main/main.neva index a970a7b6..9c7a35c0 100644 --- a/e2e/for_with_range_and_if/main/main.neva +++ b/e2e/for_with_range_and_if/main/main.neva @@ -10,7 +10,6 @@ flow Main(start) (stop) { flow Printer(data bool) (sig any) { nodes { If, Println } :data -> if - if:then -> ('true' -> println) - if:else -> ('false' -> println) + [if:then, if:else] -> println println -> :sig } diff --git a/e2e/logic_gate_and/main/main.neva b/e2e/logic_gate_and/main/main.neva index f4fb780a..95c59e22 100644 --- a/e2e/logic_gate_and/main/main.neva +++ b/e2e/logic_gate_and/main/main.neva @@ -1,8 +1,8 @@ flow Main(start) (stop) { nodes { Println, And } :start -> [ - (true -> and:A), - (true -> and:B) + (true -> and:a), + (true -> and:b) ] and -> println -> :stop } \ No newline at end of file diff --git a/e2e/logic_gate_or/main/main.neva b/e2e/logic_gate_or/main/main.neva index d6dd5bec..fac4479b 100644 --- a/e2e/logic_gate_or/main/main.neva +++ b/e2e/logic_gate_or/main/main.neva @@ -1,8 +1,8 @@ flow Main(start) (stop) { nodes { Println, Or } :start -> [ - (false -> or:A), - (false -> or:B) + (false -> or:a), + (false -> or:b) ] or -> println -> :stop } \ No newline at end of file diff --git a/e2e/simple_fan_out/e2e_test.go b/e2e/simple_fan_out/e2e_test.go new file mode 100644 index 00000000..a3d06a16 --- /dev/null +++ b/e2e/simple_fan_out/e2e_test.go @@ -0,0 +1,22 @@ +package test + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test(t *testing.T) { + cmd := exec.Command("neva", "run", "main") + + out, err := cmd.CombinedOutput() + require.NoError(t, err) + require.Equal( + t, + "\n\n", + string(out), + ) + + require.Equal(t, 0, cmd.ProcessState.ExitCode()) +} diff --git a/e2e/simple_fan_out/main/main.neva b/e2e/simple_fan_out/main/main.neva new file mode 100644 index 00000000..16705496 --- /dev/null +++ b/e2e/simple_fan_out/main/main.neva @@ -0,0 +1,7 @@ +import { time } + +flow Main(start) (stop) { + nodes { p1 Println, p2 Println } + :start -> [p1, p2] + p1 -> (p2 -> :stop) +} diff --git a/e2e/simple_fan_out/neva.yml b/e2e/simple_fan_out/neva.yml new file mode 100644 index 00000000..56866f0d --- /dev/null +++ b/e2e/simple_fan_out/neva.yml @@ -0,0 +1 @@ +neva: 0.10.0 \ No newline at end of file diff --git a/examples/99_bottles/main.neva b/examples/99_bottles/main.neva index 00e3e1bd..b00251b9 100644 --- a/examples/99_bottles/main.neva +++ b/examples/99_bottles/main.neva @@ -30,7 +30,7 @@ flow PrintFirstLine(n int) (n int) { ($firstLine1 -> printf:tpl) ] - [println, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig lock -> :n } @@ -55,6 +55,7 @@ flow PrintSecondLine(n int) (n int) { ($secondLine1 -> printf:tpl) ] - [println, printf:args[0]] -> lock:sig + [println:sig, printf:sig] -> lock:sig + lock:data -> :n } \ No newline at end of file diff --git a/examples/add_numbers/main.neva b/examples/add_numbers/main.neva index da06851b..2e1d97c5 100644 --- a/examples/add_numbers/main.neva +++ b/examples/add_numbers/main.neva @@ -1,7 +1,7 @@ flow Main(start) (stop) { nodes { - add ReducePort { Add } Println + add ReducePort { Add } } :start -> [ (1 -> add[0]), diff --git a/examples/fizzbuzz/main.neva b/examples/fizzbuzz/main.neva index bd393c01..b7c7c488 100644 --- a/examples/fizzbuzz/main.neva +++ b/examples/fizzbuzz/main.neva @@ -1,24 +1,14 @@ flow Main(start) (stop) { - nodes { Range, PrintLine, Match } - - :start -> [ - (1 -> range:from), - (101 -> range:to) - ] - range.data -> printLine -> match:data - 100 -> match:case[0] -> :stop + nodes { Range, Map{FizzBuzz}, For{Println} } + :start -> [(1 -> range:from), (101 -> range:to)] + range -> map -> for -> :stop } -flow PrintLine(data int) (data int) { - nodes { mod CaseMod, Println, Lock } - - :data -> [mod:data, lock:data] - - 15 -> mod:case[0] -> ('FizzBuzz' -> println) - 3 -> mod:case[1] -> ('Fizz' -> println) - 5 -> mod:case[2] -> ('Buzz' -> println) - mod:else -> println - - println -> lock:sig - lock:data -> :data -} \ No newline at end of file +flow FizzBuzz(data int) (res any) { + nodes { CaseMod } + :data -> caseMod:data + 15 -> caseMod:case[0] -> ('FizzBuzz' -> :res) + 3 -> caseMod:case[1] -> ('Fizz' -> :res) + 5 -> caseMod:case[2] -> ('Buzz' -> :res) + caseMod:else -> :res +} diff --git a/examples/get_args/main.neva b/examples/get_args/main.neva index d43b06f1..ee5c9339 100644 --- a/examples/get_args/main.neva +++ b/examples/get_args/main.neva @@ -2,5 +2,5 @@ import { os, lists } flow Main(start) (stop) { nodes { os.Args, lists.For{Println}} - :start -> (args -> for -> :stop) + :start -> args -> for -> :stop } \ No newline at end of file diff --git a/examples/hello_world/main.neva b/examples/hello_world/main.neva index 870754a1..67aadfb0 100644 --- a/examples/hello_world/main.neva +++ b/examples/hello_world/main.neva @@ -1,4 +1,4 @@ flow Main(start) (stop) { nodes { Println } :start -> ('Hello, World!' -> println -> :stop) -} \ No newline at end of file +} diff --git a/examples/stream_product/main.neva b/examples/stream_product/main.neva index d4c0473d..fd219b6b 100644 --- a/examples/stream_product/main.neva +++ b/examples/stream_product/main.neva @@ -1,9 +1,11 @@ flow Main(start) (stop) { nodes { - r1 Range, - r2 Range, - Product, - For>{Println>} + r1 Range + r2 Range + Product + For> { + Println> + } } :start -> [ (0 -> [r1:from, r2:from]), diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 72d5cc9d..7ce37224 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -109,12 +109,8 @@ func NewApp( if err != nil { return err } - intr := interpreter.New(bldr, goc, debug) - if err := intr.Interpret( - context.Background(), - workdir, - dirFromArg, - ); err != nil { + intr := interpreter.New(bldr, goc) + if err := intr.Interpret(context.Background(), dirFromArg, debug); err != nil { return err } return nil @@ -148,25 +144,15 @@ func NewApp( } switch target { case "go": - return goc.Compile( - workdir, dirFromArg, workdir, - ) + return goc.Compile(dirFromArg, workdir, debug) case "wasm": - return wasmc.Compile( - workdir, dirFromArg, workdir, - ) + return wasmc.Compile(dirFromArg, workdir, debug) case "json": - return jsonc.Compile( - workdir, dirFromArg, workdir, - ) + return jsonc.Compile(dirFromArg, workdir, debug) case "dot": - return dotc.Compile( - workdir, dirFromArg, workdir, - ) + return dotc.Compile(dirFromArg, workdir, debug) default: - return nativec.Compile( - workdir, dirFromArg, workdir, - ) + return nativec.Compile(dirFromArg, workdir, debug) } }, }, diff --git a/internal/compiler/analyzer/analyzer.go b/internal/compiler/analyzer/analyzer.go index 1b70e57b..67d3c9aa 100644 --- a/internal/compiler/analyzer/analyzer.go +++ b/internal/compiler/analyzer/analyzer.go @@ -223,15 +223,15 @@ func (a Analyzer) analyzeEntity(entity src.Entity, scope src.Scope) (src.Entity, }.Wrap(err) } resolvedEntity.Interface = resolvedInterface - case src.FlowEntity: - analyzedFlow, err := a.analyzeFlow(entity.Flow, scope) + case src.ComponentEntity: + analyzedComponent, err := a.analyzeComponent(entity.Component, scope) if err != nil { return src.Entity{}, compiler.Error{ Location: &scope.Location, - Meta: &entity.Flow.Meta, + Meta: &entity.Component.Meta, }.Wrap(err) } - resolvedEntity.Flow = analyzedFlow + resolvedEntity.Component = analyzedComponent default: return src.Entity{}, &compiler.Error{ Err: fmt.Errorf("%w: %v", ErrUnknownEntityKind, entity.Kind), diff --git a/internal/compiler/analyzer/component.go b/internal/compiler/analyzer/component.go index b8c1751b..8aa0b0cb 100644 --- a/internal/compiler/analyzer/component.go +++ b/internal/compiler/analyzer/component.go @@ -32,21 +32,21 @@ var ( ErrExternNoArgs = errors.New("Flow that use #extern directive must provide at least one argument") ErrBindDirectiveArgs = errors.New("Node with #bind directive must provide exactly one argument") ErrExternOverloadingArg = errors.New("Flow that use #extern with more than one argument must provide arguments in a form of pairs") - ErrExternOverloadingNodeArgs = errors.New("Node instantiated with flow with #extern with > 1 argument, must have exactly one type-argument for overloading") + ErrExternOverloadingNodeArgs = errors.New("Node instantiated from component with '#extern' with > 1 argument, must have exactly one type-argument for overloading") ) // Maybe start here -func (a Analyzer) analyzeFlow( //nolint:funlen - flow src.Flow, +func (a Analyzer) analyzeComponent( //nolint:funlen + component src.Component, scope src.Scope, -) (src.Flow, *compiler.Error) { - runtimeFuncArgs, isRuntimeFunc := flow.Directives[compiler.ExternDirective] +) (src.Component, *compiler.Error) { + runtimeFuncArgs, isRuntimeFunc := component.Directives[compiler.ExternDirective] if isRuntimeFunc && len(runtimeFuncArgs) == 0 { - return src.Flow{}, &compiler.Error{ + return src.Component{}, &compiler.Error{ Err: ErrExternNoArgs, Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, } } @@ -54,17 +54,17 @@ func (a Analyzer) analyzeFlow( //nolint:funlen for _, runtimeFuncArg := range runtimeFuncArgs { parts := strings.Split(runtimeFuncArg, " ") if len(parts) != 2 { - return src.Flow{}, &compiler.Error{ + return src.Component{}, &compiler.Error{ Err: ErrExternOverloadingArg, Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, } } } } resolvedInterface, err := a.analyzeInterface( - flow.Interface, + component.Interface, scope, analyzeInterfaceParams{ allowEmptyInports: isRuntimeFunc, @@ -72,45 +72,45 @@ func (a Analyzer) analyzeFlow( //nolint:funlen }, ) if err != nil { - return src.Flow{}, compiler.Error{ + return src.Component{}, compiler.Error{ Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, }.Wrap(err) } if isRuntimeFunc { - if len(flow.Nodes) != 0 || len(flow.Net) != 0 { - return src.Flow{}, &compiler.Error{ + if len(component.Nodes) != 0 || len(component.Net) != 0 { + return src.Component{}, &compiler.Error{ Err: ErrNormCompWithExtern, Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, } } - return flow, nil + return component, nil } resolvedNodes, nodesIfaces, hasGuard, err := a.analyzeFlowNodes( - flow.Interface, - flow.Nodes, + component.Interface, + component.Nodes, scope, ) if err != nil { - return src.Flow{}, compiler.Error{ + return src.Component{}, compiler.Error{ Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, }.Wrap(err) } - if len(flow.Net) == 0 { - return src.Flow{}, &compiler.Error{ + if len(component.Net) == 0 { + return src.Component{}, &compiler.Error{ Err: ErrNormFlowWithoutNet, Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, } } analyzedNet, err := a.analyzeFlowNetwork( - flow.Net, + component.Net, resolvedInterface, hasGuard, resolvedNodes, @@ -118,13 +118,13 @@ func (a Analyzer) analyzeFlow( //nolint:funlen scope, ) if err != nil { - return src.Flow{}, compiler.Error{ + return src.Component{}, compiler.Error{ Location: &scope.Location, - Meta: &flow.Meta, + Meta: &component.Meta, }.Wrap(err) } - return src.Flow{ + return src.Component{ Interface: resolvedInterface, Nodes: resolvedNodes, Net: analyzedNet, diff --git a/internal/compiler/analyzer/component_nodes.go b/internal/compiler/analyzer/component_nodes.go index e7240e78..5d59f951 100644 --- a/internal/compiler/analyzer/component_nodes.go +++ b/internal/compiler/analyzer/component_nodes.go @@ -80,7 +80,7 @@ func (a Analyzer) analyzeNode( } } - if nodeEntity.Kind != src.FlowEntity && + if nodeEntity.Kind != src.ComponentEntity && nodeEntity.Kind != src.InterfaceEntity { return src.Node{}, foundInterface{}, &compiler.Error{ Err: fmt.Errorf("%w: %v", ErrNodeWrongEntity, nodeEntity.Kind), @@ -258,7 +258,7 @@ func (a Analyzer) getNodeInterface( return entity.Interface, nil } - externArgs, hasExternDirective := entity.Flow.Directives[compiler.ExternDirective] + externArgs, hasExternDirective := entity.Component.Directives[compiler.ExternDirective] if usesBindDirective && !hasExternDirective { return src.Interface{}, &compiler.Error{ @@ -276,9 +276,9 @@ func (a Analyzer) getNodeInterface( } } - iface := entity.Flow.Interface + iface := entity.Component.Interface - _, hasAutoPortsDirective := entity.Flow.Directives[compiler.AutoportsDirective] + _, hasAutoPortsDirective := entity.Component.Directives[compiler.AutoportsDirective] if !hasAutoPortsDirective { return iface, nil } diff --git a/internal/compiler/analyzer/main_component.go b/internal/compiler/analyzer/main_component.go index 51f85139..6f60b11d 100644 --- a/internal/compiler/analyzer/main_component.go +++ b/internal/compiler/analyzer/main_component.go @@ -20,7 +20,7 @@ var ( ErrMainNodeEntityNotFlow = errors.New("Main flow's nodes must only refer to flow entities") ) -func (a Analyzer) analyzeMainFlow(cmp src.Flow, scope src.Scope) *compiler.Error { +func (a Analyzer) analyzeMainComponent(cmp src.Component, scope src.Scope) *compiler.Error { if len(cmp.Interface.TypeParams.Params) != 0 { return &compiler.Error{ Err: ErrMainFlowWithTypeParams, @@ -106,7 +106,7 @@ func (Analyzer) analyzeMainFlowNodes( } } - if nodeEntity.Kind != src.FlowEntity { + if nodeEntity.Kind != src.ComponentEntity { return &compiler.Error{ Err: fmt.Errorf("%w: %v: %v", ErrMainNodeEntityNotFlow, nodeName, node.EntityRef), Location: &loc, diff --git a/internal/compiler/analyzer/main_pkg.go b/internal/compiler/analyzer/main_pkg.go index aa6e96fd..dd4508c0 100644 --- a/internal/compiler/analyzer/main_pkg.go +++ b/internal/compiler/analyzer/main_pkg.go @@ -9,10 +9,10 @@ import ( ) var ( - ErrMainEntityNotFound = errors.New("Main entity is not found") - ErrMainEntityIsNotFlow = errors.New("Main entity is not a flow") - ErrMainEntityExported = errors.New("Main entity cannot be exported") - ErrMainPkgExports = errors.New("Main package must cannot have exported entities") + ErrMainEntityNotFound = errors.New("Main entity is not found") + ErrMainEntityIsNotFlow = errors.New("Main entity is not a component") + ErrMainEntityExported = errors.New("Main entity cannot be exported") + ErrMainPkgExports = errors.New("Main package must cannot have exported entities") ) func (a Analyzer) mainSpecificPkgValidation(mainPkgName string, mod src.Module, scope src.Scope) *compiler.Error { @@ -33,7 +33,7 @@ func (a Analyzer) mainSpecificPkgValidation(mainPkgName string, mod src.Module, location.FileName = filename - if entityMain.Kind != src.FlowEntity { + if entityMain.Kind != src.ComponentEntity { return &compiler.Error{ Err: ErrMainEntityIsNotFlow, Location: location, @@ -44,16 +44,16 @@ func (a Analyzer) mainSpecificPkgValidation(mainPkgName string, mod src.Module, return &compiler.Error{ Err: ErrMainEntityExported, Location: location, - Meta: &entityMain.Flow.Meta, + Meta: &entityMain.Component.Meta, } } scope = scope.WithLocation(*location) - if err := a.analyzeMainFlow(entityMain.Flow, scope); err != nil { + if err := a.analyzeMainComponent(entityMain.Component, scope); err != nil { return compiler.Error{ Location: location, - Meta: &entityMain.Flow.Meta, + Meta: &entityMain.Component.Meta, }.Wrap(err) } diff --git a/internal/compiler/backend/dot/backend.go b/internal/compiler/backend/dot/backend.go index 65ac36d6..6a897d91 100644 --- a/internal/compiler/backend/dot/backend.go +++ b/internal/compiler/backend/dot/backend.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) type Backend struct{} diff --git a/internal/compiler/backend/dot/graphviz.go b/internal/compiler/backend/dot/graphviz.go index 4b2119ec..2990561c 100644 --- a/internal/compiler/backend/dot/graphviz.go +++ b/internal/compiler/backend/dot/graphviz.go @@ -10,7 +10,7 @@ import ( "sync" "text/template" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) //go:embed *.tmpl @@ -32,7 +32,7 @@ func (p Port) FormatName() string { case strings.HasSuffix(p.Path, "/out"): portStr += "/out" } - if p.Idx != 0 { + if p.Idx != nil { portStr = fmt.Sprintf("%s/%d", portStr, p.Idx) } return strconv.Quote(portStr) @@ -40,7 +40,7 @@ func (p Port) FormatName() string { func (p Port) FormatLabel() string { escapePort := html.EscapeString(p.Port) - if p.Idx != 0 { + if p.Idx != nil { return html.EscapeString(fmt.Sprintf("%s[%d]", p.Port, p.Idx)) } return escapePort @@ -57,7 +57,7 @@ func (p Port) Format() string { path = path[:len(path)-4] // Trim /out portStr += "/out" } - if p.Idx != 0 { + if p.Idx != nil { portStr = fmt.Sprint(portStr, "/", p.Idx) } return fmt.Sprintf("%q:%q", path, portStr) diff --git a/internal/compiler/backend/golang/backend.go b/internal/compiler/backend/golang/backend.go index 01d0f720..c64d41af 100644 --- a/internal/compiler/backend/golang/backend.go +++ b/internal/compiler/backend/golang/backend.go @@ -8,7 +8,7 @@ import ( "github.com/nevalang/neva/internal" "github.com/nevalang/neva/internal/compiler" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) type Backend struct{} diff --git a/internal/compiler/backend/golang/funcmap.go b/internal/compiler/backend/golang/funcmap.go index ed1b7a18..630b6e36 100644 --- a/internal/compiler/backend/golang/funcmap.go +++ b/internal/compiler/backend/golang/funcmap.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/nevalang/neva/internal/compiler" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) -func getMsg(msg *ir.Msg) (string, error) { +func getMsg(msg *ir.Message) (string, error) { if msg == nil { return "nil", nil } @@ -20,7 +20,7 @@ func getMsg(msg *ir.Msg) (string, error) { case ir.MsgTypeFloat: return fmt.Sprintf("runtime.NewFloatMsg(%v)", msg.Float), nil case ir.MsgTypeString: - return fmt.Sprintf(`runtime.NewStrMsg("%v")`, msg.Str), nil + return fmt.Sprintf(`runtime.NewStrMsg("%v")`, msg.String), nil case ir.MsgTypeList: s := `runtime.NewListMsg( ` @@ -36,7 +36,7 @@ func getMsg(msg *ir.Msg) (string, error) { case ir.MsgTypeMap: s := `runtime.NewMapMsg(map[string]runtime.Msg{ ` - for k, v := range msg.Map { + for k, v := range msg.Dict { el, err := getMsg(compiler.Pointer(v)) if err != nil { return "", err @@ -51,9 +51,9 @@ func getMsg(msg *ir.Msg) (string, error) { return "", fmt.Errorf("%w: %v", ErrUnknownMsgType, msg.Type) } -func getConnComment(conn *ir.Connection) string { - s := fmtPortAddr(conn.SenderSide) + " -> " - for _, rcvr := range conn.ReceiverSides { +func getConnComment(sender ir.PortAddr, receivers map[ir.PortAddr]struct{}) string { + s := fmtPortAddr(sender) + " -> " + for rcvr := range receivers { s += fmtPortAddr(rcvr) } return "// " + s diff --git a/internal/compiler/backend/golang/native/backend.go b/internal/compiler/backend/golang/native/backend.go index 94081fd4..dad09c9c 100644 --- a/internal/compiler/backend/golang/native/backend.go +++ b/internal/compiler/backend/golang/native/backend.go @@ -6,7 +6,7 @@ import ( "path/filepath" "github.com/nevalang/neva/internal/compiler/backend/golang" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) type Backend struct { diff --git a/internal/compiler/backend/golang/tpl.go b/internal/compiler/backend/golang/tpl.go index 321df689..200489d0 100644 --- a/internal/compiler/backend/golang/tpl.go +++ b/internal/compiler/backend/golang/tpl.go @@ -33,8 +33,8 @@ func main() { {{- end}} }, Connections: []runtime.Connection{ - {{- range .Connections}} - {{ getConnComment . }} + {{- range $sender, $receivers := .Connections}} + {{ getConnComment $sender, $receivers }} { Sender: {{getPortChanName .SenderSide}}, Receivers: []chan runtime.Msg{ diff --git a/internal/compiler/backend/golang/wasm/backend.go b/internal/compiler/backend/golang/wasm/backend.go index a1e6e872..f6e8a52e 100644 --- a/internal/compiler/backend/golang/wasm/backend.go +++ b/internal/compiler/backend/golang/wasm/backend.go @@ -6,7 +6,7 @@ import ( "path/filepath" "github.com/nevalang/neva/internal/compiler/backend/golang" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) type Backend struct { diff --git a/internal/compiler/backend/json/backend.go b/internal/compiler/backend/json/backend.go index 81850744..b7d27c57 100644 --- a/internal/compiler/backend/json/backend.go +++ b/internal/compiler/backend/json/backend.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler/ir" ) type Backend struct{} @@ -22,5 +22,5 @@ func (b Backend) Emit(dst string, prog *ir.Program) error { } defer f.Close() e := json.NewEncoder(f) - return e.Encode(prog) + return e.Encode(prog) //nolint:staticcheck // SA1026 } diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index cd30d52c..eeafbf93 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -4,8 +4,8 @@ import ( "context" "strings" + "github.com/nevalang/neva/internal/compiler/ir" "github.com/nevalang/neva/internal/compiler/sourcecode" - "github.com/nevalang/neva/internal/runtime/ir" ) type Compiler struct { @@ -17,52 +17,36 @@ type Compiler struct { backend Backend } -// Compile compiles given rawBuild to target language -// and uses specified backend to emit files to the destination. -func (c Compiler) Compile( - src string, - mainPkgName string, - dstPath string, -) error { - ir, err := c.CompileToIR(src, mainPkgName) +func (c Compiler) Compile(main string, dst string, debug bool) error { + ir, err := c.CompileToIR(main, debug) if err != nil { return err } - return c.backend.Emit(dstPath, ir) + return c.backend.Emit(dst, ir) } -func (c Compiler) CompileToIR( - wd string, - mainPkgPath string, -) (*ir.Program, *Error) { - rawBuild, entryModRoot, err := c.builder.Build( - context.Background(), - mainPkgPath, - ) +func (c Compiler) CompileToIR(main string, debug bool) (*ir.Program, *Error) { + raw, root, err := c.builder.Build(context.Background(), main) if err != nil { - return nil, Error{ - Location: &sourcecode.Location{ - PkgName: mainPkgPath, - }, - }.Wrap(err) + return nil, Error{Location: &sourcecode.Location{PkgName: main}}.Wrap(err) } - parsedMods, err := c.parser.ParseModules(rawBuild.Modules) + parsedMods, err := c.parser.ParseModules(raw.Modules) if err != nil { return nil, err } parsedBuild := sourcecode.Build{ - EntryModRef: rawBuild.EntryModRef, + EntryModRef: raw.EntryModRef, Modules: parsedMods, } - mainPkgPath = strings.TrimPrefix(mainPkgPath, "./") - mainPkgPath = strings.TrimPrefix(mainPkgPath, entryModRoot+"/") + main = strings.TrimPrefix(main, "./") + main = strings.TrimPrefix(main, root+"/") analyzedBuild, err := c.analyzer.AnalyzeExecutableBuild( parsedBuild, - mainPkgPath, + main, ) if err != nil { return nil, err @@ -73,7 +57,7 @@ func (c Compiler) CompileToIR( return nil, err } - irProg, err := c.irgen.Generate(desugaredBuild, mainPkgPath) + irProg, err := c.irgen.Generate(desugaredBuild, main, !debug) if err != nil { return nil, err } diff --git a/internal/compiler/contract.go b/internal/compiler/contract.go index 5acfe19c..ec93e024 100644 --- a/internal/compiler/contract.go +++ b/internal/compiler/contract.go @@ -3,8 +3,8 @@ package compiler import ( "context" + "github.com/nevalang/neva/internal/compiler/ir" src "github.com/nevalang/neva/internal/compiler/sourcecode" - "github.com/nevalang/neva/internal/runtime/ir" ) const ( @@ -43,7 +43,7 @@ type ( } IRGenerator interface { - Generate(build src.Build, mainPkgName string) (*ir.Program, *Error) + Generate(build src.Build, mainpkg string, optimize bool) (*ir.Program, *Error) } Backend interface { diff --git a/internal/compiler/desugarer/component.go b/internal/compiler/desugarer/component.go index 81ecda6c..dd1cf2ec 100644 --- a/internal/compiler/desugarer/component.go +++ b/internal/compiler/desugarer/component.go @@ -13,44 +13,45 @@ var ErrConstSenderEntityKind = errors.New( "Entity that is used as a const reference in flow's network must be of kind constant", ) -type handleFlowResult struct { - desugaredFlow src.Flow +type handleComponentResult struct { + desugaredFlow src.Component virtualEntities map[string]src.Entity } -func (d Desugarer) handleFlow( - flow src.Flow, +func (d Desugarer) handleComponent( + component src.Component, scope src.Scope, -) (handleFlowResult, *compiler.Error) { - if len(flow.Net) == 0 && len(flow.Nodes) == 0 { - return handleFlowResult{desugaredFlow: flow}, nil +) (handleComponentResult, *compiler.Error) { + if len(component.Net) == 0 && len(component.Nodes) == 0 { + return handleComponentResult{desugaredFlow: component}, nil } virtualEntities := map[string]src.Entity{} desugaredNodes, virtConnsForNodes, err := d.handleNodes( - flow, + component, scope, virtualEntities, ) if err != nil { - return handleFlowResult{}, err + return handleComponentResult{}, err } - netToDesugar := append(virtConnsForNodes, flow.Net...) + netToDesugar := append(virtConnsForNodes, component.Net...) + handleNetResult, err := d.handleNetwork( netToDesugar, desugaredNodes, scope, ) if err != nil { - return handleFlowResult{}, err + return handleComponentResult{}, err } desugaredNetwork := slices.Clone(handleNetResult.desugaredConnections) // add virtual constants created by network handler to virtual entities - for name, constant := range handleNetResult.virtualConstants { + for name, constant := range handleNetResult.constsToInsert { virtualEntities[name] = src.Entity{ Kind: src.ConstEntity, Const: constant, @@ -60,11 +61,11 @@ func (d Desugarer) handleFlow( // merge real nodes with virtual ones created by network handler maps.Copy(desugaredNodes, handleNetResult.virtualNodes) - // create virtual destructor nodes and connections to handle unused outports + // create and connect Del nodes to handle unused outports unusedOutports := d.findUnusedOutports( - flow, + component, scope, - handleNetResult.usedNodePorts, + handleNetResult.nodesPortsUsed, ) if unusedOutports.len() != 0 { unusedOutportsResult := d.handleUnusedOutports(unusedOutports) @@ -72,20 +73,20 @@ func (d Desugarer) handleFlow( desugaredNodes[unusedOutportsResult.voidNodeName] = unusedOutportsResult.voidNode } - return handleFlowResult{ - desugaredFlow: src.Flow{ - Directives: flow.Directives, - Interface: flow.Interface, + return handleComponentResult{ + desugaredFlow: src.Component{ + Directives: component.Directives, + Interface: component.Interface, Nodes: desugaredNodes, Net: desugaredNetwork, - Meta: flow.Meta, + Meta: component.Meta, }, virtualEntities: virtualEntities, }, nil } func (d Desugarer) handleNodes( - flow src.Flow, + flow src.Component, scope src.Scope, virtualEntities map[string]src.Entity, ) (map[string]src.Node, []src.Connection, *compiler.Error) { diff --git a/internal/compiler/desugarer/const_sender.go b/internal/compiler/desugarer/const_sender.go index c8c05649..d0a828ff 100644 --- a/internal/compiler/desugarer/const_sender.go +++ b/internal/compiler/desugarer/const_sender.go @@ -120,6 +120,7 @@ func (d Desugarer) handleConstRefSender( EntityRef: emitterFlowRef, TypeArgs: []ts.Expr{constTypeExpr}, } + emitterNodeOutportAddr := src.PortAddr{ Node: virtualEmitterName, Port: "msg", diff --git a/internal/compiler/desugarer/defer_connections.go b/internal/compiler/desugarer/defer_connections.go index e2a0c1ff..9f1c0013 100644 --- a/internal/compiler/desugarer/defer_connections.go +++ b/internal/compiler/desugarer/defer_connections.go @@ -26,23 +26,22 @@ var virtualBlockerNode = src.Node{ }, } -type handleThenConnectionsResult struct { - connsToInsert []src.Connection - connToReplace src.Connection - constsToInsert map[string]src.Const - nodesToInsert map[string]src.Node - nodesPortsUsed nodePortsMap // (probably?) to generate "Del" instances where needed +type desugarDeferredConnectionsResult struct { + connsToInsert []src.Connection + receiversToInsert []src.ConnectionReceiver + constsToInsert map[string]src.Const + nodesToInsert map[string]src.Node + nodesPortsUsed nodePortsMap // (probably?) to generate "Del" instances where needed } var virtualBlockersCounter atomic.Uint64 -func (d Desugarer) handleDeferredConnections( - origConn src.NormalConnection, +func (d Desugarer) desugarDeferredConnections( + originalConn src.NormalConnection, nodes map[string]src.Node, scope src.Scope, -) (handleThenConnectionsResult, *compiler.Error) { - originalSender := origConn.SenderSide - deferredConnections := origConn.ReceiverSide.DeferredConnections +) (desugarDeferredConnectionsResult, *compiler.Error) { + deferredConnections := originalConn.ReceiverSide.DeferredConnections // recursively desugar every deferred connections handleNetResult, err := d.handleNetwork( @@ -51,15 +50,15 @@ func (d Desugarer) handleDeferredConnections( scope, ) if err != nil { - return handleThenConnectionsResult{}, nil + return desugarDeferredConnectionsResult{}, nil } // we want to return nodes created in recursive calls // as well as the onces created by us in this call - virtualNodes := maps.Clone(handleNetResult.virtualNodes) + nodesToInsert := maps.Clone(handleNetResult.virtualNodes) // we going to replace all desugared deferreded connections with set of our connections - virtualConns := make([]src.Connection, 0, len(handleNetResult.desugaredConnections)) + connsToInsert := make([]src.Connection, 0, len(handleNetResult.desugaredConnections)) // for every deferred connection we must do 4 things // 1) create virtual "blocker" node @@ -78,7 +77,7 @@ func (d Desugarer) handleDeferredConnections( counter := virtualBlockersCounter.Load() virtualBlockersCounter.Store(counter + 1) virtualBlockerName := fmt.Sprintf("__lock__%d", counter) - virtualNodes[virtualBlockerName] = virtualBlockerNode + nodesToInsert[virtualBlockerName] = virtualBlockerNode // 2) create connection from original sender to blocker:sig receiversForOriginalSender = append( @@ -91,7 +90,7 @@ func (d Desugarer) handleDeferredConnections( }, ) - virtualConns = append(virtualConns, + connsToInsert = append(connsToInsert, // 3) create connection from deferred sender to blocker:data src.Connection{ Normal: &src.NormalConnection{ @@ -125,28 +124,11 @@ func (d Desugarer) handleDeferredConnections( ) } - // don't forget to append normal original sender receivers - // there are connections with both deferred connections and normal receivers - receiversForOriginalSender = append( - receiversForOriginalSender, - origConn.ReceiverSide.Receivers..., - ) - - // don't forget to append first connection - connToReplace := src.Connection{ - Normal: &src.NormalConnection{ - SenderSide: originalSender, - ReceiverSide: src.ConnectionReceiverSide{ - Receivers: receiversForOriginalSender, - }, - }, - } - - return handleThenConnectionsResult{ - nodesToInsert: virtualNodes, - connsToInsert: virtualConns, - constsToInsert: handleNetResult.virtualConstants, - nodesPortsUsed: handleNetResult.usedNodePorts, - connToReplace: connToReplace, + return desugarDeferredConnectionsResult{ + nodesToInsert: nodesToInsert, + connsToInsert: connsToInsert, + constsToInsert: handleNetResult.constsToInsert, + nodesPortsUsed: handleNetResult.nodesPortsUsed, + receiversToInsert: receiversForOriginalSender, }, nil } diff --git a/internal/compiler/desugarer/del.go b/internal/compiler/desugarer/del.go index 1d8e7876..cc428a9c 100644 --- a/internal/compiler/desugarer/del.go +++ b/internal/compiler/desugarer/del.go @@ -60,7 +60,7 @@ func (Desugarer) handleUnusedOutports(unusedOutports nodePortsMap) voidResult { } func (Desugarer) findUnusedOutports( - flow src.Flow, + flow src.Component, scope src.Scope, usedNodePorts nodePortsMap, ) nodePortsMap { @@ -71,7 +71,7 @@ func (Desugarer) findUnusedOutports( if err != nil { continue } - if entity.Kind != src.InterfaceEntity && entity.Kind != src.FlowEntity { + if entity.Kind != src.InterfaceEntity && entity.Kind != src.ComponentEntity { continue } @@ -79,7 +79,7 @@ func (Desugarer) findUnusedOutports( if entity.Kind == src.InterfaceEntity { io = entity.Interface.IO } else { - io = entity.Flow.Interface.IO + io = entity.Component.Interface.IO } for outportName := range io.Out { diff --git a/internal/compiler/desugarer/desugarer.go b/internal/compiler/desugarer/desugarer.go index 72164264..73845dbc 100644 --- a/internal/compiler/desugarer/desugarer.go +++ b/internal/compiler/desugarer/desugarer.go @@ -145,14 +145,14 @@ type desugarEntityResult struct { } func (d Desugarer) desugarEntity(entity src.Entity, scope src.Scope) (desugarEntityResult, *compiler.Error) { - if entity.Kind != src.FlowEntity && entity.Kind != src.ConstEntity { + if entity.Kind != src.ComponentEntity && entity.Kind != src.ConstEntity { return desugarEntityResult{entity: entity}, nil } if entity.Kind == src.ConstEntity { desugaredConst, err := d.handleConst(entity.Const) if err != nil { - return desugarEntityResult{}, compiler.Error{Meta: &entity.Flow.Meta}.Wrap(err) + return desugarEntityResult{}, compiler.Error{Meta: &entity.Component.Meta}.Wrap(err) } return desugarEntityResult{ @@ -164,17 +164,17 @@ func (d Desugarer) desugarEntity(entity src.Entity, scope src.Scope) (desugarEnt }, nil } - flowResult, err := d.handleFlow(entity.Flow, scope) + componentResult, err := d.handleComponent(entity.Component, scope) if err != nil { - return desugarEntityResult{}, compiler.Error{Meta: &entity.Flow.Meta}.Wrap(err) + return desugarEntityResult{}, compiler.Error{Meta: &entity.Component.Meta}.Wrap(err) } return desugarEntityResult{ - entitiesToInsert: flowResult.virtualEntities, + entitiesToInsert: componentResult.virtualEntities, entity: src.Entity{ - IsPublic: entity.IsPublic, - Kind: entity.Kind, - Flow: flowResult.desugaredFlow, + IsPublic: entity.IsPublic, + Kind: entity.Kind, + Component: componentResult.desugaredFlow, }, }, nil } diff --git a/internal/compiler/desugarer/network.go b/internal/compiler/desugarer/network.go index 2d061eb5..a4ebfc62 100644 --- a/internal/compiler/desugarer/network.go +++ b/internal/compiler/desugarer/network.go @@ -2,18 +2,20 @@ package desugarer import ( "errors" + "fmt" "maps" - "slices" + "sync/atomic" "github.com/nevalang/neva/internal/compiler" src "github.com/nevalang/neva/internal/compiler/sourcecode" + "github.com/nevalang/neva/internal/compiler/sourcecode/core" ) type handleNetResult struct { desugaredConnections []src.Connection // desugared network - virtualConstants map[string]src.Const // constants that needs to be inserted in to make desugared network work + constsToInsert map[string]src.Const // constants that needs to be inserted in to make desugared network work virtualNodes map[string]src.Node // nodes that needs to be inserted in to make desugared network work - usedNodePorts nodePortsMap // to find unused to create virtual del connections + nodesPortsUsed nodePortsMap // to find unused to create virtual del connections } func (d Desugarer) handleNetwork( @@ -27,7 +29,7 @@ func (d Desugarer) handleNetwork( usedNodePorts := newNodePortsMap() for _, conn := range net { - result, err := d.desugarConn( + result, err := d.desugarConnection( conn, usedNodePorts, scope, @@ -39,35 +41,35 @@ func (d Desugarer) handleNetwork( return handleNetResult{}, err } - desugaredConns = append(desugaredConns, result.connToReplace) - desugaredConns = append(desugaredConns, result.connsToInsert...) + desugaredConns = append(desugaredConns, result.connectionToReplace) + desugaredConns = append(desugaredConns, result.connectionsToInsert...) } return handleNetResult{ desugaredConnections: desugaredConns, - usedNodePorts: usedNodePorts, - virtualConstants: constsToInsert, + nodesPortsUsed: usedNodePorts, + constsToInsert: constsToInsert, virtualNodes: nodesToInsert, }, nil } -type desugarConnResult struct { - connToReplace src.Connection - connsToInsert []src.Connection +type desugarConnectionResult struct { + connectionToReplace src.Connection + connectionsToInsert []src.Connection } -// desugarConn modifies given nodesToInsert, constsToInsert and usedNodePorts +// desugarConnection modifies given nodesToInsert, constsToInsert and usedNodePorts // it also returns connection to replace the original one and other connections // that were generated while desugared the original one. -func (d Desugarer) desugarConn( +func (d Desugarer) desugarConnection( conn src.Connection, usedNodePorts nodePortsMap, scope src.Scope, nodes map[string]src.Node, nodesToInsert map[string]src.Node, constsToInsert map[string]src.Const, -) (desugarConnResult, *compiler.Error) { - // array bypass connection - nothing to desugar, just mark as used and return as-is +) (desugarConnectionResult, *compiler.Error) { + // "array bypass" connection - nothing to desugar, just mark as used and return as-is if conn.ArrayBypass != nil { usedNodePorts.set( conn.ArrayBypass.SenderOutport.Node, @@ -77,25 +79,24 @@ func (d Desugarer) desugarConn( conn.ArrayBypass.ReceiverInport.Node, conn.ArrayBypass.ReceiverInport.Port, ) - return desugarConnResult{ - connToReplace: conn, - }, nil + return desugarConnectionResult{connectionToReplace: conn}, nil } - // normal connection with port address sender + // further we only handle normal connections + + // mark as used and handle unnamed port if needed if conn.Normal.SenderSide.PortAddr != nil { - // if port is unknown, find first and use it instead if conn.Normal.SenderSide.PortAddr.Port == "" { - found, err := getFirstOutPortName(scope, nodes, *conn.Normal.SenderSide.PortAddr) + firstOutportName, err := getFirstOutportName(scope, nodes, *conn.Normal.SenderSide.PortAddr) if err != nil { - return desugarConnResult{}, &compiler.Error{Err: err} + return desugarConnectionResult{}, &compiler.Error{Err: err} } conn = src.Connection{ Normal: &src.NormalConnection{ SenderSide: src.ConnectionSenderSide{ PortAddr: &src.PortAddr{ - Port: found, + Port: firstOutportName, Node: conn.Normal.SenderSide.PortAddr.Node, Idx: conn.Normal.SenderSide.PortAddr.Idx, Meta: conn.Normal.SenderSide.PortAddr.Meta, @@ -116,13 +117,13 @@ func (d Desugarer) desugarConn( ) } - connsToInsert := []src.Connection{} + connectionsToInsert := []src.Connection{} - // if conn has selectors, desugar it, then replace it and insert generated ones + // if conn has selectors, desugar them, replace original connection and insert what's needed if len(conn.Normal.SenderSide.Selectors) != 0 { result, err := d.desugarStructSelectors(*conn.Normal) if err != nil { - return desugarConnResult{}, compiler.Error{ + return desugarConnectionResult{}, compiler.Error{ Err: errors.New("Cannot desugar struct selectors"), Location: &scope.Location, Meta: &conn.Meta, @@ -133,7 +134,7 @@ func (d Desugarer) desugarConn( constsToInsert[result.constToInsertName] = result.constToInsert // generated connection might need desugaring itself - connToInsertDesugarRes, err := d.desugarConn( + connToInsertDesugarRes, err := d.desugarConnection( result.connToInsert, usedNodePorts, scope, @@ -142,14 +143,14 @@ func (d Desugarer) desugarConn( constsToInsert, ) if err != nil { - return desugarConnResult{}, err + return desugarConnectionResult{}, err } - connsToInsert = append(connsToInsert, connToInsertDesugarRes.connToReplace) - connsToInsert = append(connsToInsert, connToInsertDesugarRes.connsToInsert...) + connectionsToInsert = append(connectionsToInsert, connToInsertDesugarRes.connectionToReplace) + connectionsToInsert = append(connectionsToInsert, connToInsertDesugarRes.connectionsToInsert...) // connection that replaces original one might need desugaring itself - replacedConnDesugarRes, err := d.desugarConn( + replacedConnDesugarRes, err := d.desugarConnection( result.connToReplace, usedNodePorts, scope, @@ -158,27 +159,27 @@ func (d Desugarer) desugarConn( constsToInsert, ) if err != nil { - return desugarConnResult{}, err + return desugarConnectionResult{}, err } - connsToInsert = append(connsToInsert, replacedConnDesugarRes.connsToInsert...) + connectionsToInsert = append(connectionsToInsert, replacedConnDesugarRes.connectionsToInsert...) - conn = replacedConnDesugarRes.connToReplace + conn = replacedConnDesugarRes.connectionToReplace } - // if sender is const or literal, replace it with desugared and insert const/node for emitter + // if sender is const (ref or literal), replace original connection with desugared and insert const and node if conn.Normal.SenderSide.Const != nil { if conn.Normal.SenderSide.Const.Ref != nil { result, err := d.handleConstRefSender(conn, scope) if err != nil { - return desugarConnResult{}, err + return desugarConnectionResult{}, err } nodesToInsert[result.nodeToInsertName] = result.nodeToInsert conn = result.connToReplace } else if conn.Normal.SenderSide.Const.Message != nil { result, err := d.handleLiteralSender(conn) if err != nil { - return desugarConnResult{}, err + return desugarConnectionResult{}, err } constsToInsert[result.constName] = *conn.Normal.SenderSide.Const nodesToInsert[result.nodeToInsertName] = result.nodeToInsert @@ -186,63 +187,71 @@ func (d Desugarer) desugarConn( } } - // if there's no deferred connections, then desugar empty port receivers and that's it - if len(conn.Normal.ReceiverSide.DeferredConnections) == 0 { - desugaredReceivers := slices.Clone(conn.Normal.ReceiverSide.Receivers) - - for i, receiver := range conn.Normal.ReceiverSide.Receivers { - if receiver.PortAddr.Port != "" { - continue - } + desugaredReceivers := make([]src.ConnectionReceiver, 0, len(conn.Normal.ReceiverSide.Receivers)) - found, err := getFirstInportName(scope, nodes, receiver.PortAddr) - if err != nil { - return desugarConnResult{}, &compiler.Error{Err: err} - } + // desugar unnamed receivers if needed and replace them with named ones + for _, receiver := range conn.Normal.ReceiverSide.Receivers { + if receiver.PortAddr.Port != "" { + desugaredReceivers = append(desugaredReceivers, receiver) + continue + } - desugaredReceivers[i] = src.ConnectionReceiver{ - PortAddr: src.PortAddr{ - Port: found, - Node: receiver.PortAddr.Node, - Idx: receiver.PortAddr.Idx, - Meta: receiver.PortAddr.Meta, - }, - Meta: receiver.Meta, - } + firstInportName, err := getFirstInportName(scope, nodes, receiver.PortAddr) + if err != nil { + return desugarConnectionResult{}, &compiler.Error{Err: err} } - return desugarConnResult{ - connToReplace: src.Connection{ - Normal: &src.NormalConnection{ - SenderSide: conn.Normal.SenderSide, - ReceiverSide: src.ConnectionReceiverSide{ - Receivers: desugaredReceivers, - }, - }, - Meta: conn.Meta, + desugaredReceivers = append(desugaredReceivers, src.ConnectionReceiver{ + PortAddr: src.PortAddr{ + Port: firstInportName, + Node: receiver.PortAddr.Node, + Idx: receiver.PortAddr.Idx, + Meta: receiver.PortAddr.Meta, }, - connsToInsert: connsToInsert, - }, nil + Meta: receiver.Meta, + }) } - // if there's desugared connections, desugar them, - // insert what's needed and replace original connection - deferredConnsResult, err := d.handleDeferredConnections( - *conn.Normal, - nodes, - scope, - ) - if err != nil { - return desugarConnResult{}, err + // it's possible to have connection with both normal receivers and deferred connections so we handle both + + if conn.Normal.ReceiverSide.DeferredConnections != nil { + result, err := d.desugarDeferredConnections( + *conn.Normal, + nodes, + scope, + ) + if err != nil { + return desugarConnectionResult{}, err + } + + // desugaring of deferred connections is recursive process so its result must be merged with existing one + usedNodePorts.merge(result.nodesPortsUsed) + maps.Copy(constsToInsert, result.constsToInsert) + maps.Copy(nodesToInsert, result.nodesToInsert) + // after desugaring of deferred connection we need to add new receivers and new connections + desugaredReceivers = append(desugaredReceivers, result.receiversToInsert...) + connectionsToInsert = append(connectionsToInsert, result.connsToInsert...) } - usedNodePorts.merge(deferredConnsResult.nodesPortsUsed) - maps.Copy(constsToInsert, deferredConnsResult.constsToInsert) - maps.Copy(nodesToInsert, deferredConnsResult.nodesToInsert) + // desugar fan-out if needed + if len(desugaredReceivers) > 1 { + result := d.desugarFanOut(desugaredReceivers) + nodesToInsert[result.nodeToInsertName] = result.nodeToInsert + desugaredReceivers = []src.ConnectionReceiver{result.receiverToReplace} // replace all existing receivers with single one + connectionsToInsert = append(connectionsToInsert, result.connectionsToInsert...) + } - return desugarConnResult{ - connToReplace: deferredConnsResult.connToReplace, - connsToInsert: deferredConnsResult.connsToInsert, + return desugarConnectionResult{ + connectionToReplace: src.Connection{ + Normal: &src.NormalConnection{ + SenderSide: conn.Normal.SenderSide, + ReceiverSide: src.ConnectionReceiverSide{ + Receivers: desugaredReceivers, + }, + }, + Meta: conn.Meta, + }, + connectionsToInsert: connectionsToInsert, }, nil } @@ -253,7 +262,11 @@ func getNodeIOByPortAddr( ) (src.IO, *compiler.Error) { node, ok := nodes[portAddr.Node] if !ok { - panic(portAddr.Node) + return src.IO{}, &compiler.Error{ + Err: fmt.Errorf("node '%s' not found", portAddr.Node), + Location: &scope.Location, + Meta: &portAddr.Meta, + } } entity, _, err := scope.Entity(node.EntityRef) @@ -269,7 +282,7 @@ func getNodeIOByPortAddr( if entity.Kind == src.InterfaceEntity { iface = entity.Interface } else { - iface = entity.Flow.Interface + iface = entity.Component.Interface } return iface.IO, nil @@ -286,7 +299,7 @@ func getFirstInportName(scope src.Scope, nodes map[string]src.Node, portAddr src return "", errors.New("first inport not found") } -func getFirstOutPortName(scope src.Scope, nodes map[string]src.Node, portAddr src.PortAddr) (string, error) { +func getFirstOutportName(scope src.Scope, nodes map[string]src.Node, portAddr src.PortAddr) (string, error) { io, err := getNodeIOByPortAddr(scope, nodes, &portAddr) if err != nil { return "", err @@ -296,3 +309,57 @@ func getFirstOutPortName(scope src.Scope, nodes map[string]src.Node, portAddr sr } return "", errors.New("first outport not found") } + +type desugarFanOutResult struct { + nodeToInsertName string + nodeToInsert src.Node + receiverToReplace src.ConnectionReceiver // only one (no more fan-out) + connectionsToInsert []src.Connection +} + +var fanOutCounter atomic.Uint64 + +func (d Desugarer) desugarFanOut(receiverSides []src.ConnectionReceiver) desugarFanOutResult { + counter := fanOutCounter.Load() + fanOutCounter.Store(counter + 1) + nodeName := fmt.Sprintf("__fanOut__%d", counter) + + node := src.Node{ + EntityRef: core.EntityRef{ + Name: "FanOut", + Pkg: "builtin", + }, + } + + receiverToReplace := src.ConnectionReceiver{ + PortAddr: src.PortAddr{ + Node: nodeName, + Port: "data", + }, + } + + connsToInsert := make([]src.Connection, 0, len(receiverSides)) + for i, receiver := range receiverSides { + connsToInsert = append(connsToInsert, src.Connection{ + Normal: &src.NormalConnection{ + SenderSide: src.ConnectionSenderSide{ + PortAddr: &src.PortAddr{ + Node: nodeName, + Port: "data", + Idx: compiler.Pointer(uint8(i)), + }, + }, + ReceiverSide: src.ConnectionReceiverSide{ + Receivers: []src.ConnectionReceiver{receiver}, + }, + }, + }) + } + + return desugarFanOutResult{ + nodeToInsertName: nodeName, + nodeToInsert: node, + receiverToReplace: receiverToReplace, + connectionsToInsert: connsToInsert, + } +} diff --git a/internal/compiler/desugarer/node.go b/internal/compiler/desugarer/node.go index ac20f405..4b7710d3 100644 --- a/internal/compiler/desugarer/node.go +++ b/internal/compiler/desugarer/node.go @@ -49,13 +49,13 @@ func (Desugarer) handleNode( } } - if entity.Kind != src.FlowEntity { + if entity.Kind != src.ComponentEntity { desugaredNodes[nodeName] = node return extraConnections, nil } _, hasAutoports := entity. - Flow. + Component. Directives[compiler.AutoportsDirective] // nothing to desugar @@ -68,7 +68,7 @@ func (Desugarer) handleNode( depArg, ok := node.Deps[""] if ok { - for depParamName, depParam := range entity.Flow.Nodes { + for depParamName, depParam := range entity.Component.Nodes { depEntity, _, err := scope.Entity(depParam.EntityRef) if err != nil { panic(err) @@ -110,7 +110,7 @@ func (Desugarer) handleNode( }, } - localBuilderFlow := src.Flow{ + localBuilderFlow := src.Component{ Interface: src.Interface{ IO: src.IO{In: inports, Out: outports}, }, @@ -119,8 +119,8 @@ func (Desugarer) handleNode( localBuilderName := fmt.Sprintf("struct_%v", nodeName) virtualEntities[localBuilderName] = src.Entity{ - Kind: src.FlowEntity, - Flow: localBuilderFlow, + Kind: src.ComponentEntity, + Component: localBuilderFlow, } desugaredNodes[nodeName] = src.Node{ diff --git a/internal/compiler/desugarer/struct_selectors.go b/internal/compiler/desugarer/struct_selectors.go index 21b84704..8d8544b1 100644 --- a/internal/compiler/desugarer/struct_selectors.go +++ b/internal/compiler/desugarer/struct_selectors.go @@ -34,6 +34,7 @@ var selectorNodeRef = core.EntityRef{ var virtualSelectorsCount atomic.Uint64 +// desugarStructSelectors replaces one connection with 2 connections and a node with const func (d Desugarer) desugarStructSelectors( normConn src.NormalConnection, ) (handleStructSelectorsResult, *compiler.Error) { diff --git a/internal/compiler/ir/ir.go b/internal/compiler/ir/ir.go new file mode 100644 index 00000000..f0a6395e --- /dev/null +++ b/internal/compiler/ir/ir.go @@ -0,0 +1,67 @@ +package ir + +// Program is a graph where ports are vertexes and connections are edges. +type Program struct { + Ports map[PortAddr]struct{} `json:"ports,omitempty"` // All inports and outports in the program. Each with unique address. + // TODO connections must be 1-1 (or maybe fan-in) + Connections map[PortAddr]map[PortAddr]struct{} `json:"connections,omitempty"` // Sender -> receivers (fan-out). + Funcs []FuncCall `json:"funcs,omitempty"` // How to instantiate functions that send and receive messages through ports. +} + +func (p Program) FanIn() map[PortAddr]map[PortAddr]struct{} { + fanIn := make(map[PortAddr]map[PortAddr]struct{}) + + for sender, receivers := range p.Connections { + for receiver := range receivers { + if _, ok := fanIn[receiver]; !ok { + fanIn[receiver] = make(map[PortAddr]struct{}) + } + fanIn[receiver][sender] = struct{}{} + } + } + + return fanIn +} + +// PortAddr is a composite unique identifier for a port. +type PortAddr struct { + Path string `json:"path,omitempty"` // List of upstream nodes including the owner of the port. + Port string `json:"port,omitempty"` // Name of the port. + Idx *uint8 `json:"idx,omitempty"` // Optional index of a slot in array port. +} + +// FuncCall describes call of a runtime function. +type FuncCall struct { + Ref string `json:"ref,omitempty"` // Reference to the function in registry. + IO FuncIO `json:"io,omitempty"` // Input/output ports of the function. + Msg *Message `json:"msg,omitempty"` // Optional initialization message. +} + +// FuncIO is how a runtime function gets access to its ports. +type FuncIO struct { + In []PortAddr `json:"in,omitempty"` // Must be ordered by path -> port -> idx. + Out []PortAddr `json:"out,omitempty"` // Must be ordered by path -> port -> idx. +} + +// Message is a data that can be sent and received. +type Message struct { + Type MsgType `json:"-"` + Bool bool `json:"bool,omitempty"` + Int int64 `json:"int,omitempty"` + Float float64 `json:"float,omitempty"` + String string `json:"str,omitempty"` + List []Message `json:"list,omitempty"` + Dict map[string]Message `json:"map,omitempty"` +} + +// MsgType is an enumeration of message types. +type MsgType int32 + +const ( + MsgTypeBool MsgType = 1 + MsgTypeInt MsgType = 2 + MsgTypeFloat MsgType = 3 + MsgTypeString MsgType = 4 + MsgTypeList MsgType = 5 + MsgTypeMap MsgType = 6 +) diff --git a/internal/compiler/irgen/runtime_func.go b/internal/compiler/irgen/func.go similarity index 61% rename from internal/compiler/irgen/runtime_func.go rename to internal/compiler/irgen/func.go index 0b055fe2..53146736 100644 --- a/internal/compiler/irgen/runtime_func.go +++ b/internal/compiler/irgen/func.go @@ -5,35 +5,12 @@ import ( "strings" "github.com/nevalang/neva/internal/compiler" + "github.com/nevalang/neva/internal/compiler/ir" src "github.com/nevalang/neva/internal/compiler/sourcecode" ts "github.com/nevalang/neva/internal/compiler/sourcecode/typesystem" - "github.com/nevalang/neva/internal/runtime/ir" ) -// scope must contain location where node found, not function -func (g Generator) getFuncCall( - nodeCtx nodeContext, - scope src.Scope, - funcRef string, -) (ir.FuncCall, *compiler.Error) { - cfgMsg, err := getCfgMsg(nodeCtx.node, scope) - if err != nil { - return ir.FuncCall{}, &compiler.Error{ - Err: err, - Location: &scope.Location, - } - } - return ir.FuncCall{ - Ref: funcRef, - IO: ir.FuncIO{ - In: g.getFuncInports(nodeCtx), - Out: g.getFuncOutports(nodeCtx), - }, - Msg: cfgMsg, - }, nil -} - -func getFuncRef(flow src.Flow, nodeTypeArgs []ts.Expr) (string, error) { +func getFuncRef(flow src.Component, nodeTypeArgs []ts.Expr) (string, error) { args, ok := flow.Directives[compiler.ExternDirective] if !ok { return "", nil @@ -60,7 +37,7 @@ func getFuncRef(flow src.Flow, nodeTypeArgs []ts.Expr) (string, error) { return "", errors.New("type argument mismatches runtime func directive") } -func getCfgMsg(node src.Node, scope src.Scope) (*ir.Msg, *compiler.Error) { +func getConfigMsg(node src.Node, scope src.Scope) (*ir.Message, *compiler.Error) { args, ok := node.Directives[compiler.BindDirective] if !ok { return nil, nil diff --git a/internal/compiler/irgen/graph_reduction.go b/internal/compiler/irgen/graph_reduction.go index 05d37450..dde7cc21 100644 --- a/internal/compiler/irgen/graph_reduction.go +++ b/internal/compiler/irgen/graph_reduction.go @@ -1,11 +1,11 @@ package irgen -import "github.com/nevalang/neva/internal/runtime/ir" +import "github.com/nevalang/neva/internal/compiler/ir" // reduceGraph transforms program to a state where it doesn't have intermediate connections. -// It's not optimization, it's a functional requirement of runtime. func reduceGraph(prog *ir.Program) (map[ir.PortAddr]struct{}, map[ir.PortAddr]map[ir.PortAddr]struct{}) { intermediatePorts := map[ir.PortAddr]struct{}{} + netWithoutIntermediateReceivers := make( map[ir.PortAddr]map[ir.PortAddr]struct{}, len(prog.Connections), diff --git a/internal/compiler/irgen/irgen.go b/internal/compiler/irgen/irgen.go index 567bea16..07a3d234 100644 --- a/internal/compiler/irgen/irgen.go +++ b/internal/compiler/irgen/irgen.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/nevalang/neva/internal/compiler" + "github.com/nevalang/neva/internal/compiler/ir" src "github.com/nevalang/neva/internal/compiler/sourcecode" "github.com/nevalang/neva/internal/compiler/sourcecode/core" - "github.com/nevalang/neva/internal/runtime/ir" ) var ErrNodeUsageNotFound = errors.New("node usage not found") @@ -28,13 +28,14 @@ type ( relPortAddr struct { Port string - Idx uint8 + Idx *uint8 } ) func (g Generator) Generate( build src.Build, mainPkgName string, + shouldReduceGraph bool, ) (*ir.Program, *compiler.Error) { scope := src.Scope{ Build: build, @@ -75,11 +76,15 @@ func (g Generator) Generate( }.Wrap(err) } - reducedPorts, reducerNet := reduceGraph(result) + if shouldReduceGraph { + reducedPorts, reducerNet := reduceGraph(result) + result.Ports = reducedPorts + result.Connections = reducerNet + } return &ir.Program{ - Ports: reducedPorts, - Connections: reducerNet, + Ports: result.Ports, + Connections: result.Connections, Funcs: result.Funcs, }, nil } @@ -97,7 +102,10 @@ func (g Generator) processNode( } } - flow := flowEntity.Flow + flow := flowEntity.Component + + inportAddrs := g.insertAndReturnInports(nodeCtx, result) // for inports we only use parent context because all inports are used + outportAddrs := g.insertAndReturnOutports(nodeCtx, result) // for outports we use both parent context and flow's interface runtimeFuncRef, err := getFuncRef(flow, nodeCtx.node.TypeArgs) if err != nil { @@ -109,11 +117,21 @@ func (g Generator) processNode( } if runtimeFuncRef != "" { - call, err := g.getFuncCall(nodeCtx, scope, runtimeFuncRef) + cfgMsg, err := getConfigMsg(nodeCtx.node, scope) if err != nil { - return err + return &compiler.Error{ + Err: err, + Location: &scope.Location, + } } - result.Funcs = append(result.Funcs, call) + result.Funcs = append(result.Funcs, ir.FuncCall{ + Ref: runtimeFuncRef, + IO: ir.FuncIO{ + In: inportAddrs, + Out: outportAddrs, + }, + Msg: cfgMsg, + }) return nil } @@ -170,6 +188,52 @@ func (g Generator) processNode( return nil } +func (Generator) insertAndReturnInports( + nodeCtx nodeContext, + result *ir.Program, +) []ir.PortAddr { + inports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.in)) + + // in valid program all inports are used, so it's safe to depend on nodeCtx and not use flow's IO + // actually we can't use IO because we need to know how many slots are used + for addr := range nodeCtx.portsUsage.in { + addr := ir.PortAddr{ + Path: joinNodePath(nodeCtx.path, "in"), + Port: addr.Port, + Idx: addr.Idx, + } + result.Ports[addr] = struct{}{} + inports = append(inports, addr) + } + + sortPortAddrs(inports) + + return inports +} + +func (Generator) insertAndReturnOutports( + nodeCtx nodeContext, + result *ir.Program, +) []ir.PortAddr { + outports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.out)) + + // In a valid (desugared) program all outports are used so it's safe to depend on nodeCtx and not use flow's IO. + // Actually we can't use IO because we need to know how many slots are used. + for addr := range nodeCtx.portsUsage.out { + irAddr := ir.PortAddr{ + Path: joinNodePath(nodeCtx.path, "out"), + Port: addr.Port, + Idx: addr.Idx, + } + result.Ports[irAddr] = struct{}{} + outports = append(outports, irAddr) + } + + sortPortAddrs(outports) + + return outports +} + func New() Generator { return Generator{} } diff --git a/internal/compiler/irgen/message.go b/internal/compiler/irgen/message.go index 2086ff6d..6e33dbe4 100644 --- a/internal/compiler/irgen/message.go +++ b/internal/compiler/irgen/message.go @@ -4,11 +4,11 @@ import ( "errors" "github.com/nevalang/neva/internal/compiler" + "github.com/nevalang/neva/internal/compiler/ir" src "github.com/nevalang/neva/internal/compiler/sourcecode" - "github.com/nevalang/neva/internal/runtime/ir" ) -func getIRMsgBySrcRef(constant src.Const, scope src.Scope) (*ir.Msg, *compiler.Error) { +func getIRMsgBySrcRef(constant src.Const, scope src.Scope) (*ir.Message, *compiler.Error) { if constant.Ref != nil { entity, location, err := scope.Entity(*constant.Ref) if err != nil { @@ -22,33 +22,33 @@ func getIRMsgBySrcRef(constant src.Const, scope src.Scope) (*ir.Msg, *compiler.E switch { case constant.Message.Bool != nil: - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeBool, Bool: *constant.Message.Bool, }, nil case constant.Message.Int != nil: - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeInt, Int: int64(*constant.Message.Int), }, nil case constant.Message.Float != nil: - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeFloat, Float: *constant.Message.Float, }, nil case constant.Message.Str != nil: - return &ir.Msg{ - Type: ir.MsgTypeString, - Str: *constant.Message.Str, + return &ir.Message{ + Type: ir.MsgTypeString, + String: *constant.Message.Str, }, nil case constant.Message.Enum != nil: enumTypeExpr := constant.Message.TypeExpr.Lit.Enum - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeInt, Int: int64(getEnumMemberIndex(enumTypeExpr, constant.Message.Enum.MemberName)), }, nil case constant.Message.List != nil: - listMsg := make([]ir.Msg, len(constant.Message.List)) + listMsg := make([]ir.Message, len(constant.Message.List)) for i, el := range constant.Message.List { result, err := getIRMsgBySrcRef(el, scope) @@ -58,12 +58,12 @@ func getIRMsgBySrcRef(constant src.Const, scope src.Scope) (*ir.Msg, *compiler.E listMsg[i] = *result } - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeList, List: listMsg, }, nil case constant.Message.MapOrStruct != nil: - mapMsg := make(map[string]ir.Msg, len(constant.Message.MapOrStruct)) + mapMsg := make(map[string]ir.Message, len(constant.Message.MapOrStruct)) for name, el := range constant.Message.MapOrStruct { result, err := getIRMsgBySrcRef(el, scope) @@ -73,9 +73,9 @@ func getIRMsgBySrcRef(constant src.Const, scope src.Scope) (*ir.Msg, *compiler.E mapMsg[name] = *result // see Q&A on why we don't create flat maps for nested structures } - return &ir.Msg{ + return &ir.Message{ Type: ir.MsgTypeMap, - Map: mapMsg, + Dict: mapMsg, }, nil } diff --git a/internal/compiler/irgen/network.go b/internal/compiler/irgen/network.go index 5c3ae1e7..87191ed3 100644 --- a/internal/compiler/irgen/network.go +++ b/internal/compiler/irgen/network.go @@ -5,8 +5,8 @@ import ( "sort" "strings" + "github.com/nevalang/neva/internal/compiler/ir" src "github.com/nevalang/neva/internal/compiler/sourcecode" - "github.com/nevalang/neva/internal/runtime/ir" ) // processNetwork @@ -21,9 +21,9 @@ func (g Generator) processNetwork( for _, conn := range conns { // here's how we handle array-bypass connections: - // sender is always flow's inport + // sender is always component's inport // based on that, we can set receiver's inport slots - // equal to slots of our inport + // equal to slots of our own inport if conn.ArrayBypass != nil { arrBypassSender := conn.ArrayBypass.SenderOutport arrBypassReceiver := conn.ArrayBypass.ReceiverInport @@ -37,23 +37,25 @@ func (g Generator) processNetwork( var slotIdx uint8 = 0 for usageAddr := range nodeCtx.portsUsage.in { - if usageAddr.Port != arrBypassSender.Port { + if usageAddr.Port != arrBypassSender.Port { // we only care about the port we're bypassing continue } - addr := relPortAddr{Port: arrBypassReceiver.Port, Idx: slotIdx} + slotIdxCopy := slotIdx // fix of "Using the variable on range scope" issue + + addr := relPortAddr{Port: arrBypassReceiver.Port, Idx: &slotIdxCopy} // TODO check if pointer is ok to use here nodesPortsUsage[arrBypassReceiver.Node].in[addr] = struct{}{} irSenderSlot := ir.PortAddr{ Path: joinNodePath(nodeCtx.path, arrBypassSender.Node), Port: arrBypassSender.Port, - Idx: uint32(slotIdx), + Idx: &slotIdxCopy, } irReceiverSlot := ir.PortAddr{ Path: joinNodePath(nodeCtx.path, arrBypassReceiver.Node) + "/in", Port: arrBypassReceiver.Port, - Idx: uint32(slotIdx), + Idx: &slotIdxCopy, } result.Connections[irSenderSlot] = map[ir.PortAddr]struct{}{ @@ -78,9 +80,11 @@ func (g Generator) processNetwork( } receiverPortsIR := make(map[ir.PortAddr]struct{}, len(conn.Normal.ReceiverSide.Receivers)) + for _, receiverSide := range conn.Normal.ReceiverSide.Receivers { receiverSideIR := g.mapReceiverSide(nodeCtx.path, receiverSide) - receiverPortsIR[*receiverSideIR] = struct{}{} + + receiverPortsIR[receiverSideIR] = struct{}{} // same receiver can be used by multiple senders so we only add it once if _, ok := nodesPortsUsage[receiverSide.PortAddr.Node]; !ok { @@ -90,21 +94,16 @@ func (g Generator) processNetwork( } } - var idx uint8 - if receiverSide.PortAddr.Idx != nil { - idx = *receiverSide.PortAddr.Idx - } - receiverNode := receiverSide.PortAddr.Node receiverPortAddr := relPortAddr{ Port: receiverSide.PortAddr.Port, - Idx: idx, + Idx: receiverSide.PortAddr.Idx, } nodesPortsUsage[receiverNode].in[receiverPortAddr] = struct{}{} } - result.Connections[*irSenderSidePortAddr] = receiverPortsIR + result.Connections[irSenderSidePortAddr] = receiverPortsIR } return nodesPortsUsage, nil @@ -114,7 +113,7 @@ func (g Generator) processSenderSide( nodeCtx nodeContext, senderSide src.ConnectionSenderSide, result map[string]portsUsage, -) (*ir.PortAddr, error) { +) (ir.PortAddr, error) { // there could be many connections with the same sender but we must only add it once if _, ok := result[senderSide.PortAddr.Node]; !ok { result[senderSide.PortAddr.Node] = portsUsage{ @@ -123,52 +122,25 @@ func (g Generator) processSenderSide( } } - var idx uint8 - if senderSide.PortAddr.Idx != nil { - idx = *senderSide.PortAddr.Idx - } - // insert outport usage result[senderSide.PortAddr.Node].out[relPortAddr{ Port: senderSide.PortAddr.Port, - Idx: idx, + Idx: senderSide.PortAddr.Idx, }] = struct{}{} - irSenderSide := &ir.PortAddr{ + irSenderPort := ir.PortAddr{ Path: joinNodePath(nodeCtx.path, senderSide.PortAddr.Node), Port: senderSide.PortAddr.Port, - Idx: uint32(idx), - } - - if senderSide.PortAddr.Node == "in" { - return irSenderSide, nil + Idx: senderSide.PortAddr.Idx, } - irSenderSide.Path += "/out" - return irSenderSide, nil -} - -// for in we only use parent ctx cuz all inports are used -func (Generator) getFuncInports(nodeCtx nodeContext) []ir.PortAddr { - inports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.in)) - - // in valid program all inports are used, so it's safe to depend on nodeCtx and not use flow's IO - // actually we can't use IO because we need to know how many slots are used - for addr := range nodeCtx.portsUsage.in { - addr := &ir.PortAddr{ - Path: joinNodePath(nodeCtx.path, "in"), - Port: addr.Port, - Idx: uint32(addr.Idx), - } - inports = append(inports, *addr) + if senderSide.PortAddr.Node != "in" { + irSenderPort.Path += "/out" } - sortPortAddrs(inports) - - return inports + return irSenderPort, nil } -// sortPortAddrs sorts port addresses by path, port and idx, // this is very important because runtime function calls depends on this order. func sortPortAddrs(addrs []ir.PortAddr) { sort.Slice(addrs, func(i, j int) bool { @@ -178,47 +150,25 @@ func sortPortAddrs(addrs []ir.PortAddr) { if addrs[i].Port != addrs[j].Port { return addrs[i].Port < addrs[j].Port } - return addrs[i].Idx < addrs[j].Idx - }) -} - -// for out we use both parent ctx and interface -func (Generator) getFuncOutports(nodeCtx nodeContext) []ir.PortAddr { - outports := make([]ir.PortAddr, 0, len(nodeCtx.portsUsage.out)) - - // In a valid (desugared) program all outports are used so it's safe to depend on nodeCtx and not use flow's IO. - // Actually we can't use IO because we need to know how many slots are used. - for addr := range nodeCtx.portsUsage.out { - irAddr := &ir.PortAddr{ - Path: joinNodePath(nodeCtx.path, "out"), - Port: addr.Port, - Idx: uint32(addr.Idx), + if addrs[i].Idx == nil { + return true } - - outports = append(outports, *irAddr) - } - - sortPortAddrs(outports) - - return outports + return *addrs[i].Idx < *addrs[j].Idx + }) } // mapReceiverSide maps src connection side to ir connection side 1-1 just making the port addr's path absolute -func (g Generator) mapReceiverSide(nodeCtxPath []string, side src.ConnectionReceiver) *ir.PortAddr { - var idx uint8 - if side.PortAddr.Idx != nil { - idx = *side.PortAddr.Idx - } - - result := &ir.PortAddr{ +func (g Generator) mapReceiverSide(nodeCtxPath []string, side src.ConnectionReceiver) ir.PortAddr { + result := ir.PortAddr{ Path: joinNodePath(nodeCtxPath, side.PortAddr.Node), Port: side.PortAddr.Port, - Idx: uint32(idx), + Idx: side.PortAddr.Idx, } - if side.PortAddr.Node == "out" { // 'out' node is actually receiver but we don't want to have 'out.in' addresses - return result + + // 'out' node is actually receiver but we don't want to have 'out/in' path + if side.PortAddr.Node != "out" { + result.Path += "/in" } - result.Path += "/in" return result } diff --git a/internal/compiler/irgen/network_test.go b/internal/compiler/irgen/network_test.go index ea0f09e1..b89c94b6 100644 --- a/internal/compiler/irgen/network_test.go +++ b/internal/compiler/irgen/network_test.go @@ -3,7 +3,8 @@ package irgen import ( "testing" - "github.com/nevalang/neva/internal/runtime/ir" + "github.com/nevalang/neva/internal/compiler" + "github.com/nevalang/neva/internal/compiler/ir" "github.com/stretchr/testify/require" ) @@ -44,22 +45,22 @@ func Test_sortPortAddrs(t *testing.T) { { name: "messed up order", addrs: []ir.PortAddr{ - {Path: "b", Port: "A", Idx: 1}, - {Path: "b", Port: "A", Idx: 0}, - {Path: "a", Port: "B", Idx: 0}, - {Path: "a", Port: "B", Idx: 1}, - {Path: "a", Port: "A", Idx: 2}, - {Path: "a", Port: "A", Idx: 1}, - {Path: "a", Port: "A", Idx: 0}, + {Path: "b", Port: "A", Idx: compiler.Pointer(uint8(1))}, + {Path: "b", Port: "A", Idx: compiler.Pointer(uint8(0))}, + {Path: "a", Port: "B", Idx: compiler.Pointer(uint8(0))}, + {Path: "a", Port: "B", Idx: compiler.Pointer(uint8(1))}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(2))}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(1))}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(0))}, }, want: []ir.PortAddr{ - {Path: "a", Port: "A", Idx: 0}, - {Path: "a", Port: "A", Idx: 1}, - {Path: "a", Port: "A", Idx: 2}, - {Path: "a", Port: "B", Idx: 0}, - {Path: "a", Port: "B", Idx: 1}, - {Path: "b", Port: "A", Idx: 0}, - {Path: "b", Port: "A", Idx: 1}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(0))}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(1))}, + {Path: "a", Port: "A", Idx: compiler.Pointer(uint8(2))}, + {Path: "a", Port: "B", Idx: compiler.Pointer(uint8(0))}, + {Path: "a", Port: "B", Idx: compiler.Pointer(uint8(1))}, + {Path: "b", Port: "A", Idx: compiler.Pointer(uint8(0))}, + {Path: "b", Port: "A", Idx: compiler.Pointer(uint8(1))}, }, }, } diff --git a/internal/compiler/parser/listener.go b/internal/compiler/parser/listener.go index 421279fc..75b606e0 100644 --- a/internal/compiler/parser/listener.go +++ b/internal/compiler/parser/listener.go @@ -105,7 +105,7 @@ func (s *treeShapeListener) EnterCompStmt(actx *generated.CompStmtContext) { } parsedCompEntity.IsPublic = actx.PUB_KW() != nil - parsedCompEntity.Flow.Directives = parseCompilerDirectives( + parsedCompEntity.Component.Directives = parseCompilerDirectives( actx.CompilerDirectives(), ) name := compDef.InterfaceDef().IDENTIFIER().GetText() diff --git a/internal/compiler/parser/listener_helpers.go b/internal/compiler/parser/listener_helpers.go index d86e2b1b..2d165f75 100644 --- a/internal/compiler/parser/listener_helpers.go +++ b/internal/compiler/parser/listener_helpers.go @@ -1070,8 +1070,8 @@ func parseCompDef(actx generated.ICompDefContext) (src.Entity, *compiler.Error) body := actx.CompBody() if body == nil { return src.Entity{ - Kind: src.FlowEntity, - Flow: src.Flow{ + Kind: src.ComponentEntity, + Component: src.Component{ Interface: parsedInterfaceDef, }, }, nil @@ -1090,8 +1090,8 @@ func parseCompDef(actx generated.ICompDefContext) (src.Entity, *compiler.Error) nodesDef := body.CompNodesDef() if nodesDef == nil { return src.Entity{ - Kind: src.FlowEntity, - Flow: src.Flow{ + Kind: src.ComponentEntity, + Component: src.Component{ Interface: parsedInterfaceDef, Net: parsedConnections, }, @@ -1106,8 +1106,8 @@ func parseCompDef(actx generated.ICompDefContext) (src.Entity, *compiler.Error) parsedNodes = v return src.Entity{ - Kind: src.FlowEntity, - Flow: src.Flow{ + Kind: src.ComponentEntity, + Component: src.Component{ Interface: parsedInterfaceDef, Nodes: parsedNodes, Net: parsedConnections, diff --git a/internal/compiler/parser/parser_test.go b/internal/compiler/parser/parser_test.go index 621bdcfa..30fdab4c 100644 --- a/internal/compiler/parser/parser_test.go +++ b/internal/compiler/parser/parser_test.go @@ -19,7 +19,7 @@ func TestParser_ParseFile_StructSelectorsWithLonelyChain(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - net := got.Entities["C1"].Flow.Net + net := got.Entities["C1"].Component.Net require.Equal(t, 2, len(net)) conn := net[0].Normal @@ -48,7 +48,7 @@ func TestParser_ParseFile_PortlessArrPortAddr(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.Equal(t, true, err == nil) - net := got.Entities["C1"].Flow.Net + net := got.Entities["C1"].Component.Net conn := net[0].Normal // foo[0]-> @@ -74,7 +74,7 @@ func TestParser_ParseFile_ChainedConnectionsWithDefer(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - net := got.Entities["C1"].Flow.Net + net := got.Entities["C1"].Component.Net require.Equal(t, 2, len(net)) } @@ -96,7 +96,7 @@ func TestParser_ParseFile_LonelyPorts(t *testing.T) { // 2) lonely -> :port // 3) :port -> lonely // 4) lonely -> :port - net := got.Entities["C1"].Flow.Net + net := got.Entities["C1"].Component.Net require.Equal(t, 4, len(net)) // 1) :port -> lonely @@ -130,7 +130,7 @@ func TestParser_ParseFile_ChainedConnections(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - net := got.Entities["C1"].Flow.Net + net := got.Entities["C1"].Component.Net require.Equal(t, 2, len(net)) } @@ -174,10 +174,10 @@ func TestParser_ParseFile_Directives(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - d1 := got.Entities["C1"].Flow.Directives[compiler.ExternDirective][0] + d1 := got.Entities["C1"].Component.Directives[compiler.ExternDirective][0] require.Equal(t, "d1", d1) - c2 := got.Entities["C2"].Flow + c2 := got.Entities["C2"].Component d2 := c2.Directives[compiler.ExternDirective][0] require.Equal(t, "d2", d2) @@ -188,11 +188,11 @@ func TestParser_ParseFile_Directives(t *testing.T) { d4 := c2.Nodes["n2"].Directives[compiler.BindDirective][0] require.Equal(t, "d4", d4) - c3 := got.Entities["C3"].Flow + c3 := got.Entities["C3"].Component _, ok := c3.Directives[compiler.AutoportsDirective] require.Equal(t, true, ok) - c4 := got.Entities["C4"].Flow + c4 := got.Entities["C4"].Component d5, ok := c4.Directives[compiler.ExternDirective] require.Equal(t, true, ok) require.Equal(t, "d5", d5[0]) @@ -212,7 +212,7 @@ func TestParser_ParseFile_IONodes(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - conn := got.Entities["C1"].Flow.Net[0] + conn := got.Entities["C1"].Component.Net[0] sender := conn.Normal.SenderSide.PortAddr.Node require.Equal(t, "in", sender) @@ -236,7 +236,7 @@ func TestParser_ParseFile_AnonymousNodes(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - nodes := got.Entities["C1"].Flow.Nodes + nodes := got.Entities["C1"].Component.Nodes _, ok := nodes["scanner"] require.Equal(t, true, ok) @@ -280,14 +280,14 @@ func TestParser_ParseFile_EnumLiteralSenders(t *testing.T) { got, err := p.parseFile(src.Location{}, text) require.True(t, err == nil) - conn := got.Entities["C1"].Flow.Net[0] + conn := got.Entities["C1"].Component.Net[0] senderEnum := conn.Normal.SenderSide.Const.Message.Enum require.Equal(t, "", senderEnum.EnumRef.Pkg) require.Equal(t, "Foo", senderEnum.EnumRef.Name) require.Equal(t, "Bar", senderEnum.MemberName) - conn = got.Entities["C1"].Flow.Net[1] + conn = got.Entities["C1"].Component.Net[1] senderEnum = conn.Normal.SenderSide.Const.Message.Enum require.Equal(t, "foo", senderEnum.EnumRef.Pkg) require.Equal(t, "Bar", senderEnum.EnumRef.Name) diff --git a/internal/compiler/sourcecode/sourcecode.go b/internal/compiler/sourcecode/sourcecode.go index b97f5f67..ebdfdb0c 100644 --- a/internal/compiler/sourcecode/sourcecode.go +++ b/internal/compiler/sourcecode/sourcecode.go @@ -101,7 +101,7 @@ type Entity struct { Const Const `json:"const,omitempty"` Type ts.Def `json:"type,omitempty"` Interface Interface `json:"interface,omitempty"` - Flow Flow `json:"flow,omitempty"` + Component Component `json:"flow,omitempty"` } func (e Entity) Meta() *core.Meta { @@ -113,8 +113,8 @@ func (e Entity) Meta() *core.Meta { m = e.Type.Meta.(core.Meta) //nolint case InterfaceEntity: m = e.Interface.Meta - case FlowEntity: - m = e.Flow.Meta + case ComponentEntity: + m = e.Component.Meta } return &m } @@ -122,13 +122,13 @@ func (e Entity) Meta() *core.Meta { type EntityKind string // It's handy to transmit strings enum instead of digital const ( - FlowEntity EntityKind = "flow_entity" + ComponentEntity EntityKind = "component_entity" ConstEntity EntityKind = "const_entity" TypeEntity EntityKind = "type_entity" InterfaceEntity EntityKind = "interface_entity" ) -type Flow struct { +type Component struct { Directives map[Directive][]string `json:"directives,omitempty"` Interface `json:"interface,omitempty"` Nodes map[string]Node `json:"nodes,omitempty"` diff --git a/internal/interpreter/adapter/adapter.go b/internal/interpreter/adapter/adapter.go new file mode 100644 index 00000000..7db12e9a --- /dev/null +++ b/internal/interpreter/adapter/adapter.go @@ -0,0 +1,136 @@ +package adapter + +import ( + "github.com/nevalang/neva/internal/compiler/ir" + "github.com/nevalang/neva/internal/runtime" +) + +type Adapter struct{} + +func (a Adapter) Adapt(irProg *ir.Program) (runtime.Program, error) { + fanIn := irProg.FanIn() + + ports := a.getPorts(irProg.Ports, fanIn) + + connections := a.getConnections(fanIn, ports) + + funcs, err := a.getFuncs(irProg, ports) + if err != nil { + return runtime.Program{}, err + } + + start := ports[runtime.PortAddr{ + Path: "in", + Port: "start", + }] + + stop := ports[runtime.PortAddr{ + Path: "out", + Port: "stop", + }] + + return runtime.Program{ + Start: start, + Stop: stop, + Connections: connections, + Funcs: funcs, + }, nil +} + +func (Adapter) getConnections( + fanIn map[ir.PortAddr]map[ir.PortAddr]struct{}, + ports map[runtime.PortAddr]chan runtime.IndexedMsg, +) map[runtime.Receiver][]runtime.Sender { + runtimeConnections := make( + map[runtime.Receiver][]runtime.Sender, + len(fanIn), + ) + + for irReceiverAddr, senderAddrs := range fanIn { + runtimeReceiverAddr := runtime.PortAddr{ + Path: irReceiverAddr.Path, + Port: irReceiverAddr.Port, + } + + if irReceiverAddr.Idx != nil { + runtimeReceiverAddr.Idx = *irReceiverAddr.Idx + runtimeReceiverAddr.Arr = true + } + + receiverChan, ok := ports[runtimeReceiverAddr] + if !ok { + panic("receiver chan not found") + } + + receiver := runtime.Receiver{ + Addr: runtimeReceiverAddr, + Port: receiverChan, + } + + senders := make([]runtime.Sender, 0, len(senderAddrs)) + + for senderAddr := range senderAddrs { + senderRuntimeAddr := runtime.PortAddr{ + Path: senderAddr.Path, + Port: senderAddr.Port, + } + + if senderAddr.Idx != nil { + senderRuntimeAddr.Idx = *senderAddr.Idx + senderRuntimeAddr.Arr = true + } + + senderChan, ok := ports[senderRuntimeAddr] + if !ok { + panic("sender chan not found: " + senderRuntimeAddr.String()) + } + + senders = append(senders, runtime.Sender{ + Addr: senderRuntimeAddr, + Port: senderChan, + }) + } + + runtimeConnections[receiver] = senders + } + + return runtimeConnections +} + +func (Adapter) getPorts( + ports map[ir.PortAddr]struct{}, + fanIn map[ir.PortAddr]map[ir.PortAddr]struct{}, +) map[runtime.PortAddr]chan runtime.IndexedMsg { + runtimePorts := make( + map[runtime.PortAddr]chan runtime.IndexedMsg, + len(ports), + ) + + for irAddr := range ports { + runtimeAddr := runtime.PortAddr{ + Path: irAddr.Path, + Port: irAddr.Port, + } + + if irAddr.Idx != nil { + runtimeAddr.Idx = *irAddr.Idx + runtimeAddr.Arr = true + } + + // TODO figure out how to set buf for senders (we don't have fan-out) + // IDEA we can do it in src lvl + + bufSize := 0 + // if senders, ok := fanIn[irAddr]; ok { + // bufSize = len(senders) + // } + + runtimePorts[runtimeAddr] = make(chan runtime.IndexedMsg, bufSize) + } + + return runtimePorts +} + +func NewAdapter() Adapter { + return Adapter{} +} diff --git a/internal/interpreter/adapter/funcs.go b/internal/interpreter/adapter/funcs.go new file mode 100644 index 00000000..0968cbcf --- /dev/null +++ b/internal/interpreter/adapter/funcs.go @@ -0,0 +1,163 @@ +package adapter + +import ( + "errors" + "fmt" + + "github.com/nevalang/neva/internal/compiler/ir" + "github.com/nevalang/neva/internal/runtime" +) + +func (a Adapter) getFuncs( + prog *ir.Program, + ports map[runtime.PortAddr]chan runtime.IndexedMsg, +) ([]runtime.FuncCall, error) { + result := make([]runtime.FuncCall, 0, len(prog.Funcs)) + + for _, call := range prog.Funcs { + // INPORTS + + funcInports := make( + map[string]runtime.FuncInport, + len(call.IO.In), + ) + + tmpArrInports := make(map[string][]<-chan runtime.IndexedMsg, len(call.IO.In)) + + // in first run we fill single ports and collect array ports to tmp var + for _, addr := range call.IO.In { + runtimeAddr := runtime.PortAddr{ + Path: addr.Path, + Port: addr.Port, + } + + if addr.Idx != nil { + runtimeAddr.Idx = *addr.Idx + runtimeAddr.Arr = true + } + + port, ok := ports[runtimeAddr] + if !ok { + panic("port not found") + } + + if addr.Idx == nil { + funcInports[addr.Port] = runtime.NewFuncInport( + nil, + runtime.NewSingleInport(port), + ) + continue + } + + tmpArrInports[addr.Port] = append(tmpArrInports[addr.Port], port) + } + + // single ports already handled, it's time to create arr ports from tmp var + for name, slots := range tmpArrInports { + funcInports[name] = runtime.NewFuncInport( + runtime.NewArrayInport(slots), + nil, + ) + } + + // OUTPORTS + + funcOutports := make(map[string]runtime.FuncOutport, len(call.IO.Out)) + + tmpArrOutports := map[string][]chan<- runtime.IndexedMsg{} + + for _, addr := range call.IO.Out { + runtimeAddr := runtime.PortAddr{ + Path: addr.Path, + Port: addr.Port, + // Idx: addr.Idx, + } + + if addr.Idx != nil { + runtimeAddr.Idx = *addr.Idx + runtimeAddr.Arr = true + } + + port, ok := ports[runtimeAddr] + if !ok { + panic("port not found") + } + + if addr.Idx == nil { + funcOutports[addr.Port] = runtime.NewFuncOutport( + runtime.NewSingleOutport(runtimeAddr, port), + nil, + ) + continue + } + + tmpArrOutports[addr.Port] = append(tmpArrOutports[addr.Port], port) + } + + for name, slots := range tmpArrOutports { + funcOutports[name] = runtime.NewFuncOutport( + nil, + runtime.NewArrayOutport(slots), + ) + } + + rFunc := runtime.FuncCall{ + Ref: call.Ref, + IO: runtime.FuncIO{ + In: runtime.NewFuncInports(funcInports), + Out: runtime.NewFuncOutports(funcOutports), + }, + } + + if call.Msg != nil { + rMsg, err := a.getMessage(*call.Msg) + if err != nil { + return nil, fmt.Errorf("msg: %w", err) + } + rFunc.ConfigMsg = rMsg + } + + result = append(result, rFunc) + } + + return result, nil +} + +func (a Adapter) getMessage(msg ir.Message) (runtime.Msg, error) { + var result runtime.Msg + + switch msg.Type { + case ir.MsgTypeBool: + result = runtime.NewBoolMsg(msg.Bool) + case ir.MsgTypeInt: + result = runtime.NewIntMsg(msg.Int) + case ir.MsgTypeFloat: + result = runtime.NewFloatMsg(msg.Float) + case ir.MsgTypeString: + result = runtime.NewStrMsg(msg.String) + case ir.MsgTypeList: + list := make([]runtime.Msg, len(msg.List)) + for i, v := range msg.List { + el, err := a.getMessage(v) + if err != nil { + return nil, err + } + list[i] = el + } + result = runtime.NewListMsg(list) + case ir.MsgTypeMap: + m := make(map[string]runtime.Msg, len(msg.List)) + for k, v := range msg.Dict { + el, err := a.getMessage(v) + if err != nil { + return nil, err + } + m[k] = el + } + result = runtime.NewMapMsg(m) + default: + return nil, errors.New("unknown message type") + } + + return result, nil +} diff --git a/internal/interpreter/event_listener.go b/internal/interpreter/event_listener.go deleted file mode 100644 index 7105439e..00000000 --- a/internal/interpreter/event_listener.go +++ /dev/null @@ -1,14 +0,0 @@ -package interpreter - -import ( - "fmt" - - "github.com/nevalang/neva/internal/runtime" -) - -type DebugEventListener struct{} - -func (e DebugEventListener) Send(event runtime.Event, msg runtime.Msg) runtime.Msg { - fmt.Println(event, msg) - return msg -} diff --git a/internal/interpreter/interpreter.go b/internal/interpreter/interpreter.go index 74552845..219dd27a 100644 --- a/internal/interpreter/interpreter.go +++ b/internal/interpreter/interpreter.go @@ -6,8 +6,8 @@ import ( "github.com/nevalang/neva/internal/builder" "github.com/nevalang/neva/internal/compiler" "github.com/nevalang/neva/internal/compiler/sourcecode" + "github.com/nevalang/neva/internal/interpreter/adapter" "github.com/nevalang/neva/internal/runtime" - "github.com/nevalang/neva/internal/runtime/adapter" "github.com/nevalang/neva/internal/runtime/funcs" ) @@ -18,12 +18,12 @@ type Interpreter struct { adapter adapter.Adapter } -func (i Interpreter) Interpret(ctx context.Context, workdirPath string, mainPkgName string) *compiler.Error { - irProg, compilerErr := i.compiler.CompileToIR(workdirPath, mainPkgName) +func (i Interpreter) Interpret(ctx context.Context, main string, debug bool) *compiler.Error { + irProg, compilerErr := i.compiler.CompileToIR(main, debug) if compilerErr != nil { return compiler.Error{ Location: &sourcecode.Location{ - PkgName: mainPkgName, + PkgName: main, }, }.Wrap(compilerErr) } @@ -31,19 +31,15 @@ func (i Interpreter) Interpret(ctx context.Context, workdirPath string, mainPkgN rprog, err := i.adapter.Adapt(irProg) if err != nil { return &compiler.Error{ - Err: err, - Location: &sourcecode.Location{ - PkgName: mainPkgName, - }, + Err: err, + Location: &sourcecode.Location{PkgName: main}, } } - if err := i.runtime.Run(ctx, rprog); err != nil { + if err := i.runtime.Run(ctx, rprog, debug); err != nil { return &compiler.Error{ - Err: err, - Location: &sourcecode.Location{ - PkgName: mainPkgName, - }, + Err: err, + Location: &sourcecode.Location{PkgName: main}, } } @@ -53,21 +49,13 @@ func (i Interpreter) Interpret(ctx context.Context, workdirPath string, mainPkgN func New( builder builder.Builder, compiler compiler.Compiler, - isDebug bool, ) Interpreter { - var connector runtime.Connector - if isDebug { - connector = runtime.NewConnector(DebugEventListener{}) - } else { - connector = runtime.NewDefaultConnector() - } return Interpreter{ builder: builder, compiler: compiler, adapter: adapter.NewAdapter(), runtime: runtime.New( - connector, - runtime.MustNewFuncRunner( + runtime.NewFuncRunner( funcs.CreatorRegistry(), ), ), diff --git a/internal/runtime/adapter/adapter.go b/internal/runtime/adapter/adapter.go deleted file mode 100644 index 231e7110..00000000 --- a/internal/runtime/adapter/adapter.go +++ /dev/null @@ -1,156 +0,0 @@ -package adapter - -import ( - "errors" - "fmt" - - "github.com/nevalang/neva/internal/runtime" - "github.com/nevalang/neva/internal/runtime/ir" -) - -type Adapter struct{} - -func (a Adapter) Adapt(irProg *ir.Program) (runtime.Program, error) { - runtimePorts := make(runtime.Ports, len(irProg.Ports)) - - for portInfo := range irProg.Ports { - addr := runtime.PortAddr{ - Path: portInfo.Path, - Port: portInfo.Port, - Idx: uint8(portInfo.Idx), - } - runtimePorts[addr] = make(chan runtime.Msg) - } - - runtimeConnections := make([]runtime.Connection, 0, len(irProg.Connections)) - for sender, receivers := range irProg.Connections { - senderPortAddr := runtime.PortAddr{ - Path: sender.Path, - Port: sender.Port, - Idx: uint8(sender.Idx), - } - - senderPortChan, ok := runtimePorts[senderPortAddr] - if !ok { - return runtime.Program{}, fmt.Errorf("sender port not found %v", senderPortAddr) - } - - meta := runtime.ConnectionMeta{ - SenderPortAddr: senderPortAddr, - ReceiverPortAddrs: make([]runtime.PortAddr, 0, len(receivers)), - } - receiverChans := make([]chan runtime.Msg, 0, len(receivers)) - - for rcvr := range receivers { - receiverPortAddr := runtime.PortAddr{ - Path: rcvr.Path, - Port: rcvr.Port, - Idx: uint8(rcvr.Idx), - } - - receiverPortChan, ok := runtimePorts[receiverPortAddr] - if !ok { - return runtime.Program{}, fmt.Errorf("receiver port not found: %v", receiverPortAddr) - } - - meta.ReceiverPortAddrs = append(meta.ReceiverPortAddrs, receiverPortAddr) - receiverChans = append(receiverChans, receiverPortChan) - } - - runtimeConnections = append(runtimeConnections, runtime.Connection{ - Sender: senderPortChan, - Receivers: receiverChans, - Meta: meta, - }) - } - - runtimeFuncs := make([]runtime.FuncCall, 0, len(irProg.Funcs)) - for _, f := range irProg.Funcs { - rIOIn := make(map[string][]chan runtime.Msg, len(f.IO.In)) - for _, addr := range f.IO.In { - rPort := runtimePorts[runtime.PortAddr{ - Path: addr.Path, - Port: addr.Port, - Idx: uint8(addr.Idx), - }] - rIOIn[addr.Port] = append(rIOIn[addr.Port], rPort) - } - - rIOOut := make(map[string][]chan runtime.Msg, len(f.IO.Out)) - for _, addr := range f.IO.Out { - rPort := runtimePorts[runtime.PortAddr{ - Path: addr.Path, - Port: addr.Port, - Idx: uint8(addr.Idx), - }] - rIOOut[addr.Port] = append(rIOOut[addr.Port], rPort) - } - - rFunc := runtime.FuncCall{ - Ref: f.Ref, - IO: runtime.FuncIO{ - In: rIOIn, - Out: rIOOut, - }, - } - - if f.Msg != nil { - rMsg, err := a.msg(*f.Msg) - if err != nil { - return runtime.Program{}, fmt.Errorf("msg: %w", err) - } - rFunc.ConfigMsg = rMsg - } - - runtimeFuncs = append(runtimeFuncs, rFunc) - } - - return runtime.Program{ - Ports: runtimePorts, - Connections: runtimeConnections, - Funcs: runtimeFuncs, - }, nil -} - -func (a Adapter) msg(msg ir.Msg) (runtime.Msg, error) { - var result runtime.Msg - - switch msg.Type { - case ir.MsgTypeBool: - result = runtime.NewBoolMsg(msg.Bool) - case ir.MsgTypeInt: - result = runtime.NewIntMsg(msg.Int) - case ir.MsgTypeFloat: - result = runtime.NewFloatMsg(msg.Float) - case ir.MsgTypeString: - result = runtime.NewStrMsg(msg.Str) - case ir.MsgTypeList: - list := make([]runtime.Msg, len(msg.List)) - for i, v := range msg.List { - el, err := a.msg(v) - if err != nil { - return nil, err - } - list[i] = el - } - result = runtime.NewListMsg(list...) - case ir.MsgTypeMap: - m := make(map[string]runtime.Msg, len(msg.List)) - for k, v := range msg.Map { - el, err := a.msg(v) - if err != nil { - return nil, err - } - m[k] = el - } - result = runtime.NewMapMsg(m) - default: - return nil, errors.New("unknown message type") - } - - return result, nil -} - -func NewAdapter() Adapter { - return Adapter{} -} diff --git a/internal/runtime/connector.go b/internal/runtime/connector.go deleted file mode 100644 index e4b7c1b3..00000000 --- a/internal/runtime/connector.go +++ /dev/null @@ -1,138 +0,0 @@ -package runtime - -import ( - "context" - "sync" -) - -type Connector struct { - listener EventListener -} - -func (c Connector) Connect(ctx context.Context, conns []Connection) { - wg := sync.WaitGroup{} - wg.Add(len(conns)) - - for i := range conns { - go func(conn Connection) { - c.broadcast(ctx, conn) - wg.Done() - }(conns[i]) - } - - wg.Wait() -} - -func (c Connector) broadcast(ctx context.Context, conn Connection) { - receiversForEvent := getReceiversForEvent(conn) - - for { - select { - case <-ctx.Done(): - return - case msg := <-conn.Sender: - event := Event{ - Type: MessageSentEvent, - MessageSent: &EventMessageSent{ - SenderPortAddr: conn.Meta.SenderPortAddr, - ReceiverPortAddrs: receiversForEvent, - }, - } - - // distribute will send to this channel after processing first receiver - // warning: it's not clear whether it's safe to move on before all receivers processed - // order of messages must be preserved while distribute goroutines might be concurrent to each other - - c.distribute( - ctx, - c.listener.Send(event, msg), - conn.Meta, - conn.Receivers, - ) - } - } -} - -func getReceiversForEvent(conn Connection) map[PortAddr]struct{} { - receiversForEvent := make(map[PortAddr]struct{}, len(conn.Meta.ReceiverPortAddrs)) - for _, receiverPortAddr := range conn.Meta.ReceiverPortAddrs { - receiversForEvent[receiverPortAddr] = struct{}{} - } - return receiversForEvent -} - -// distribute implements the "Queue-based Round-Robin Algorithm". -func (c Connector) distribute( - ctx context.Context, - msg Msg, - meta ConnectionMeta, - receiverChans []chan Msg, -) { - i := 0 - interceptedMsgs := make(map[PortAddr]Msg, len(receiverChans)) // we can handle same receiver multiple times - - // we make copy because we're gonna modify it - // this is crucial because this array is shared across goroutines - queue := make([]chan Msg, len(receiverChans)) - copy(queue, receiverChans) - receiversPortAddrs := make([]PortAddr, len(receiverChans)) - copy(receiversPortAddrs, meta.ReceiverPortAddrs) - - for len(queue) > 0 { - curRecv := queue[i] - recvPortAddr := receiversPortAddrs[i] - - if _, ok := interceptedMsgs[recvPortAddr]; !ok { // avoid multuple interceptions - event := Event{ - Type: MessagePendingEvent, - MessagePending: &EventMessagePending{ - Meta: meta, - ReceiverPortAddr: recvPortAddr, - }, - } - msg = c.listener.Send(event, msg) - interceptedMsgs[recvPortAddr] = msg - } - - interceptedMsg := interceptedMsgs[recvPortAddr] - - select { - case <-ctx.Done(): - return - case curRecv <- interceptedMsg: // receiver has accepted the message - event := Event{ - Type: MessageReceivedEvent, - MessageReceived: &EventMessageReceived{ - Meta: meta, - ReceiverPortAddr: recvPortAddr, - }, - } - - msg = c.listener.Send(event, msg) // notify listener about the event and save intercepted message - - // remove current receiver from queue - queue = append(queue[:i], queue[i+1:]...) // this append modifies array - receiversPortAddrs = append(receiversPortAddrs[:i], receiversPortAddrs[i+1:]...) - default: // current receiver is busy - if i < len(queue) { // so if we are not at the end of the queue - i++ // then go try next receiver - } - } - - if i == len(queue) { // if this is the end of the queue (and loop isn't over) - i = 0 // then start over - } - } -} - -func NewDefaultConnector() Connector { - return Connector{ - listener: EmptyListener{}, - } -} - -func NewConnector(lis EventListener) Connector { - return Connector{ - listener: lis, - } -} diff --git a/internal/runtime/event_listener.go b/internal/runtime/event_listener.go deleted file mode 100644 index 3ebe2463..00000000 --- a/internal/runtime/event_listener.go +++ /dev/null @@ -1,100 +0,0 @@ -package runtime - -import "fmt" - -type EmptyListener struct{} - -func (l EmptyListener) Send(_ Event, msg Msg) Msg { - return msg -} - -type Event struct { - Type EventType - MessageSent *EventMessageSent - MessagePending *EventMessagePending - MessageReceived *EventMessageReceived -} - -func (e Event) String() string { - var s string - switch e.Type { - case MessageSentEvent: - s = e.MessageSent.String() - case MessagePendingEvent: - s = e.MessagePending.String() - case MessageReceivedEvent: - s = e.MessageReceived.String() - } - - return fmt.Sprintf("%v: %v", e.Type.String(), s) -} - -type EventMessageSent struct { - SenderPortAddr PortAddr - ReceiverPortAddrs map[PortAddr]struct{} // We use map to work with breakpoints -} - -func (e EventMessageSent) String() string { - if len(e.ReceiverPortAddrs) == 1 { - for singleReceiver := range e.ReceiverPortAddrs { - return fmt.Sprintf("%v -> %v", e.SenderPortAddr, singleReceiver) - } - } - - i := 0 - receiversStr := "[" - for receiver := range e.ReceiverPortAddrs { - receiversStr += receiver.String() - if i != len(e.ReceiverPortAddrs)-1 { - receiversStr += ", " - } - i++ - } - receiversStr += "]" - - return fmt.Sprintf("%v -> %v", e.SenderPortAddr, receiversStr) -} - -// EventMessagePending describes event when message has reached receiver but not yet passed inside. -// It's usefull only for interception and modifying message for specific receiver. -type EventMessagePending struct { - Meta ConnectionMeta // We can use sender from here and receivers just as a handy metadata - ReceiverPortAddr PortAddr // So what we really need is sender and receiver port addrs -} - -func (e EventMessagePending) String() string { - return fmt.Sprintf("%v -> %v", e.Meta.SenderPortAddr, e.ReceiverPortAddr) -} - -type EventMessageReceived struct { - Meta ConnectionMeta // Same as with pending event - ReceiverPortAddr PortAddr -} - -func (e EventMessageReceived) String() string { - return fmt.Sprintf("%v -> %v", e.Meta.SenderPortAddr, e.ReceiverPortAddr) -} - -type EventType uint8 - -const ( - MessageSentEvent EventType = 1 // Message is sent from sender to its receivers - MessagePendingEvent EventType = 2 // Message has reached receiver but not yet passed inside - MessageReceivedEvent EventType = 3 // Message is passed inside receiver -) - -func (e EventType) String() string { - switch e { - case MessageSentEvent: - return "sent" - case MessagePendingEvent: - return "pending" - case MessageReceivedEvent: - return "received" - } - panic("unknown_event_type") -} - -type EventListener interface { - Send(event Event, msg Msg) Msg -} diff --git a/internal/runtime/func_runner.go b/internal/runtime/func_runner.go deleted file mode 100644 index fde8b42c..00000000 --- a/internal/runtime/func_runner.go +++ /dev/null @@ -1,54 +0,0 @@ -package runtime - -import ( - "context" - "fmt" - "sync" -) - -type FuncRunner struct { - registry map[string]FuncCreator -} - -type FuncCreator interface { - // Create method validates the input and builds ready to use function - Create(funcIO FuncIO, msg Msg) (func(context.Context), error) -} - -func (d FuncRunner) Run(funcCalls []FuncCall) (func(ctx context.Context), error) { - funcs := make([]func(context.Context), len(funcCalls)) - - for i, call := range funcCalls { - creator, ok := d.registry[call.Ref] - if !ok { - return nil, fmt.Errorf("func creator not found: %v", call.Ref) - } - - handler, err := creator.Create(call.IO, call.ConfigMsg) - if err != nil { - return nil, fmt.Errorf("create: %w: %v", err, call.Ref) - } - - funcs[i] = handler - } - - return func(ctx context.Context) { - wg := sync.WaitGroup{} - wg.Add(len(funcs)) - for i := range funcs { - routine := funcs[i] - go func() { - routine(ctx) - wg.Done() - }() - } - wg.Wait() - }, nil -} - -func MustNewFuncRunner(registry map[string]FuncCreator) FuncRunner { - if registry == nil { - panic(ErrNilDeps) - } - return FuncRunner{registry: registry} -} diff --git a/internal/runtime/funcrunner.go b/internal/runtime/funcrunner.go new file mode 100644 index 00000000..e5a1ab99 --- /dev/null +++ b/internal/runtime/funcrunner.go @@ -0,0 +1,63 @@ +package runtime + +import ( + "context" + "fmt" + "sync" +) + +type FuncRunner struct { + registry map[string]FuncCreator +} + +type FuncCreator interface { + Create(FuncIO, Msg) (func(context.Context), error) +} + +// Run returns a function that runs all runtime functions with their configurations. +// Each runtime function runs in a goroutine, but returned function blocks until all functions finish. +// You can cancel the context that you pass to returned function, to finish all runtime functions. +// It returns error if any of the runtime functions fails to start because of invalid configuration. +func (d FuncRunner) Run(funcCalls []FuncCall) (func(ctx context.Context), error) { + handlers, err := d.createHandlers(funcCalls) + if err != nil { + return nil, err + } + + return func(ctx context.Context) { + wg := sync.WaitGroup{} + wg.Add(len(handlers)) + for i := range handlers { + routine := handlers[i] + go func() { + routine(ctx) + wg.Done() + }() + } + wg.Wait() + }, nil +} + +func (d FuncRunner) createHandlers(funcCalls []FuncCall) ([]func(context.Context), error) { + funcs := make([]func(context.Context), len(funcCalls)) + + for i, call := range funcCalls { + creator, ok := d.registry[call.Ref] + if !ok { + return nil, fmt.Errorf("func creator not found: %v", call.Ref) + } + + handler, err := creator.Create(call.IO, call.ConfigMsg) + if err != nil { + return nil, fmt.Errorf("%v: %w", call.Ref, err) + } + + funcs[i] = handler + } + + return funcs, nil +} + +func NewFuncRunner(registry map[string]FuncCreator) FuncRunner { + return FuncRunner{registry: registry} +} diff --git a/internal/runtime/funcs/and.go b/internal/runtime/funcs/and.go index b337c0ed..d5a6b29c 100644 --- a/internal/runtime/funcs/and.go +++ b/internal/runtime/funcs/and.go @@ -2,51 +2,46 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type and struct{} func (p and) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - AIn, err := io.In.Port("A") + aIn, err := io.In.Single("a") if err != nil { return nil, err } - BIn, err := io.In.Port("B") + + bIn, err := io.In.Single("b") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } + // TODO send false as soon as A in is false, but do it correctly return func(ctx context.Context) { - var ( - AVAL runtime.Msg - BVAL runtime.Msg - ) - for { - select { - case <-ctx.Done(): + aMsg, ok := aIn.Receive(ctx) + if !ok { return - case AVAL = <-AIn: } - select { - case <-ctx.Done(): + bMsg, ok := bIn.Receive(ctx) + if !ok { return - case BVAL = <-BIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg(aMsg.Bool() && bMsg.Bool()), + ) { return - - default: - resOut <- runtime.NewBoolMsg(BVAL.Bool() && AVAL.Bool()) } } }, nil diff --git a/internal/runtime/funcs/args.go b/internal/runtime/funcs/args.go index 90f30736..72aedeee 100644 --- a/internal/runtime/funcs/args.go +++ b/internal/runtime/funcs/args.go @@ -2,30 +2,37 @@ package funcs import ( "context" - "github.com/nevalang/neva/internal/runtime" "os" + + "github.com/nevalang/neva/internal/runtime" ) type args struct{} func (a args) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - outport, err := io.Out.Port("data") + sigIn, err := io.In.Single("sig") if err != nil { return nil, err } + dataOut, err := io.Out.Single("data") + if err != nil { + return nil, err + } + + // TODO concider replacing with stream return func(ctx context.Context) { - var lst_args []runtime.Msg - for i := 0; i < len(os.Args); i++ { - lst_args = append(lst_args, runtime.NewStrMsg(os.Args[i])) + if _, ok := sigIn.Receive(ctx); !ok { + return + } + + result := make([]runtime.Msg, len(os.Args)) + for i := range os.Args { + result = append(result, runtime.NewStrMsg(os.Args[i])) } - for { - select { - case <-ctx.Done(): - return - case outport <- runtime.NewListMsg(lst_args...): - } + if !dataOut.Send(ctx, runtime.NewListMsg(result)) { + return } }, nil } diff --git a/internal/runtime/funcs/del.go b/internal/runtime/funcs/del.go index fe475432..af718cd3 100644 --- a/internal/runtime/funcs/del.go +++ b/internal/runtime/funcs/del.go @@ -9,17 +9,15 @@ import ( type del struct{} func (d del) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - outport, err := io.In.Port("msg") + dataIn, err := io.In.Single("msg") // TODO rename to data? if err != nil { return nil, err } return func(ctx context.Context) { for { - select { - case <-ctx.Done(): + if _, ok := dataIn.Receive(ctx); !ok { return - case <-outport: } } }, nil diff --git a/internal/runtime/funcs/eq.go b/internal/runtime/funcs/eq.go index 2336b3e4..89898da6 100644 --- a/internal/runtime/funcs/eq.go +++ b/internal/runtime/funcs/eq.go @@ -2,49 +2,45 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type eq struct{} func (p eq) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - expectIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + val1, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + val2, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-expectIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg(val1 == val2), + ) { return - case resOut <- runtime.NewBoolMsg(val1 == val2): } } }, nil diff --git a/internal/runtime/funcs/fan_out.go b/internal/runtime/funcs/fan_out.go new file mode 100644 index 00000000..47334647 --- /dev/null +++ b/internal/runtime/funcs/fan_out.go @@ -0,0 +1,34 @@ +package funcs + +import ( + "context" + + "github.com/nevalang/neva/internal/runtime" +) + +type fanOut struct{} + +func (d fanOut) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { + dataIn, err := io.In.Single("data") + if err != nil { + return nil, err + } + + dataOut, err := io.Out.Array("data") + if err != nil { + return nil, err + } + + return func(ctx context.Context) { + for { + dataMsg, ok := dataIn.Receive(ctx) + if !ok { + return + } + + if !dataOut.SendAll(ctx, dataMsg) { + return + } + } + }, nil +} diff --git a/internal/runtime/funcs/field.go b/internal/runtime/funcs/field.go index 9955d2f3..06330bb5 100644 --- a/internal/runtime/funcs/field.go +++ b/internal/runtime/funcs/field.go @@ -9,47 +9,45 @@ import ( type readStructField struct{} -func (s readStructField) Create(io runtime.FuncIO, fieldPathMsg runtime.Msg) (func(ctx context.Context), error) { - fieldPath := fieldPathMsg.List() - if len(fieldPath) == 0 { +func (s readStructField) Create(io runtime.FuncIO, cfg runtime.Msg) (func(ctx context.Context), error) { + path := cfg.List() + if len(path) == 0 { return nil, errors.New("field path cannot be empty") } - pathStrings := make([]string, 0, len(fieldPath)) - for _, el := range fieldPath { + pathStrings := make([]string, 0, len(path)) + for _, el := range path { pathStrings = append(pathStrings, el.Str()) } - msgIn, err := io.In.Port("msg") + msgIn, err := io.In.Single("msg") if err != nil { return nil, err } - msgOut, err := io.Out.Port("msg") + msgOut, err := io.Out.Single("msg") if err != nil { return nil, err } return func(ctx context.Context) { - var msg runtime.Msg - for { - select { - case <-ctx.Done(): + msg, ok := msgIn.Receive(ctx) + if !ok { return - case msg = <-msgIn: } - select { - case <-ctx.Done(): + if !msgOut.Send( + ctx, + s.recursive(msg, pathStrings), + ) { return - case msgOut <- s.mapLookup(msg, pathStrings): } } }, nil } -func (readStructField) mapLookup(m runtime.Msg, path []string) runtime.Msg { +func (readStructField) recursive(m runtime.Msg, path []string) runtime.Msg { for len(path) > 0 { m = m.Map()[path[0]] path = path[1:] diff --git a/internal/runtime/funcs/float_div.go b/internal/runtime/funcs/float_div.go index 609e2449..bc059789 100644 --- a/internal/runtime/funcs/float_div.go +++ b/internal/runtime/funcs/float_div.go @@ -2,65 +2,59 @@ package funcs import ( "context" - "errors" "github.com/nevalang/neva/internal/runtime" ) -var errFloatDivideByZero = errors.New("float divide by zero") - type floatDiv struct{} func (floatDiv) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - xIn, err := io.In.Port("x") + xIn, err := io.In.Single("x") if err != nil { return nil, err } - yIn, err := io.In.Port("y") + yIn, err := io.In.Single("y") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var x, y float64 - select { - case <-ctx.Done(): + xMsg, ok := xIn.Receive(ctx) + if !ok { return - case msg := <-xIn: - x = msg.Float() } - select { - case <-ctx.Done(): + + yMsg, ok := yIn.Receive(ctx) + if !ok { return - case msg := <-yIn: - y = msg.Float() } - if y == 0 { - select { - case <-ctx.Done(): + + if yMsg.Float() == 0 { + if !errOut.Send(ctx, errFromString("divide by zero")) { return - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg(errFloatDivideByZero.Error()), - }): - continue } + continue } - select { - case <-ctx.Done(): + + if !resOut.Send( + ctx, + runtime.NewFloatMsg( + xMsg.Float()/yMsg.Float(), + ), + ) { return - case resOut <- runtime.NewFloatMsg(x / y): } } }, nil diff --git a/internal/runtime/funcs/float_is_greater.go b/internal/runtime/funcs/float_is_greater.go index e0d4c08e..6df82202 100644 --- a/internal/runtime/funcs/float_is_greater.go +++ b/internal/runtime/funcs/float_is_greater.go @@ -2,49 +2,45 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type floatIsGreater struct{} func (p floatIsGreater) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + val1, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + val2, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg(val1.Float() > val2.Float()), + ) { return - case resOut <- runtime.NewBoolMsg(val1.Float() > val2.Float()): } } }, nil diff --git a/internal/runtime/funcs/float_is_lesser.go b/internal/runtime/funcs/float_is_lesser.go index a9968320..0a6844e7 100644 --- a/internal/runtime/funcs/float_is_lesser.go +++ b/internal/runtime/funcs/float_is_lesser.go @@ -2,49 +2,47 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type floatIsLesser struct{} func (p floatIsLesser) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + val1, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + val2, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg( + val1.Float() < val2.Float(), + ), + ) { return - case resOut <- runtime.NewBoolMsg(val1.Int() < val2.Int()): } } }, nil diff --git a/internal/runtime/funcs/http.go b/internal/runtime/funcs/http.go index 2329361e..194a9f0b 100644 --- a/internal/runtime/funcs/http.go +++ b/internal/runtime/funcs/http.go @@ -2,7 +2,7 @@ package funcs import ( "context" - goio "io" + "io" "net/http" "github.com/nevalang/neva/internal/runtime" @@ -10,61 +10,58 @@ import ( type httpGet struct{} -func (httpGet) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - urlIn, err := io.In.Port("url") +func (httpGet) Create(funcIO runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { + urlIn, err := funcIO.In.Single("url") if err != nil { return nil, err } - respOut, err := io.Out.Port("resp") + respOut, err := funcIO.Out.Single("resp") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := funcIO.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var u string - select { - case m := <-urlIn: - u = m.Str() - case <-ctx.Done(): + urlMsg, ok := urlIn.Receive(ctx) + if !ok { return } - resp, err := http.Get(u) + + resp, err := http.Get(urlMsg.Str()) if err != nil { - select { - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg(err.Error()), - }): - continue - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return } + continue } - body, err := goio.ReadAll(resp.Body) + + body, err := io.ReadAll(resp.Body) if err != nil { - select { - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg(err.Error()), - }): - continue - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return } + continue } - select { - case respOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "statusCode": runtime.NewIntMsg(int64(resp.StatusCode)), - "body": runtime.NewStrMsg(string(body)), - }): - case <-ctx.Done(): + + if !respOut.Send( + ctx, + respMsg(resp.StatusCode, body), + ) { return } } }, nil } + +func respMsg(statusCode int, body []byte) runtime.MapMsg { + return runtime.NewMapMsg(map[string]runtime.Msg{ + "body": runtime.NewStrMsg(string(body)), + "statusCode": runtime.NewIntMsg(int64(statusCode)), + }) +} diff --git a/internal/runtime/funcs/if.go b/internal/runtime/funcs/if.go index 44e796e3..3694d6eb 100644 --- a/internal/runtime/funcs/if.go +++ b/internal/runtime/funcs/if.go @@ -2,49 +2,44 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type if_ struct{} func (p if_) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - okOut, err := io.Out.Port("then") + thenOut, err := io.Out.Single("then") if err != nil { return nil, err } - elseOut, err := io.Out.Port("else") + elseOut, err := io.Out.Single("else") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case val1 = <-dataIn: } - select { - case <-ctx.Done(): - return + var out runtime.SingleOutport + if dataMsg.Bool() { + out = thenOut + } else { + out = elseOut + } - default: - if val1.Bool() { - okOut <- val1 - } else { - elseOut <- val1 - } + if !out.Send(ctx, dataMsg) { + return } } }, nil diff --git a/internal/runtime/funcs/image.go b/internal/runtime/funcs/image.go index 79875fc8..3b8079b0 100644 --- a/internal/runtime/funcs/image.go +++ b/internal/runtime/funcs/image.go @@ -7,6 +7,7 @@ import ( "github.com/nevalang/neva/internal/runtime" ) +// TODO can't we use uint8 here? type rgbaMsg struct { r int64 g int64 @@ -21,6 +22,7 @@ func (c *rgbaMsg) decode(msg runtime.Msg) { c.b = m["b"].Int() c.a = m["a"].Int() } + func (c rgbaMsg) color() color.Color { return color.RGBA64{R: uint16(c.r), G: uint16(c.g), B: uint16(c.b), A: uint16(c.a)} } @@ -45,13 +47,14 @@ type imageMsg struct { } func (i *imageMsg) reset() { *i = imageMsg{} } -func (i *imageMsg) decode(msg runtime.Msg) { + +func (i *imageMsg) decode(msg map[string]runtime.Msg) { i.reset() - m := msg.Map() - i.pixels = m["pixels"].Str() - i.width = m["width"].Int() - i.height = m["height"].Int() + i.pixels = msg["pixels"].Str() + i.width = msg["width"].Int() + i.height = msg["height"].Int() } + func (i imageMsg) encode() runtime.Msg { return runtime.NewMapMsg(map[string]runtime.Msg{ "pixels": runtime.NewStrMsg(i.pixels), @@ -59,6 +62,7 @@ func (i imageMsg) encode() runtime.Msg { "height": runtime.NewIntMsg(i.height), }) } + func (i imageMsg) createImage() image.Image { // Use pixels directly if available. pix := []uint8(i.pixels) @@ -76,6 +80,7 @@ func (i imageMsg) createImage() image.Image { } return im } + func (i *imageMsg) decodeImage(img *image.RGBA) { i.pixels = string(img.Pix) i.width = int64(img.Rect.Dx()) diff --git a/internal/runtime/funcs/image_encode.go b/internal/runtime/funcs/image_encode.go index 83812aae..d4e1a3a2 100644 --- a/internal/runtime/funcs/image_encode.go +++ b/internal/runtime/funcs/image_encode.go @@ -11,48 +11,45 @@ import ( type imageEncode struct{} func (imageEncode) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - in, err := io.In.Port("img") + imgIn, err := io.In.Single("img") if err != nil { return nil, err } - data, err := io.Out.Port("data") + dataOut, err := io.Out.Single("data") if err != nil { return nil, err } - errCh, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var b imageMsg - select { - case m := <-in: - b.decode(m) - case <-ctx.Done(): + imgMsg, ok := imgIn.Receive(ctx) + if !ok { return } + + var b imageMsg + b.decode(imgMsg.Map()) + im := b.createImage() + // Encode the image in the desired format to sb. var sb strings.Builder // for encoded output. if err := png.Encode(&sb, im); err != nil { - // Something went wrong. Send err. - select { - case errCh <- runtime.NewMapMsg(map[string]runtime.Msg{ - "error": runtime.NewStrMsg(err.Error()), - }): - continue - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return } } - // Send the image. - select { - case data <- runtime.NewStrMsg(sb.String()): - case <-ctx.Done(): + + if !dataOut.Send( + ctx, + runtime.NewStrMsg(sb.String()), + ) { return } } diff --git a/internal/runtime/funcs/image_new.go b/internal/runtime/funcs/image_new.go index 6ee51299..d58ae004 100644 --- a/internal/runtime/funcs/image_new.go +++ b/internal/runtime/funcs/image_new.go @@ -10,17 +10,17 @@ import ( type imageNew struct{} func (imageNew) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - pixelsIn, err := io.In.Port("pixels") + pixelsIn, err := io.In.Single("pixels") if err != nil { return nil, err } - imgOut, err := io.Out.Port("img") + imgOut, err := io.Out.Single("img") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } @@ -34,42 +34,38 @@ func (imageNew) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Conte ) stream: for { - select { - case m := <-pixelsIn: - var pix pixelStreamMsg - pix.decode(m) - if pix.x < 0 || pix.y < 0 { - select { - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg("image.New: Pixel out of bounds"), - }): - case <-ctx.Done(): - return - } - } - if pix.x >= width { - width = pix.x + 1 - } - if pix.y >= height { - height = pix.y + 1 - } - im[pix.pixelMsg] = struct{}{} - if pix.last { - break stream - } - case <-ctx.Done(): + m, ok := pixelsIn.Receive(ctx) + if !ok { return } + + var pix pixelStreamMsg + pix.decode(m) + if pix.x < 0 || pix.y < 0 { + if !errOut.Send(ctx, errFromString("image.New: Pixel out of bounds")) { + return + } + } + if pix.x >= width { + width = pix.x + 1 + } + if pix.y >= height { + height = pix.y + 1 + } + im[pix.pixelMsg] = struct{}{} + if pix.last { + break stream + } } + img := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) for pix := range im { img.Set(int(pix.x), int(pix.y), pix.color.color()) } + var i imageMsg i.decodeImage(img) - select { - case imgOut <- i.encode(): - case <-ctx.Done(): + if !imgOut.Send(ctx, i.encode()) { return } } diff --git a/internal/runtime/funcs/int_add.go b/internal/runtime/funcs/int_add.go index fedd0118..e834b8a9 100644 --- a/internal/runtime/funcs/int_add.go +++ b/internal/runtime/funcs/int_add.go @@ -12,12 +12,12 @@ func (intAdd) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - seqIn, err := io.In.Port("seq") + seqIn, err := io.In.Single("seq") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } @@ -26,25 +26,24 @@ func (intAdd) Create( var acc int64 = 0 for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + msg, ok := seqIn.Receive(ctx) + if !ok { return - case msg := <-seqIn: - item = msg.Map() } + item := msg.Map() + acc += item["data"].Int() - if item["last"].Bool() { - select { - case <-ctx.Done(): - return - case resOut <- runtime.NewIntMsg(acc): - acc = 0 // reset - continue - } + if !item["last"].Bool() { + continue } + + if !resOut.Send(ctx, runtime.NewIntMsg(acc)) { + return + } + + acc = 0 } }, nil } diff --git a/internal/runtime/funcs/int_casemod.go b/internal/runtime/funcs/int_casemod.go index f2c661e8..9aa4556b 100644 --- a/internal/runtime/funcs/int_casemod.go +++ b/internal/runtime/funcs/int_casemod.go @@ -10,72 +10,57 @@ import ( type intCaseMod struct{} func (intCaseMod) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - caseIn, ok := io.In["case"] - if !ok { - return nil, errors.New("port 'case' is required") + caseIn, err := io.In.Array("case") + if err != nil { + return nil, err } - caseOut, ok := io.Out["case"] - if !ok { - return nil, errors.New("port 'then' is required") + caseOut, err := io.Out.Array("case") + if err != nil { + return nil, err } - elseOut, err := io.Out.Port("else") + elseOut, err := io.Out.Single("else") if err != nil { return nil, err } - if len(caseIn) != len(caseOut) { - return nil, errors.New("number of 'case' inports must match number of 'then' outports") + if caseIn.Len() != caseOut.Len() { + return nil, errors.New("number of 'case' inports must match number of 'case' outports") } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: - } - - cases := make([]runtime.Msg, len(caseIn)) - for i, slot := range caseIn { - select { - case <-ctx.Done(): - return - case caseMsg := <-slot: - cases[i] = caseMsg - } } matchIdx := -1 dataInt := data.Int() - for i, caseMsg := range cases { - if dataInt%caseMsg.Int() == 0 { - matchIdx = i - break + + if !caseIn.Receive(ctx, func(idx int, msg runtime.Msg) bool { + if matchIdx == -1 && dataInt%msg.Int() == 0 { + matchIdx = idx } + return true + }) { + return } if matchIdx != -1 { - select { - case <-ctx.Done(): + if !caseOut.Send(ctx, uint8(matchIdx), data) { + return + } + } else { + if !elseOut.Send(ctx, data) { return - case caseOut[matchIdx] <- data: - continue } - } - - select { - case <-ctx.Done(): - return - case elseOut <- data: } } }, nil diff --git a/internal/runtime/funcs/int_decr.go b/internal/runtime/funcs/int_decr.go index 1d09cb69..77b4cbdf 100644 --- a/internal/runtime/funcs/int_decr.go +++ b/internal/runtime/funcs/int_decr.go @@ -9,30 +9,25 @@ import ( type intDecr struct{} func (i intDecr) Create(io runtime.FuncIO, _ runtime.Msg) (func(context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var dataMsg runtime.Msg - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case dataMsg = <-dataIn: } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewIntMsg(dataMsg.Int()-1)) { return - case resOut <- runtime.NewIntMsg(dataMsg.Int() - 1): } } }, nil diff --git a/internal/runtime/funcs/int_div.go b/internal/runtime/funcs/int_div.go index 569fbdda..baa135af 100644 --- a/internal/runtime/funcs/int_div.go +++ b/internal/runtime/funcs/int_div.go @@ -9,55 +9,52 @@ import ( type intDiv struct{} func (intDiv) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - xIn, err := io.In.Port("x") + xIn, err := io.In.Single("x") if err != nil { return nil, err } - yIn, err := io.In.Port("y") + yIn, err := io.In.Single("y") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var x, y int64 - select { - case <-ctx.Done(): + xMsg, ok := xIn.Receive(ctx) + if !ok { return - case msg := <-xIn: - x = msg.Int() } - select { - case <-ctx.Done(): + + yMsg, ok := yIn.Receive(ctx) + if !ok { return - case msg := <-yIn: - y = msg.Int() } - if y == 0 { - select { - case <-ctx.Done(): + + if yMsg.Int() == 0 { + if !errOut.Send(ctx, errFromString("divide by zero")) { return - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg(errIntegerDivideByZero.Error()), - }): - continue } + continue } - select { - case <-ctx.Done(): + + if !resOut.Send( + ctx, + runtime.NewIntMsg( + xMsg.Int()/yMsg.Int(), + ), + ) { return - case resOut <- runtime.NewIntMsg(x / y): } } }, nil diff --git a/internal/runtime/funcs/int_is_even.go b/internal/runtime/funcs/int_is_even.go deleted file mode 100644 index 207434d0..00000000 --- a/internal/runtime/funcs/int_is_even.go +++ /dev/null @@ -1,38 +0,0 @@ -package funcs - -import ( - "context" - "github.com/nevalang/neva/internal/runtime" -) - -type intIsEven struct{} - -func (p intIsEven) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") - if err != nil { - return nil, err - } - resOut, err := io.Out.Port("res") - if err != nil { - return nil, err - } - - return func(ctx context.Context) { - var val runtime.Msg - - for { - - select { - case <-ctx.Done(): - return - case val = <-dataIn: - } - - select { - case <-ctx.Done(): - return - case resOut <- runtime.NewBoolMsg(val.Int()%2 == 0): - } - } - }, nil -} diff --git a/internal/runtime/funcs/int_is_greater.go b/internal/runtime/funcs/int_is_greater.go index 29375ed1..4afa9413 100644 --- a/internal/runtime/funcs/int_is_greater.go +++ b/internal/runtime/funcs/int_is_greater.go @@ -2,49 +2,42 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type intIsGreater struct{} func (p intIsGreater) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + actualMsg, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + comparedMsg, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewBoolMsg(actualMsg.Int() > comparedMsg.Int())) { return - case resOut <- runtime.NewBoolMsg(val1.Int() > val2.Int()): } } }, nil diff --git a/internal/runtime/funcs/int_is_lesser.go b/internal/runtime/funcs/int_is_lesser.go index c127bdde..7c253dd2 100644 --- a/internal/runtime/funcs/int_is_lesser.go +++ b/internal/runtime/funcs/int_is_lesser.go @@ -2,49 +2,42 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type intIsLesser struct{} func (p intIsLesser) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + actualMsg, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + comparedMsg, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewBoolMsg(actualMsg.Int() < comparedMsg.Int())) { return - case resOut <- runtime.NewBoolMsg(val1.Int() < val2.Int()): } } }, nil diff --git a/internal/runtime/funcs/int_mod.go b/internal/runtime/funcs/int_mod.go index ae445f38..3834c934 100644 --- a/internal/runtime/funcs/int_mod.go +++ b/internal/runtime/funcs/int_mod.go @@ -2,65 +2,59 @@ package funcs import ( "context" - "errors" "github.com/nevalang/neva/internal/runtime" ) -var errIntegerDivideByZero = errors.New("integer divide by zero") - type intMod struct{} func (intMod) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - xIn, err := io.In.Port("x") + xIn, err := io.In.Single("x") if err != nil { return nil, err } - yIn, err := io.In.Port("y") + yIn, err := io.In.Single("y") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var x, y int64 - select { - case <-ctx.Done(): + xMsg, ok := xIn.Receive(ctx) + if !ok { return - case msg := <-xIn: - x = msg.Int() } - select { - case <-ctx.Done(): + + yMsg, ok := yIn.Receive(ctx) + if !ok { return - case msg := <-yIn: - y = msg.Int() } - if y == 0 { - select { - case <-ctx.Done(): + + if yMsg.Int() == 0 { + if !errOut.Send(ctx, errFromString("divide by zero")) { return - case errOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "text": runtime.NewStrMsg(errIntegerDivideByZero.Error()), - }): - continue } + continue } - select { - case <-ctx.Done(): + + if !resOut.Send( + ctx, + runtime.NewIntMsg( + xMsg.Int()%yMsg.Int(), + ), + ) { return - case resOut <- runtime.NewIntMsg(x % y): } } }, nil diff --git a/internal/runtime/funcs/int_mul.go b/internal/runtime/funcs/int_mul.go index ee6e32fd..9e3b4bf5 100644 --- a/internal/runtime/funcs/int_mul.go +++ b/internal/runtime/funcs/int_mul.go @@ -9,12 +9,12 @@ import ( type intMul struct{} func (intMul) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - seqIn, err := io.In.Port("seq") + seqIn, err := io.In.Single("seq") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } @@ -23,25 +23,23 @@ func (intMul) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context var acc int64 = 1 for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := seqIn.Receive(ctx) + if !ok { return - case msg := <-seqIn: - item = msg.Map() } + item := seqMsg.Map() acc *= item["data"].Int() - if item["last"].Bool() { - select { - case <-ctx.Done(): - return - case resOut <- runtime.NewIntMsg(acc): - acc = 1 // reset - continue - } + if !item["last"].Bool() { + continue } + + if !resOut.Send(ctx, runtime.NewIntMsg(acc)) { + return + } + + acc = 1 } }, nil } diff --git a/internal/runtime/funcs/int_parse.go b/internal/runtime/funcs/int_parse.go index 9137bb4c..7b045914 100644 --- a/internal/runtime/funcs/int_parse.go +++ b/internal/runtime/funcs/int_parse.go @@ -12,45 +12,38 @@ import ( type parseInt struct{} func (p parseInt) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { - var str runtime.Msg - for { - select { - case <-ctx.Done(): + str, ok := dataIn.Receive(ctx) + if !ok { return - case str = <-dataIn: } parsedNum, err := parse(str.Str()) if err != nil { - select { - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return - case errOut <- runtime.NewStrMsg(err.Error()): } continue } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, parsedNum) { return - case resOut <- parsedNum: } } }, nil diff --git a/internal/runtime/funcs/int_sub.go b/internal/runtime/funcs/int_sub.go index a4a13598..b2f0457d 100644 --- a/internal/runtime/funcs/int_sub.go +++ b/internal/runtime/funcs/int_sub.go @@ -9,12 +9,12 @@ import ( type intSub struct{} func (intSub) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - seqIn, err := io.In.Port("seq") + seqIn, err := io.In.Single("seq") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } @@ -26,14 +26,13 @@ func (intSub) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context ) for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := seqIn.Receive(ctx) + if !ok { return - case msg := <-seqIn: - item = msg.Map() } + item := seqMsg.Map() + if !started { acc = item["data"].Int() started = true @@ -42,14 +41,11 @@ func (intSub) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context } if item["last"].Bool() { - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewIntMsg(acc)) { return - case resOut <- runtime.NewIntMsg(acc): - acc = 0 - started = false - continue } + acc = 0 + started = false } } }, nil diff --git a/internal/runtime/funcs/list_at.go b/internal/runtime/funcs/list_at.go index a77cad65..c9b3e495 100644 --- a/internal/runtime/funcs/list_at.go +++ b/internal/runtime/funcs/list_at.go @@ -2,75 +2,64 @@ package funcs import ( "context" - "errors" "github.com/nevalang/neva/internal/runtime" ) -var errListIndexOutOfBounds = errors.New("list index out of bounds") - type listAt struct{} func (listAt) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - idxIn, err := io.In.Port("idx") + idxIn, err := io.In.Single("idx") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var data []runtime.Msg - var idx int64 - - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case msg := <-dataIn: - data = msg.List() } - select { - case <-ctx.Done(): + idxMsg, ok := idxIn.Receive(ctx) + if !ok { return - case msg := <-idxIn: - idx = msg.Int() + } + + idx := idxMsg.Int() + data := dataMsg.List() + + l := int64(len(data)) + if idx < -l || idx >= l { + if !errOut.Send(ctx, errFromString("index out of bounds")) { + return + } } if idx < 0 { - // Support negative indexing: + // support negative indexing: // $l = [1, 2, 3] // $l[-1] // 3 idx += int64(len(data)) } - if idx < 0 || idx >= int64(len(data)) { - select { - case <-ctx.Done(): - return - case errOut <- errorFromString(errListIndexOutOfBounds.Error()): - continue - } - } - - select { - case <-ctx.Done(): + if !resOut.Send(ctx, data[idx]) { return - case resOut <- data[idx]: } } }, nil diff --git a/internal/runtime/funcs/list_len.go b/internal/runtime/funcs/list_len.go index 8846333a..92f4183b 100644 --- a/internal/runtime/funcs/list_len.go +++ b/internal/runtime/funcs/list_len.go @@ -9,32 +9,27 @@ import ( type listlen struct{} func (p listlen) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } - l := len(data.List()) + l := len(dataMsg.List()) - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewIntMsg(int64(l))) { return - case resOut <- runtime.NewIntMsg(int64(l)): } } }, nil diff --git a/internal/runtime/funcs/list_push.go b/internal/runtime/funcs/list_push.go index b8a3eb13..75de4711 100644 --- a/internal/runtime/funcs/list_push.go +++ b/internal/runtime/funcs/list_push.go @@ -10,46 +10,41 @@ import ( type listPush struct{} func (p listPush) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - lstIn, err := io.In.Port("lst") + lstIn, err := io.In.Single("lst") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - data runtime.Msg - lst runtime.Msg - ) - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } - select { - case <-ctx.Done(): + lstMsg, ok := lstIn.Receive(ctx) + if !ok { return - case lst = <-lstIn: } - lstCopy := slices.Clone(lst.List()) - lstCopy = append(lstCopy, data) + lstCopy := slices.Clone(lstMsg.List()) - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewListMsg( + append(lstCopy, dataMsg), + ), + ) { return - case resOut <- runtime.NewListMsg(lstCopy...): } } }, nil diff --git a/internal/runtime/funcs/list_sort_float.go b/internal/runtime/funcs/list_sort_float.go index 154ca506..baf01a2b 100644 --- a/internal/runtime/funcs/list_sort_float.go +++ b/internal/runtime/funcs/list_sort_float.go @@ -10,24 +10,21 @@ import ( type listSortFloat struct{} func (p listSortFloat) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } clone := slices.Clone(data.List()) @@ -35,10 +32,8 @@ func (p listSortFloat) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx contex return i.Float() < j.Float() }) - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewListMsg(clone)) { return - case resOut <- runtime.NewListMsg(clone...): } } }, nil diff --git a/internal/runtime/funcs/list_sort_int.go b/internal/runtime/funcs/list_sort_int.go index e8aa49b9..ec2df533 100644 --- a/internal/runtime/funcs/list_sort_int.go +++ b/internal/runtime/funcs/list_sort_int.go @@ -10,24 +10,21 @@ import ( type listSortInt struct{} func (p listSortInt) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } clone := slices.Clone(data.List()) @@ -35,10 +32,8 @@ func (p listSortInt) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context. return i.Int() < j.Int() }) - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewListMsg(clone)) { return - case resOut <- runtime.NewListMsg(clone...): } } }, nil diff --git a/internal/runtime/funcs/list_sort_string.go b/internal/runtime/funcs/list_sort_string.go index acf37c25..a59eb7f7 100644 --- a/internal/runtime/funcs/list_sort_string.go +++ b/internal/runtime/funcs/list_sort_string.go @@ -10,24 +10,21 @@ import ( type listSortString struct{} func (p listSortString) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } clone := slices.Clone(data.List()) @@ -35,10 +32,8 @@ func (p listSortString) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx conte return i.Str() < j.Str() }) - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewListMsg(clone)) { return - case resOut <- runtime.NewListMsg(clone...): } } }, nil diff --git a/internal/runtime/funcs/list_to_stream.go b/internal/runtime/funcs/list_to_stream.go index 771526cd..3d4c618c 100644 --- a/internal/runtime/funcs/list_to_stream.go +++ b/internal/runtime/funcs/list_to_stream.go @@ -12,27 +12,25 @@ func (c listToStream) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - seqOut, err := io.Out.Port("seq") + seqOut, err := io.Out.Single("seq") if err != nil { return nil, err } return func(ctx context.Context) { for { - var list []runtime.Msg - - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case dataMsg := <-dataIn: - list = dataMsg.List() } + list := data.List() + for idx := 0; idx < len(list); idx++ { item := streamItem( list[idx], @@ -40,10 +38,8 @@ func (c listToStream) Create( idx == len(list)-1, ) - select { - case <-ctx.Done(): + if !seqOut.Send(ctx, item) { return - case seqOut <- item: } } } diff --git a/internal/runtime/funcs/lock.go b/internal/runtime/funcs/lock.go index 7d32c35b..5ceddee0 100644 --- a/internal/runtime/funcs/lock.go +++ b/internal/runtime/funcs/lock.go @@ -9,50 +9,35 @@ import ( type lock struct{} func (l lock) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - sigIn, err := io.In.Port("sig") + sigIn, err := io.In.Single("sig") if err != nil { return nil, err } - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - dataOut, err := io.Out.Port("data") + dataOut, err := io.Out.Single("data") if err != nil { return nil, err } - return l.Handle(sigIn, dataIn, dataOut), nil -} - -func (lock) Handle( - sigIn, - dataIn, - dataOut chan runtime.Msg, -) func(ctx context.Context) { return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + if _, ok := sigIn.Receive(ctx); !ok { return - case <-sigIn: } - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } - select { - case <-ctx.Done(): + if !dataOut.Send(ctx, data) { return - case dataOut <- data: } } - } + }, nil } diff --git a/internal/runtime/funcs/match.go b/internal/runtime/funcs/match.go index dc676f8b..a78c9f58 100644 --- a/internal/runtime/funcs/match.go +++ b/internal/runtime/funcs/match.go @@ -10,71 +10,62 @@ import ( type match struct{} func (match) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - caseIn, ok := io.In["case"] - if !ok { - return nil, errors.New("inport 'case' is required") + caseIn, err := io.In.Array("case") + if err != nil { + return nil, err } - caseOut, ok := io.Out["case"] - if !ok { - return nil, errors.New("outport 'case' is required") + caseOut, err := io.Out.Array("case") + if err != nil { + return nil, err } - elseOut, err := io.Out.Port("else") + elseOut, err := io.Out.Single("else") if err != nil { return nil, err } - if len(caseIn) != len(caseOut) { + if caseIn.Len() != caseOut.Len() { return nil, errors.New("number of 'case' inports must match number of 'then' outports") } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } - cases := make([]runtime.Msg, len(caseIn)) - for i, slot := range caseIn { - select { - case <-ctx.Done(): - return - case caseMsg := <-slot: - cases[i] = caseMsg - } + cases := make([]runtime.Msg, caseIn.Len()) + if !caseIn.Receive(ctx, func(idx int, msg runtime.Msg) bool { + cases[idx] = msg + return true + }) { + return } matchIdx := -1 for i, caseMsg := range cases { - if data == caseMsg { + if dataMsg == caseMsg { matchIdx = i break } } if matchIdx != -1 { - select { - case <-ctx.Done(): + if !caseOut.Send(ctx, uint8(matchIdx), dataMsg) { return - case caseOut[matchIdx] <- data: - continue } + continue } - select { - case <-ctx.Done(): + if !elseOut.Send(ctx, dataMsg) { return - case elseOut <- data: } } }, nil diff --git a/internal/runtime/funcs/new.go b/internal/runtime/funcs/new.go index f44584e5..41eb03aa 100644 --- a/internal/runtime/funcs/new.go +++ b/internal/runtime/funcs/new.go @@ -8,18 +8,16 @@ import ( type new struct{} -func (c new) Create(io runtime.FuncIO, msg runtime.Msg) (func(ctx context.Context), error) { - outport, err := io.Out.Port("msg") +func (c new) Create(io runtime.FuncIO, cfg runtime.Msg) (func(ctx context.Context), error) { + outport, err := io.Out.Single("msg") if err != nil { return nil, err } return func(ctx context.Context) { for { - select { - case <-ctx.Done(): + if !outport.Send(ctx, cfg) { return - case outport <- msg: } } }, nil diff --git a/internal/runtime/funcs/not.go b/internal/runtime/funcs/not.go index f731c7a1..b2b679e1 100644 --- a/internal/runtime/funcs/not.go +++ b/internal/runtime/funcs/not.go @@ -2,36 +2,36 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type not struct{} func (p not) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var val runtime.Msg - for { - - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case val = <-dataIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg( + !dataMsg.Bool(), + ), + ) { return - case resOut <- runtime.NewBoolMsg(!val.Bool()): } } }, nil diff --git a/internal/runtime/funcs/or.go b/internal/runtime/funcs/or.go index 10a3fdb0..54208f28 100644 --- a/internal/runtime/funcs/or.go +++ b/internal/runtime/funcs/or.go @@ -2,51 +2,47 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type or struct{} func (p or) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - AIn, err := io.In.Port("A") + aIn, err := io.In.Single("a") if err != nil { return nil, err } - BIn, err := io.In.Port("B") + + bIn, err := io.In.Single("b") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - AVAL runtime.Msg - BVAL runtime.Msg - ) - for { - select { - case <-ctx.Done(): + aMsg, ok := aIn.Receive(ctx) + if !ok { return - case AVAL = <-AIn: } - select { - case <-ctx.Done(): + bMsg, ok := bIn.Receive(ctx) + if !ok { return - case BVAL = <-BIn: } - select { - case <-ctx.Done(): + if !resOut.Send( + ctx, + runtime.NewBoolMsg( + aMsg.Bool() || bMsg.Bool(), + ), + ) { return - - default: - resOut <- runtime.NewBoolMsg(BVAL.Bool() || AVAL.Bool()) } } }, nil diff --git a/internal/runtime/funcs/panic.go b/internal/runtime/funcs/panic.go index 90141d6a..68ddb2a3 100644 --- a/internal/runtime/funcs/panic.go +++ b/internal/runtime/funcs/panic.go @@ -13,18 +13,20 @@ func (p panicker) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - msgIn, err := io.In.Port("msg") + msgIn, err := io.In.Single("msg") if err != nil { return nil, err } + return func(ctx context.Context) { - select { - case <-ctx.Done(): + panicMsg, ok := msgIn.Receive(ctx) + if !ok { return - case panicMsg := <-msgIn: - cancel := ctx.Value("cancel").(context.CancelFunc) - cancel() - fmt.Printf("panic: %v\n", panicMsg) } + + cancel := ctx.Value("cancel").(context.CancelFunc) + cancel() + + fmt.Printf("panic: %v\n", panicMsg) // stderr? }, nil } diff --git a/internal/runtime/funcs/port_streamer.go b/internal/runtime/funcs/port_streamer.go index df9d94ea..bedbccd8 100644 --- a/internal/runtime/funcs/port_streamer.go +++ b/internal/runtime/funcs/port_streamer.go @@ -13,39 +13,28 @@ func (arrayPortToStream) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(context.Context), error) { - portIn, ok := io.In["port"] - if !ok { + portIn, err := io.In.Array("port") + if err != nil { return nil, errors.New("missing array inport 'port'") } - streamOut, err := io.Out.Port("seq") + seqOut, err := io.Out.Single("seq") if err != nil { return nil, err } return func(ctx context.Context) { for { - l := len(portIn) - - for i, slot := range portIn { - var msg runtime.Msg - select { - case <-ctx.Done(): - return - case msg = <-slot: - } - + l := portIn.Len() + if !portIn.Receive(ctx, func(idx int, msg runtime.Msg) bool { item := streamItem( msg, - int64(i), - i == l-1, + int64(idx), + idx == l-1, ) - - select { - case <-ctx.Done(): - return - case streamOut <- item: - } + return seqOut.Send(ctx, item) + }) { + return } } }, nil diff --git a/internal/runtime/funcs/printf.go b/internal/runtime/funcs/printf.go index ab1dde3e..e2c21f00 100644 --- a/internal/runtime/funcs/printf.go +++ b/internal/runtime/funcs/printf.go @@ -12,86 +12,71 @@ import ( type printf struct{} func (p printf) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - tplIn, err := io.In.Port("tpl") + tplIn, err := io.In.Single("tpl") if err != nil { return nil, err } - argsIn, ok := io.In["args"] - if !ok { + argsIn, err := io.In.Array("args") + if err != nil { return nil, fmt.Errorf("missing required input port 'args'") } - argsOut, ok := io.Out["args"] - if !ok { + sigOut, err := io.Out.Single("sig") + if err != nil { return nil, fmt.Errorf("missing required output port 'args'") } - if len(argsIn) != len(argsOut) { - return nil, fmt.Errorf("input and output ports 'args' must have the same length") - } - - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } - return p.handle(tplIn, argsIn, errOut, argsOut) + return p.handle(tplIn, argsIn, errOut, sigOut) } func (printf) handle( - tplIn chan runtime.Msg, - argsIn []chan runtime.Msg, - errOut chan runtime.Msg, - argsOuts []chan runtime.Msg, + tplIn runtime.SingleInport, + argsIn runtime.ArrayInport, + errOut runtime.SingleOutport, + sigOut runtime.SingleOutport, ) (func(ctx context.Context), error) { return func(ctx context.Context) { - var ( - tpl runtime.Msg - args = make([]runtime.Msg, len(argsIn)) - ) - for { - select { - case <-ctx.Done(): + tpl, ok := tplIn.Receive(ctx) + if !ok { return - case tpl = <-tplIn: } - for i, argIn := range argsIn { - select { - case <-ctx.Done(): - return - case arg := <-argIn: - args[i] = arg - } + args := make([]runtime.Msg, argsIn.Len()) + if !argsIn.Receive(ctx, func(idx int, msg runtime.Msg) bool { + args[idx] = msg + return true + }) { + return + } + + if args[0] == nil { + fmt.Println("here") } res, err := format(tpl.Str(), args) if err != nil { - select { - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return - case errOut <- errorFromString(err.Error()): } continue } if _, err := fmt.Print(res); err != nil { - select { - case <-ctx.Done(): + if !errOut.Send(ctx, errFromErr(err)) { return - case errOut <- errorFromString(err.Error()): } continue } - for i, argOut := range argsOuts { - select { - case <-ctx.Done(): - return - case argOut <- args[i]: - } + if !sigOut.Send(ctx, runtime.NewStrMsg(res)) { + return } } }, nil @@ -147,7 +132,10 @@ func format(tpl string, args []runtime.Msg) (string, error) { // Check if all arguments were used if len(usedArgs) != len(args) { - return "", fmt.Errorf("not all arguments were used in the template") + return "", fmt.Errorf( + "not all arguments are used in the template: got %v, used %v", + len(args), len(usedArgs), + ) } return result.String(), nil diff --git a/internal/runtime/funcs/println.go b/internal/runtime/funcs/println.go index a65e524a..01b8b335 100644 --- a/internal/runtime/funcs/println.go +++ b/internal/runtime/funcs/println.go @@ -10,34 +10,29 @@ import ( type println struct{} func (p println) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - sigOut, err := io.Out.Port("sig") + sigOut, err := io.Out.Single("sig") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } if _, err := fmt.Println(data); err != nil { panic(err) } - select { - case <-ctx.Done(): + if !sigOut.Send(ctx, data) { return - case sigOut <- data: } } }, nil diff --git a/internal/runtime/funcs/range.go b/internal/runtime/funcs/range.go index 98ce265e..73df643a 100644 --- a/internal/runtime/funcs/range.go +++ b/internal/runtime/funcs/range.go @@ -12,45 +12,45 @@ func (streamIntRange) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - fromIn, err := io.In.Port("from") + fromIn, err := io.In.Single("from") if err != nil { return nil, err } - toIn, err := io.In.Port("to") + toIn, err := io.In.Single("to") if err != nil { return nil, err } - dataOut, err := io.Out.Port("data") + dataOut, err := io.Out.Single("data") if err != nil { return nil, err } return func(ctx context.Context) { - var fromMsg, toMsg runtime.Msg - for { - select { - case <-ctx.Done(): + fromMsg, ok := fromIn.Receive(ctx) + if !ok { return - case fromMsg = <-fromIn: } - select { - case <-ctx.Done(): + toMsg, ok := toIn.Receive(ctx) + if !ok { return - case toMsg = <-toIn: } + var ( + from = fromMsg.Int() + to = toMsg.Int() + idx int64 = 0 last bool = false - data int64 = fromMsg.Int() + data int64 = from ) - if fromMsg.Int() < toMsg.Int() { + if from < to { for !last { - if data == toMsg.Int()-1 { + if data == to-1 { last = true } @@ -60,10 +60,8 @@ func (streamIntRange) Create( last, ) - select { - case <-ctx.Done(): + if !dataOut.Send(ctx, item) { return - case dataOut <- item: } idx++ @@ -81,17 +79,14 @@ func (streamIntRange) Create( last, ) - select { - case <-ctx.Done(): + if !dataOut.Send(ctx, item) { return - case dataOut <- item: } idx++ data-- } } - } }, nil } diff --git a/internal/runtime/funcs/read_all.go b/internal/runtime/funcs/read_all.go index ff291389..f8f98c10 100644 --- a/internal/runtime/funcs/read_all.go +++ b/internal/runtime/funcs/read_all.go @@ -10,64 +10,54 @@ import ( type readAll struct{} -func (c readAll) Create(rio runtime.FuncIO, msg runtime.Msg) (func(ctx context.Context), error) { - filename, err := rio.In.Port("filename") +func (c readAll) Create(rio runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { + filename, err := rio.In.Single("filename") if err != nil { return nil, err } - dataPort, err := rio.Out.Port("data") + dataPort, err := rio.Out.Single("data") if err != nil { return nil, err } - errPort, err := rio.Out.Port("err") + errPort, err := rio.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var name runtime.Msg - select { - case <-ctx.Done(): + name, ok := filename.Receive(ctx) + if !ok { return - case name = <-filename: } f, err := os.Open(name.Str()) if err != nil { - select { - case <-ctx.Done(): + if !errPort.Send(ctx, errFromErr(err)) { return - case errPort <- errorFromString(err.Error()): - continue } + continue } data, err := io.ReadAll(f) if err != nil { - select { - case <-ctx.Done(): + if !errPort.Send(ctx, errFromErr(err)) { return - case errPort <- errorFromString(err.Error()): - continue } + continue } if err := f.Close(); err != nil { - select { - case <-ctx.Done(): + if !errPort.Send(ctx, errFromErr(err)) { return - case errPort <- errorFromString(err.Error()): - continue } + continue } - select { - case <-ctx.Done(): + if !dataPort.Send(ctx, runtime.NewStrMsg(string(data))) { return - case dataPort <- runtime.NewStrMsg(string(data)): } } }, nil diff --git a/internal/runtime/funcs/regexp_submatch.go b/internal/runtime/funcs/regexp_submatch.go index 90b375ad..5210e846 100644 --- a/internal/runtime/funcs/regexp_submatch.go +++ b/internal/runtime/funcs/regexp_submatch.go @@ -10,70 +10,65 @@ import ( type regexpSubmatch struct{} -func (r regexpSubmatch) Create(io runtime.FuncIO, cfgMsg runtime.Msg) (func(ctx context.Context), error) { - regexpIn, err := io.In.Port("regexp") +func (r regexpSubmatch) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { + regexpIn, err := io.In.Single("regexp") if err != nil { return nil, err } - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - regexpMsg runtime.Msg - dataMsg runtime.Msg - ) - for { - select { - case <-ctx.Done(): + regexpMsg, ok := regexpIn.Receive(ctx) + if !ok { return - case regexpMsg = <-regexpIn: } regex, err := regexp.Compile(regexpMsg.Str()) if err != nil { - select { - case <-ctx.Done(): + if !errOut.Send(ctx, runtime.NewStrMsg(err.Error())) { return - case errOut <- runtime.NewStrMsg(err.Error()): - continue } + continue } - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case dataMsg = <-dataIn: } - res := regex.FindStringSubmatch(fmt.Sprint(dataMsg)) - - select { - case <-ctx.Done(): - case resOut <- wrap(res): + if !resOut.Send( + ctx, + stringsToList( + regex.FindStringSubmatch( + fmt.Sprint(dataMsg), + ), + ), + ) { + return } } }, nil } -func wrap(ss []string) runtime.Msg { +func stringsToList(ss []string) runtime.Msg { msgs := make([]runtime.Msg, 0, len(ss)) for _, s := range ss { msgs = append(msgs, runtime.NewStrMsg(s)) } - return runtime.NewListMsg(msgs...) + return runtime.NewListMsg(msgs) } diff --git a/internal/runtime/funcs/registry.go b/internal/runtime/funcs/registry.go index b655071c..d62383ab 100644 --- a/internal/runtime/funcs/registry.go +++ b/internal/runtime/funcs/registry.go @@ -9,10 +9,11 @@ import ( func CreatorRegistry() map[string]runtime.FuncCreator { return map[string]runtime.FuncCreator{ // core - "new": new{}, - "del": del{}, - "lock": lock{}, - "unwrap": unwrap{}, + "new": new{}, + "del": del{}, + "lock": lock{}, + "unwrap": unwrap{}, + "fan_out": fanOut{}, // runtime "panic": panicker{}, @@ -30,7 +31,6 @@ func CreatorRegistry() map[string]runtime.FuncCreator { "string_is_lesser": strIsLesser{}, "float_is_greater": floatIsGreater{}, "float_is_lesser": floatIsLesser{}, - "int_is_even": intIsEven{}, // streamers "array_port_to_stream": arrayPortToStream{}, diff --git a/internal/runtime/funcs/scanln.go b/internal/runtime/funcs/scanln.go index aba375b7..4971fdee 100644 --- a/internal/runtime/funcs/scanln.go +++ b/internal/runtime/funcs/scanln.go @@ -11,24 +11,22 @@ import ( type scanln struct{} func (r scanln) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - sigIn, err := io.In.Port("sig") + sigIn, err := io.In.Single("sig") if err != nil { return nil, err } - dataOut, err := io.Out.Port("data") + dataOut, err := io.Out.Single("data") if err != nil { return nil, err } return func(ctx context.Context) { - var reader = bufio.NewReader(os.Stdin) - for { - select { - case <-ctx.Done(): + reader := bufio.NewReader(os.Stdin) + + if _, ok := sigIn.Receive(ctx); !ok { return - case <-sigIn: } bb, _, err := reader.ReadLine() @@ -36,10 +34,8 @@ func (r scanln) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Conte panic(err) } - select { - case <-ctx.Done(): + if !dataOut.Send(ctx, runtime.NewStrMsg(string(bb))) { return - case dataOut <- runtime.NewStrMsg(string(bb)): } } }, nil diff --git a/internal/runtime/funcs/stream_product.go b/internal/runtime/funcs/stream_product.go index bf84bac3..1471cd6c 100644 --- a/internal/runtime/funcs/stream_product.go +++ b/internal/runtime/funcs/stream_product.go @@ -12,65 +12,66 @@ func (streamProduct) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - firstIn, err := io.In.Port("first") + firstIn, err := io.In.Single("first") if err != nil { return nil, err } - secondIn, err := io.In.Port("second") + secondIn, err := io.In.Single("second") if err != nil { return nil, err } - seqOut, err := io.Out.Port("seq") + seqOut, err := io.Out.Single("seq") if err != nil { return nil, err } return func(ctx context.Context) { for { - var firstData, secondData []runtime.Msg + firstData := []runtime.Msg{} for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := firstIn.Receive(ctx) + if !ok { return - case seqMsg := <-firstIn: - item = seqMsg.Map() } - if firstData = append(firstData, item["data"]); item["last"].Bool() { + + item := seqMsg.Map() + firstData = append(firstData, item["data"]) + + if item["last"].Bool() { break } } + + secondData := []runtime.Msg{} for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := secondIn.Receive(ctx) + if !ok { return - case seqMsg := <-secondIn: - item = seqMsg.Map() } - if secondData = append(secondData, item["data"]); item["last"].Bool() { + + item := seqMsg.Map() + secondData = append(secondData, item["data"]) + + if item["last"].Bool() { break } } - idx := int64(0) - for i, e1 := range firstData { - for j, e2 := range secondData { - select { - case <-ctx.Done(): - return - case seqOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "idx": runtime.NewIntMsg(idx), - "last": runtime.NewBoolMsg(i == len(firstData)-1 && j == len(secondData)-1), - "data": runtime.NewMapMsg(map[string]runtime.Msg{ - "first": e1, - "second": e2, - }), - }): - idx++ - } + for i, msg1 := range firstData { + for j, msg2 := range secondData { + seqOut.Send( + ctx, + streamItem( + runtime.NewMapMsg(map[string]runtime.Msg{ + "first": msg1, + "second": msg2, + }), + int64(i), + i == len(firstData)-1 && j == len(secondData)-1, + ), + ) } } } diff --git a/internal/runtime/funcs/stream_to_list.go b/internal/runtime/funcs/stream_to_list.go index bcb758ef..44cc6e99 100644 --- a/internal/runtime/funcs/stream_to_list.go +++ b/internal/runtime/funcs/stream_to_list.go @@ -12,12 +12,12 @@ func (s streamToList) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - seqIn, err := io.In.Port("seq") + seqIn, err := io.In.Single("seq") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } @@ -26,25 +26,24 @@ func (s streamToList) Create( acc := []runtime.Msg{} for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + msg, ok := seqIn.Receive(ctx) + if !ok { return - case seqMsg := <-seqIn: - item = seqMsg.Map() } + item := msg.Map() + acc = append(acc, item["data"]) - if item["last"].Bool() { - select { - case <-ctx.Done(): - return - case resOut <- runtime.NewListMsg(acc...): - acc = []runtime.Msg{} // reset - continue - } + if !item["last"].Bool() { + continue } + + if !resOut.Send(ctx, runtime.NewListMsg(acc)) { + return + } + + acc = []runtime.Msg{} } }, nil } diff --git a/internal/runtime/funcs/stream_zip.go b/internal/runtime/funcs/stream_zip.go index fb24a7c1..0b931ce3 100644 --- a/internal/runtime/funcs/stream_zip.go +++ b/internal/runtime/funcs/stream_zip.go @@ -12,45 +12,48 @@ func (streamZip) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - firstIn, err := io.In.Port("first") + firstIn, err := io.In.Single("first") if err != nil { return nil, err } - secondIn, err := io.In.Port("second") + secondIn, err := io.In.Single("second") if err != nil { return nil, err } - seqOut, err := io.Out.Port("seq") + seqOut, err := io.Out.Single("seq") if err != nil { return nil, err } + // TODO optimize (read 1 message at a time from each inport, then send 1 message to outport + // close out stream as soon as one of the two input messages are last, but be careful with the + // rest messages in the second stream, you also need to read them) return func(ctx context.Context) { for { - var firstData, secondData []runtime.Msg + firstData := []runtime.Msg{} for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := firstIn.Receive(ctx) + if !ok { return - case seqMsg := <-firstIn: - item = seqMsg.Map() } - if firstData = append(firstData, item["data"]); item["last"].Bool() { + item := seqMsg.Map() + firstData = append(firstData, item["data"]) + if item["last"].Bool() { break } } + + secondData := []runtime.Msg{} for { - var item map[string]runtime.Msg - select { - case <-ctx.Done(): + seqMsg, ok := secondIn.Receive(ctx) + if !ok { return - case seqMsg := <-secondIn: - item = seqMsg.Map() } - if secondData = append(secondData, item["data"]); item["last"].Bool() { + item := seqMsg.Map() + secondData = append(secondData, item["data"]) + if item["last"].Bool() { break } } @@ -60,21 +63,19 @@ func (streamZip) Create( n = m } - idx := int64(0) for i := 0; i < n; i++ { - e1, e2 := firstData[i], secondData[i] - select { - case <-ctx.Done(): + if !seqOut.Send( + ctx, + streamItem( + runtime.NewMapMsg(map[string]runtime.Msg{ + "first": firstData[i], + "second": secondData[i], + }), + int64(i), + i == n-1, + ), + ) { return - case seqOut <- runtime.NewMapMsg(map[string]runtime.Msg{ - "idx": runtime.NewIntMsg(idx), - "last": runtime.NewBoolMsg(i == n-1), - "data": runtime.NewMapMsg(map[string]runtime.Msg{ - "first": e1, - "second": e2, - }), - }): - idx++ } } } diff --git a/internal/runtime/funcs/string_at.go b/internal/runtime/funcs/string_at.go index eea3672e..43c06d89 100644 --- a/internal/runtime/funcs/string_at.go +++ b/internal/runtime/funcs/string_at.go @@ -2,87 +2,64 @@ package funcs import ( "context" - "errors" + "unicode/utf8" "github.com/nevalang/neva/internal/runtime" ) -var errStrIndexOutOfBounds = errors.New("string index out of bounds") - type stringAt struct{} func (stringAt) Create(io runtime.FuncIO, _ runtime.Msg) (func(context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - idxIn, err := io.In.Port("idx") + idxIn, err := io.In.Single("idx") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } - errOut, err := io.Out.Port("err") + errOut, err := io.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var data string - var idx int64 - - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case msg := <-dataIn: - data = msg.Str() } - select { - case <-ctx.Done(): + idxMsg, ok := idxIn.Receive(ctx) + if !ok { return - case msg := <-idxIn: - idx = msg.Int() } - if idx < 0 { - // Support negaitve indexing: - // $s = "abc" - // $s[-1] // "c" - idx += int64(len(data)) - } + idx := idxMsg.Int() + data := dataMsg.Str() + l := int64(utf8.RuneCountInString(data)) - if idx >= 0 && idx < int64(len(data)) { - var res rune - var found bool - for i, r := range data { - if int64(i) == idx { - res = r - found = true - break - } + if idx < -l || idx >= l { + if !errOut.Send(ctx, errFromString("index out of bounds")) { + return } - if found { - select { - case <-ctx.Done(): + } + + for i, r := range data { + if int64(i) == idx { + if !resOut.Send(ctx, runtime.NewStrMsg(string(r))) { return - case resOut <- runtime.NewStrMsg(string(res)): - continue } + break } } - - select { - case <-ctx.Done(): - return - case errOut <- errorFromString(errStrIndexOutOfBounds.Error()): - } } }, nil } diff --git a/internal/runtime/funcs/string_is_greater.go b/internal/runtime/funcs/string_is_greater.go index 2ec11c7b..650143ce 100644 --- a/internal/runtime/funcs/string_is_greater.go +++ b/internal/runtime/funcs/string_is_greater.go @@ -2,49 +2,42 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type strIsGreater struct{} func (p strIsGreater) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + val1, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + val2, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewBoolMsg(val1.Str() > val2.Str())) { return - case resOut <- runtime.NewBoolMsg(val1.Str() > val2.Str()): } } }, nil diff --git a/internal/runtime/funcs/string_is_lesser.go b/internal/runtime/funcs/string_is_lesser.go index 46c88819..a51a0081 100644 --- a/internal/runtime/funcs/string_is_lesser.go +++ b/internal/runtime/funcs/string_is_lesser.go @@ -2,49 +2,42 @@ package funcs import ( "context" + "github.com/nevalang/neva/internal/runtime" ) type strIsLesser struct{} func (p strIsLesser) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - actualIn, err := io.In.Port("actual") + actualIn, err := io.In.Single("actual") if err != nil { return nil, err } - comparedIn, err := io.In.Port("compared") + + comparedIn, err := io.In.Single("compared") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - val1 runtime.Msg - val2 runtime.Msg - ) - for { - select { - case <-ctx.Done(): + val1, ok := actualIn.Receive(ctx) + if !ok { return - case val1 = <-actualIn: } - select { - case <-ctx.Done(): + val2, ok := comparedIn.Receive(ctx) + if !ok { return - case val2 = <-comparedIn: } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewBoolMsg(val1.Str() < val2.Str())) { return - case resOut <- runtime.NewBoolMsg(val1.Str() < val2.Str()): } } }, nil diff --git a/internal/runtime/funcs/string_join.go b/internal/runtime/funcs/string_join.go index ba6e541c..b248d0ee 100644 --- a/internal/runtime/funcs/string_join.go +++ b/internal/runtime/funcs/string_join.go @@ -10,24 +10,21 @@ import ( type stringJoin struct{} func (p stringJoin) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var data runtime.Msg - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } builder := strings.Builder{} @@ -36,7 +33,9 @@ func (p stringJoin) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.C builder.WriteString(list[i].Str()) } - resOut <- runtime.NewStrMsg(builder.String()) + if !resOut.Send(ctx, runtime.NewStrMsg(builder.String())) { + return + } } }, nil } diff --git a/internal/runtime/funcs/string_split.go b/internal/runtime/funcs/string_split.go index 30e69b44..b61727cf 100644 --- a/internal/runtime/funcs/string_split.go +++ b/internal/runtime/funcs/string_split.go @@ -10,38 +10,31 @@ import ( type stringSplit struct{} func (p stringSplit) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - delimIn, err := io.In.Port("delim") + delimIn, err := io.In.Single("delim") if err != nil { return nil, err } - resOut, err := io.Out.Port("res") + resOut, err := io.Out.Single("res") if err != nil { return nil, err } return func(ctx context.Context) { - var ( - data runtime.Msg - delim runtime.Msg - ) - for { - select { - case <-ctx.Done(): + data, ok := dataIn.Receive(ctx) + if !ok { return - case data = <-dataIn: } - select { - case <-ctx.Done(): + delim, ok := delimIn.Receive(ctx) + if !ok { return - case delim = <-delimIn: } splitted := strings.Split(data.Str(), delim.Str()) @@ -50,10 +43,8 @@ func (p stringSplit) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context. res[i] = runtime.NewStrMsg(s) } - select { - case <-ctx.Done(): + if !resOut.Send(ctx, runtime.NewListMsg(res)) { return - case resOut <- runtime.NewListMsg(res...): } } }, nil diff --git a/internal/runtime/funcs/struct_builder.go b/internal/runtime/funcs/struct_builder.go index 4c2bf69c..3fccd338 100644 --- a/internal/runtime/funcs/struct_builder.go +++ b/internal/runtime/funcs/struct_builder.go @@ -13,19 +13,19 @@ func (s structBuilder) Create( io runtime.FuncIO, _ runtime.Msg, ) (func(ctx context.Context), error) { - if len(io.In) == 0 { + if len(io.In.Ports()) == 0 { return nil, errors.New("cannot create struct builder without inports") } - inports := make(map[string]chan runtime.Msg, len(io.In)) - for inportName, inportSlots := range io.In { - if len(inportSlots) != 1 { + inports := make(map[string]runtime.SingleInport, len(io.In.Ports())) + for inportName, inportSlots := range io.In.Ports() { + if inportSlots.Single() == nil { return nil, errors.New("non-single port found: " + inportName) } - inports[inportName] = inportSlots[0] + inports[inportName] = *inportSlots.Single() } - outport, err := io.Out.Port("msg") + outport, err := io.Out.Single("msg") if err != nil { return nil, err } @@ -34,26 +34,23 @@ func (s structBuilder) Create( } func (structBuilder) Handle( - inports map[string]chan runtime.Msg, - outport chan runtime.Msg, + inports map[string]runtime.SingleInport, + outport runtime.SingleOutport, ) func(ctx context.Context) { return func(ctx context.Context) { for { var structure = make(map[string]runtime.Msg, len(inports)) for inportName, inportChan := range inports { - select { - case <-ctx.Done(): + msg, ok := inportChan.Receive(ctx) + if !ok { return - case msg := <-inportChan: - structure[inportName] = msg } + structure[inportName] = msg } - select { - case <-ctx.Done(): + if !outport.Send(ctx, runtime.NewMapMsg(structure)) { return - case outport <- runtime.NewMapMsg(structure): } } } diff --git a/internal/runtime/funcs/time_sleep.go b/internal/runtime/funcs/time_sleep.go index a8f7453e..b43f389e 100644 --- a/internal/runtime/funcs/time_sleep.go +++ b/internal/runtime/funcs/time_sleep.go @@ -10,43 +10,37 @@ import ( type timeSleep struct{} func (timeSleep) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - durIn, err := io.In.Port("dur") + durIn, err := io.In.Single("dur") if err != nil { return nil, err } - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - dataOut, err := io.Out.Port("data") + dataOut, err := io.Out.Single("data") if err != nil { return nil, err } return func(ctx context.Context) { for { - var durMsg runtime.Msg - select { - case <-ctx.Done(): + durMsg, ok := durIn.Receive(ctx) + if !ok { return - case durMsg = <-durIn: } - var dataMsg runtime.Msg - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case dataMsg = <-dataIn: } time.Sleep(time.Duration(durMsg.Int())) - select { - case <-ctx.Done(): + if !dataOut.Send(ctx, dataMsg) { return - case dataOut <- dataMsg: } } }, nil diff --git a/internal/runtime/funcs/unwrap.go b/internal/runtime/funcs/unwrap.go index 803ddc5e..f831ecfa 100644 --- a/internal/runtime/funcs/unwrap.go +++ b/internal/runtime/funcs/unwrap.go @@ -9,44 +9,37 @@ import ( type unwrap struct{} func (unwrap) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - dataIn, err := io.In.Port("data") + dataIn, err := io.In.Single("data") if err != nil { return nil, err } - someOut, err := io.Out.Port("some") + someOut, err := io.Out.Single("some") if err != nil { return nil, err } - noneOut, err := io.Out.Port("none") + noneOut, err := io.Out.Single("none") if err != nil { return nil, err } return func(ctx context.Context) { - var dataMsg runtime.Msg - for { - select { - case <-ctx.Done(): + dataMsg, ok := dataIn.Receive(ctx) + if !ok { return - case dataMsg = <-dataIn: } if dataMsg == nil { - select { - case <-ctx.Done(): + if !noneOut.Send(ctx, nil) { return - case noneOut <- runtime.NewMapMsg(nil): } continue } - select { - case <-ctx.Done(): + if !someOut.Send(ctx, dataMsg) { return - case someOut <- dataMsg: } } }, nil diff --git a/internal/runtime/funcs/utils.go b/internal/runtime/funcs/utils.go index 94ba49a6..33c49f4d 100644 --- a/internal/runtime/funcs/utils.go +++ b/internal/runtime/funcs/utils.go @@ -2,13 +2,19 @@ package funcs import "github.com/nevalang/neva/internal/runtime" -func errorFromString(s string) runtime.Msg { +func errFromErr(err error) runtime.MapMsg { + return runtime.NewMapMsg(map[string]runtime.Msg{ + "text": runtime.NewStrMsg(err.Error()), + }) +} + +func errFromString(s string) runtime.MapMsg { return runtime.NewMapMsg(map[string]runtime.Msg{ "text": runtime.NewStrMsg(s), }) } -func streamItem(data runtime.Msg, idx int64, last bool) runtime.Msg { +func streamItem(data runtime.Msg, idx int64, last bool) runtime.MapMsg { return runtime.NewMapMsg(map[string]runtime.Msg{ "data": data, "idx": runtime.NewIntMsg(idx), diff --git a/internal/runtime/funcs/wait_group.go b/internal/runtime/funcs/wait_group.go index 872bbe52..136f8574 100644 --- a/internal/runtime/funcs/wait_group.go +++ b/internal/runtime/funcs/wait_group.go @@ -2,7 +2,6 @@ package funcs import ( "context" - "sync" "github.com/nevalang/neva/internal/runtime" ) @@ -10,17 +9,17 @@ import ( type waitGroup struct{} func (g waitGroup) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { - countIn, err := io.In.Port("count") + countIn, err := io.In.Single("count") if err != nil { return nil, err } - sigIn, err := io.In.Port("sig") + sigIn, err := io.In.Single("sig") if err != nil { return nil, err } - sigOut, err := io.Out.Port("sig") + sigOut, err := io.Out.Single("sig") if err != nil { return nil, err } @@ -29,38 +28,24 @@ func (g waitGroup) Create(io runtime.FuncIO, _ runtime.Msg) (func(ctx context.Co } func (waitGroup) Handle( - countIn, - sigIn, - sigOut chan runtime.Msg, + countIn runtime.SingleInport, + sigIn runtime.SingleInport, + sigOut runtime.SingleOutport, ) func(ctx context.Context) { return func(ctx context.Context) { for { - var wg sync.WaitGroup - var count int64 - - select { - case n := <-countIn: - count = n.Int() - wg.Add(int(count)) - case <-ctx.Done(): + n, ok := countIn.Receive(ctx) + if !ok { return } - go func() { - for i := int64(0); i < count; i++ { - select { - case <-sigIn: - wg.Done() - case <-ctx.Done(): - return - } + for i := int64(0); i < n.Int(); i++ { + if _, ok := sigIn.Receive(ctx); !ok { + return } - }() - wg.Wait() + } - select { - case sigOut <- nil: - case <-ctx.Done(): + if !sigOut.Send(ctx, nil) { return } } diff --git a/internal/runtime/funcs/write_all.go b/internal/runtime/funcs/write_all.go index ea7a7fba..6ef397e5 100644 --- a/internal/runtime/funcs/write_all.go +++ b/internal/runtime/funcs/write_all.go @@ -9,56 +9,49 @@ import ( type writeAll struct{} -func (c writeAll) Create(rio runtime.FuncIO, msg runtime.Msg) (func(ctx context.Context), error) { - filename, err := rio.In.Port("filename") +func (c writeAll) Create(rio runtime.FuncIO, _ runtime.Msg) (func(ctx context.Context), error) { + filename, err := rio.In.Single("filename") if err != nil { return nil, err } - dataPort, err := rio.In.Port("data") + dataPort, err := rio.In.Single("data") if err != nil { return nil, err } - sig, err := rio.Out.Port("sig") + sig, err := rio.Out.Single("sig") if err != nil { return nil, err } - errPort, err := rio.Out.Port("err") + errPort, err := rio.Out.Single("err") if err != nil { return nil, err } return func(ctx context.Context) { for { - var name, data runtime.Msg - select { - case <-ctx.Done(): + name, ok := filename.Receive(ctx) + if !ok { return - case name = <-filename: } - select { - case <-ctx.Done(): + data, ok := dataPort.Receive(ctx) + if !ok { return - case data = <-dataPort: } err := os.WriteFile(name.Str(), []byte(data.Str()), 0755) if err != nil { - select { - case <-ctx.Done(): + if !errPort.Send(ctx, errFromErr(err)) { return - case errPort <- errorFromString(err.Error()): - continue } + continue } - select { - case <-ctx.Done(): + if !sig.Send(ctx, nil) { return - case sig <- nil: } } }, nil diff --git a/internal/runtime/ir/ir.go b/internal/runtime/ir/ir.go deleted file mode 100644 index cabb9f8f..00000000 --- a/internal/runtime/ir/ir.go +++ /dev/null @@ -1,58 +0,0 @@ -package ir - -// Program represents the main structure containing ports, connections, and funcs. -type Program struct { - Ports map[PortAddr]struct{} `json:"ports,omitempty"` - Connections map[PortAddr]map[PortAddr]struct{} `json:"connections,omitempty"` - Funcs []FuncCall `json:"funcs,omitempty"` // can't be map cuz multuple calls can have same ref -} - -// PortAddr represents the address of a port. -type PortAddr struct { - Path string `json:"path,omitempty"` - Port string `json:"port,omitempty"` - Idx uint32 `json:"idx,omitempty"` -} - -// Connection represents connections between ports. -type Connection struct { // TODO remove? - SenderSide PortAddr `json:"sender_side,omitempty"` - ReceiverSides []PortAddr `json:"receiver_sides,omitempty"` -} - -// FuncCall represents a function within the program. -type FuncCall struct { - Ref string `json:"ref,omitempty"` - IO FuncIO `json:"io,omitempty"` - Msg *Msg `json:"msg,omitempty"` -} - -// FuncIO represents the input/output ports of a function. -type FuncIO struct { - In []PortAddr `json:"in,omitempty"` // Must be ordered by path -> port -> idx - Out []PortAddr `json:"out,omitempty"` // Must be ordered by path -> port -> idx -} - -// Msg represents a message. -type Msg struct { - Type MsgType `json:"-"` - Bool bool `json:"bool,omitempty"` - Int int64 `json:"int,omitempty"` - Float float64 `json:"float,omitempty"` - Str string `json:"str,omitempty"` - List []Msg `json:"list,omitempty"` - Map map[string]Msg `json:"map,omitempty"` -} - -// MsgType is an enumeration of message types. -type MsgType int32 - -const ( - MsgTypeUnspecified MsgType = 0 - MsgTypeBool MsgType = 1 - MsgTypeInt MsgType = 2 - MsgTypeFloat MsgType = 3 - MsgTypeString MsgType = 4 - MsgTypeList MsgType = 5 - MsgTypeMap MsgType = 6 -) diff --git a/internal/runtime/msg.go b/internal/runtime/message.go similarity index 95% rename from internal/runtime/msg.go rename to internal/runtime/message.go index 174dc784..48401b4b 100644 --- a/internal/runtime/msg.go +++ b/internal/runtime/message.go @@ -13,7 +13,8 @@ type Msg interface { Float() float64 Str() string List() []Msg - Map() map[string]Msg + Map() map[string]Msg // TODO rename maps to dicts + // IDEA use reflect for structures (instead of maps) } // Base (internal helper) @@ -123,7 +124,7 @@ func (msg ListMsg) String() string { } func (msg ListMsg) MarshalJSON() ([]byte, error) { return json.Marshal(msg.v) } -func NewListMsg(v ...Msg) ListMsg { +func NewListMsg(v []Msg) ListMsg { return ListMsg{ baseMsg: &baseMsg{}, v: v, diff --git a/internal/runtime/network.go b/internal/runtime/network.go new file mode 100644 index 00000000..8560a2e2 --- /dev/null +++ b/internal/runtime/network.go @@ -0,0 +1,178 @@ +package runtime + +import ( + "context" + "fmt" + "sort" + "sync" +) + +type Network struct { + connections map[Receiver][]Sender + interceptor Interceptor +} + +type Interceptor interface { + Sent(sender, receiver PortAddr, msg Msg) Msg + Received(sender, receiver PortAddr, msg Msg) +} + +type Sender struct { + Addr PortAddr + Port <-chan IndexedMsg +} + +type Receiver struct { + Addr PortAddr + Port chan<- IndexedMsg +} + +type PortAddr struct { + Path string + Port string + // combination of uint8 + bool is more memory efficient than *uint8 + Idx uint8 + Arr bool +} + +type IndexedMsg struct { + data Msg + index uint64 +} + +func (n Network) Run(ctx context.Context) { + wg := sync.WaitGroup{} + wg.Add(len(n.connections)) + + for r, ss := range n.connections { + r := r + ss := ss + + var f func() + if len(ss) == 1 { + f = func() { n.pipe(ctx, r, ss[0]) } + } else { + f = func() { n.fanIn(ctx, r, ss) } + } + + go func() { + f() + wg.Done() + }() + } + + wg.Wait() +} + +func (n Network) pipe(ctx context.Context, r Receiver, s Sender) { + for { + var msg IndexedMsg + select { + case <-ctx.Done(): + return + case msg = <-s.Port: + } + + if intercepted := n.interceptor.Sent(s.Addr, r.Addr, msg.data); intercepted != nil { + msg = IndexedMsg{ + data: intercepted, + index: msg.index, + } + } + + select { + case <-ctx.Done(): + return + case r.Port <- msg: + } + + n.interceptor.Received(s.Addr, r.Addr, msg.data) + } +} + +type fanInQueueItem struct { + sender PortAddr + msg IndexedMsg +} + +func (n Network) fanIn(ctx context.Context, r Receiver, ss []Sender) { + for { + i := 0 + buf := make([]fanInQueueItem, 0, len(ss)^2) + + for { // wait long enough to fill the buffer + if len(buf) > 0 && i >= len(ss) { + break + } + + for _, s := range ss { + select { + case <-ctx.Done(): + return + case msg := <-s.Port: + if intercepted := n.interceptor.Sent(s.Addr, r.Addr, msg.data); intercepted != nil { + msg = IndexedMsg{ + data: intercepted, + index: msg.index, + } + } + buf = append(buf, fanInQueueItem{ + sender: s.Addr, + msg: msg, + }) + default: + continue + } + } + + // TODO: properly add runtime.Gosched() + + i++ + } + + // at this point buffer has >= 1 and <= len(outs)^2 messages + + // we not sure we received messages in same order they were sent so we sort them + sort.Slice(buf, func(i, j int) bool { + return buf[i].msg.index < buf[j].msg.index + }) + + // finally send them to inport + // this is the bottleneck where slow receiver slows down fast senders + for _, item := range buf { + select { + case <-ctx.Done(): + return + case r.Port <- item.msg: + n.interceptor.Received(item.sender, r.Addr, item.msg.data) + } + } + } +} + +type printer struct{} + +func (p printer) Sent(sender, receiver PortAddr, msg Msg) Msg { + fmt.Println("sent: ", sender, "->", receiver, msg) + return nil +} + +func (p printer) Received(sender, receiver PortAddr, msg Msg) { + fmt.Println("received", sender, "->", receiver, msg) +} + +type dummy struct{} + +func (dummy) Sent(sender, receiver PortAddr, msg Msg) Msg { return nil } + +func (dummy) Received(sender, receiver PortAddr, msg Msg) {} + +func NewNetwork(connections map[Receiver][]Sender, debug bool) Network { + n := Network{connections: connections} + if debug { + n.interceptor = printer{} + } else { + n.interceptor = dummy{} + } + return n +} diff --git a/internal/runtime/program.go b/internal/runtime/program.go index 57815808..372d40ba 100644 --- a/internal/runtime/program.go +++ b/internal/runtime/program.go @@ -1,39 +1,24 @@ package runtime import ( + "context" "errors" "fmt" ) type Program struct { - Ports Ports - Connections []Connection + Start, Stop chan IndexedMsg + Connections map[Receiver][]Sender Funcs []FuncCall } -type PortAddr struct { - Path string // Path is needed to distinguish ports with the same name - Port string // Separate port field is needed for functions - Idx uint8 -} - func (p PortAddr) String() string { + if !p.Arr { + return fmt.Sprintf("%v:%v", p.Path, p.Port) + } return fmt.Sprintf("%v:%v[%v]", p.Path, p.Port, p.Idx) } -type Ports map[PortAddr]chan Msg - -type Connection struct { - Sender chan Msg - Receivers []chan Msg - Meta ConnectionMeta -} - -type ConnectionMeta struct { - SenderPortAddr PortAddr - ReceiverPortAddrs []PortAddr -} - type FuncCall struct { Ref string IO FuncIO @@ -41,26 +26,216 @@ type FuncCall struct { } type FuncIO struct { - In, Out FuncPorts + In FuncInports + Out FuncOutports } -// FuncPorts is data structure that runtime functions must use at startup to get needed ports. -// Its methods can return error because it's okay to fail at startup. Keys are port names and values are slots. -type FuncPorts map[string][]chan Msg +type FuncInports struct { + ports map[string]FuncInport +} -var ErrSinglePortCount = errors.New("number of ports found by name not equals to one") +func (f FuncInports) Ports() map[string]FuncInport { + return f.ports +} -// Port returns first slot of port found by the given name. -// It returns error if port is not found or if it's not a single port. -func (f FuncPorts) Port(name string) (chan Msg, error) { - slots, ok := f[name] +func (f FuncInports) Single(name string) (SingleInport, error) { + ports, ok := f.ports[name] if !ok { - return nil, fmt.Errorf("port '%v' not found", name) + return SingleInport{}, errors.New("port not found by name") + } + + if ports.single == nil { + return SingleInport{}, errors.New("port is not single") } - if len(slots) != 1 { - return nil, fmt.Errorf("%w: %v", ErrSinglePortCount, len(f[name])) + return *ports.single, nil +} + +func NewFuncInports(ports map[string]FuncInport) FuncInports { + return FuncInports{ + ports: ports, } +} + +type FuncInport struct { + array *ArrayInport + single *SingleInport +} + +func (f FuncInport) Array() *ArrayInport { + return f.array +} + +func (f FuncInport) Single() *SingleInport { + return f.single +} + +func NewFuncInport( + array *ArrayInport, + single *SingleInport, +) FuncInport { + return FuncInport{ + array: array, + single: single, + } +} + +type SingleInport struct{ ch <-chan IndexedMsg } + +func NewSingleInport(ch <-chan IndexedMsg) *SingleInport { + return &SingleInport{ch} +} + +func (s SingleInport) Receive(ctx context.Context) (Msg, bool) { + select { + case <-ctx.Done(): + return nil, false + case msg := <-s.ch: + return msg.data, true + } +} + +func (f FuncInports) Array(name string) (ArrayInport, error) { + ports, ok := f.ports[name] + if !ok { + return ArrayInport{}, errors.New("port not found by name") + } + + if ports.array == nil { + return ArrayInport{}, errors.New("port is not array") + } + + return *ports.array, nil +} + +type ArrayInport struct{ chans []<-chan IndexedMsg } + +func NewArrayInport(chans []<-chan IndexedMsg) *ArrayInport { + return &ArrayInport{chans} +} + +func (a ArrayInport) Receive(ctx context.Context, f func(idx int, msg Msg) bool) bool { + for i, ch := range a.chans { + select { + case msg := <-ch: + if !f(i, msg.data) { + return false + } + case <-ctx.Done(): + return false + } + } + return true +} + +func (a ArrayInport) Len() int { + return len(a.chans) +} + +type FuncOutports struct { + ports map[string]FuncOutport +} + +func NewFuncOutports(ports map[string]FuncOutport) FuncOutports { + return FuncOutports{ports} +} + +func (f FuncOutports) Single(name string) (SingleOutport, error) { + port, ok := f.ports[name] + if !ok { + return SingleOutport{}, fmt.Errorf("port '%v' not found", name) + } + + if port.single == nil { + return SingleOutport{}, fmt.Errorf("port '%v' is not single", name) + } + + return *port.single, nil +} + +func (f FuncOutports) Array(name string) (ArrayOutport, error) { + port, ok := f.ports[name] + if !ok { + return ArrayOutport{}, fmt.Errorf("port '%v' not found", name) + } + + if port.array == nil { + return ArrayOutport{}, fmt.Errorf("port '%v' is not array", name) + } + + return *port.array, nil +} + +type FuncOutport struct { + single *SingleOutport + array *ArrayOutport +} + +func NewFuncOutport( + single *SingleOutport, + array *ArrayOutport, +) FuncOutport { + return FuncOutport{single, array} +} + +type SingleOutport struct { + addr PortAddr + ch chan<- IndexedMsg +} + +func NewSingleOutport( + addr PortAddr, + ch chan<- IndexedMsg, +) *SingleOutport { + return &SingleOutport{ + addr: addr, + ch: ch, + } +} + +func (s SingleOutport) Send(ctx context.Context, msg Msg) bool { + select { + case s.ch <- IndexedMsg{ + data: msg, + index: counter.Add(1), + }: + return true + case <-ctx.Done(): + return false + } +} + +type ArrayOutport struct { + slots []chan<- IndexedMsg +} + +func NewArrayOutport(slots []chan<- IndexedMsg) *ArrayOutport { + return &ArrayOutport{slots: slots} +} + +func (a ArrayOutport) Send(ctx context.Context, idx uint8, msg Msg) bool { + select { + case <-ctx.Done(): + return false + case a.slots[idx] <- IndexedMsg{data: msg, index: counter.Add(1)}: + return true + } +} + +func (a ArrayOutport) SendAll(ctx context.Context, msg Msg) bool { + for _, slot := range a.slots { + select { + case <-ctx.Done(): + return false + case slot <- IndexedMsg{ + data: msg, + index: counter.Add(1), + }: + } + } + return true +} - return slots[0], nil +func (a ArrayOutport) Len() int { + return len(a.slots) } diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 555d3ca8..a5919ec5 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -1,81 +1,56 @@ -// Package runtime implements environment for dataflow programs execution. package runtime import ( "context" - "errors" - "fmt" "sync" + "sync/atomic" ) +var counter atomic.Uint64 + type Runtime struct { - connector Connector funcRunner FuncRunner } -var ErrNilDeps = errors.New("runtime deps nil") - -func New(connector Connector, funcRunner FuncRunner) Runtime { - return Runtime{ - connector: connector, - funcRunner: funcRunner, - } -} - -var ( - ErrStartPortNotFound = errors.New("start port not found") - ErrExitPortNotFound = errors.New("stop port not found") - ErrConnector = errors.New("connector") - ErrFuncRunner = errors.New("func runner") -) - -func (r Runtime) Run(ctx context.Context, prog Program) error { - enter := prog.Ports[PortAddr{Path: "in", Port: "start"}] - if enter == nil { - return ErrStartPortNotFound - } - - exit := prog.Ports[PortAddr{Path: "out", Port: "stop"}] - if exit == nil { - return ErrExitPortNotFound - } - - funcRun, err := r.funcRunner.Run(prog.Funcs) - if err != nil { - return fmt.Errorf("%w: %v", ErrFuncRunner, err) - } - - cancelableCtx, cancel := context.WithCancel(ctx) +func (p *Runtime) Run(ctx context.Context, prog Program, debug bool) error { + ctx, cancel := context.WithCancel(ctx) + go func() { + <-prog.Stop + cancel() + }() wg := sync.WaitGroup{} wg.Add(2) + net := NewNetwork(prog.Connections, debug) go func() { - funcRun( - context.WithValue( - cancelableCtx, - "cancel", //nolint:staticcheck // SA1029 - cancel, - ), - ) + net.Run(ctx) wg.Done() }() - go func() { - r.connector.Connect(cancelableCtx, prog.Connections) - wg.Done() - }() + funcrun, err := p.funcRunner.Run(prog.Funcs) + if err != nil { + return err + } go func() { - enter <- &baseMsg{} + ctx = context.WithValue(ctx, "cancel", cancel) //nolint:staticcheck // SA1029 + funcrun(ctx) + wg.Done() }() - go func() { - <-exit - cancel() - }() + prog.Start <- IndexedMsg{ + index: counter.Add(1), + data: &baseMsg{}, + } wg.Wait() return nil } + +func New(funcRunner FuncRunner) Runtime { + return Runtime{ + funcRunner: funcRunner, + } +} diff --git a/pkg/version.go b/pkg/version.go index 09e75ee5..672c24bf 100644 --- a/pkg/version.go +++ b/pkg/version.go @@ -2,4 +2,4 @@ package pkg // Version is the current version of the language and stdlib. // Don't forget to update it before release new tag. -var Version = "0.25.1" //nolint:gochecknoglobals +var Version = "0.25.0" //nolint:gochecknoglobals diff --git a/std/builtin/core.neva b/std/builtin/core.neva index 7e5f04ba..983044c6 100644 --- a/std/builtin/core.neva +++ b/std/builtin/core.neva @@ -22,3 +22,6 @@ pub flow Unwrap(data maybe) (some T, none struct{}) #extern(stream_int_range) pub flow Range(from int, to int) (data stream) + +#extern(fan_out) +pub flow FanOut(data any) ([data] any) \ No newline at end of file diff --git a/std/builtin/io.neva b/std/builtin/io.neva index cc9fc6fe..c4327552 100644 --- a/std/builtin/io.neva +++ b/std/builtin/io.neva @@ -2,4 +2,4 @@ pub flow Println(data T) (sig T) #extern(printf) -pub flow Printf(tpl string, [args] any) ([args] any, err error) \ No newline at end of file +pub flow Printf(tpl string, [args] any) (sig any, err error) \ No newline at end of file diff --git a/std/builtin/logic.neva b/std/builtin/logic.neva index d2b9e43f..5590a1fe 100644 --- a/std/builtin/logic.neva +++ b/std/builtin/logic.neva @@ -11,10 +11,10 @@ pub flow If(data bool) (then any, else any) pub flow Not(data bool) (res bool) #extern(and) -pub flow And(A bool, B bool) (res bool) +pub flow And(a bool, b bool) (res bool) #extern(or) -pub flow Or(A bool, B bool) (res bool) +pub flow Or(a bool, b bool) (res bool) #extern(int int_is_greater, float float_is_greater, string string_is_greater) pub flow Gt(actual T, compared T) (res bool) diff --git a/std/builtin/streams.neva b/std/builtin/streams.neva index 75d65b6f..cbc28d02 100644 --- a/std/builtin/streams.neva +++ b/std/builtin/streams.neva @@ -89,13 +89,13 @@ pub flow Map(item stream) (res stream) { wrap -> :res } -pub type ProductRes struct { +pub type ProductResult struct { first T second R } #extern(stream_product) -pub flow Product(first stream, second stream) (seq stream>) +pub flow Product(first stream, second stream) (seq stream>) pub type ZipResult struct { first T diff --git a/std/io/io.neva b/std/io/io.neva index 40e641cc..f8eb7ce0 100644 --- a/std/io/io.neva +++ b/std/io/io.neva @@ -1,3 +1,2 @@ #extern(scanln) pub flow Scanln(sig any) (data string) - diff --git a/std/os/os.neva b/std/os/os.neva index 97a5baf3..3e074d3d 100644 --- a/std/os/os.neva +++ b/std/os/os.neva @@ -1,2 +1,2 @@ #extern(args) -pub flow Args() (data list) \ No newline at end of file +pub flow Args(sig any) (data list) \ No newline at end of file