From 7c15b5714ce1ea3c35f408584518f04855a9375a Mon Sep 17 00:00:00 2001 From: Emil Valeev Date: Wed, 7 Feb 2024 23:42:15 +0600 Subject: [PATCH] feat(desugarer): implement literal senders --- CONTRIBUTING.md | 10 ++ README.md | 35 ++---- internal/compiler/analyzer/component.go | 2 +- internal/compiler/analyzer/component_nodes.go | 6 +- internal/compiler/contract.go | 12 +- internal/compiler/desugarer/component.go | 24 ++-- internal/compiler/desugarer/const_sender.go | 115 ++++++++++++++---- .../compiler/desugarer/struct_selectors.go | 4 +- internal/compiler/irgen/runtime_func.go | 4 +- internal/compiler/parser/listener.go | 16 +-- internal/compiler/parser/parser_test.go | 18 +-- 11 files changed, 166 insertions(+), 80 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4078bd1d..19db5886 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -216,3 +216,13 @@ Lastly it's just common to have `|` syntax for unions. Because type-system is public package that can be used by others to implement languages (or something else constraint-based). Since there's no arrays at the syntax and internal representation levels then there's no performance overhead. Also having arrays in type system is not the most complicated thing so removing them won't save us much. + +### Why isn't Nevalang self-hosted? + +- Runtime will never be written in Nevalang itself because of the overhead of FBP runtime on to of Go's runtime. Go provides exactly that level of control we needed to implement FBP runtime for Nevalang. +- Compiler will be someday rewritten in Nevalang itself but we need several years of active usage of the language before that + +There's 2 reasons why we don't rewrite compiler in Nevalang right now: + +1. Language is incredibly unstable. Stdlib and even the core is massively changing these days. Compiler will be even more unstable and hard to maintain if we do that, until Nevalang is more or less stable. +2. Languages that are mostly used for writing compilers are eventually better suited for that purpose. While it's good to be able to write compiler in Nevalang without much effort, it's not the goal to create a language for compilers. Writing compilers is a good thing but it's not very popular task for programmers. Actually it's incredibly rare to write compilers at work. We want Nevalang to be good language for many programmers. diff --git a/README.md b/README.md index 1c952b96..5d21229e 100644 --- a/README.md +++ b/README.md @@ -20,29 +20,18 @@ component Main(start any) (stop any) { ## 🚀 Features -🌊 **Flow-Based Programming** - -🔀 **Effortless Concurrency** - -🛡ī¸ **Static Type System** - -đŸŽ¯ **Multi-Target Compilation** - -✨ **Simple and Clean C-like Syntax** - -🏃‍♂ī¸ **Interpreter Mode** - -💉 **First-Class Dependency Injection** - -đŸ•ĩī¸â€â™‚ī¸ **Builtin Observability** - -â™ģī¸ **Garbage Collection** - -🌈 **Visual Programming** (WIP) - -đŸĻĢ **Go Interop** (WIP) - -đŸĻē **No Runtime Exceptions** (WIP) +- 🌊 Flow-Based Programming +- 🔀 Effortless Concurrency +- 🛡ī¸ Static Type System +- đŸŽ¯ Multi-Target Compilation +- ✨ Simple and Clean C-like Syntax +- 🏃‍♂ī¸ Interpreter Mode +- 💉 First-Class Dependency Injection +- đŸ•ĩī¸â€â™‚ī¸ Builtin Observability +- â™ģī¸ Garbage Collection +- 🌈 Visual Programming (WIP) +- đŸĻĢ Go Interop (WIP) +- đŸĻē No Runtime Exceptions (WIP) ## Contributing diff --git a/internal/compiler/analyzer/component.go b/internal/compiler/analyzer/component.go index a9e4adbc..9328c3bf 100644 --- a/internal/compiler/analyzer/component.go +++ b/internal/compiler/analyzer/component.go @@ -37,7 +37,7 @@ func (a Analyzer) analyzeComponent( //nolint:funlen component src.Component, scope src.Scope, ) (src.Component, *compiler.Error) { - runtimeFuncArgs, isRuntimeFunc := component.Directives[compiler.RuntimeFuncDirective] + runtimeFuncArgs, isRuntimeFunc := component.Directives[compiler.ExternDirective] if isRuntimeFunc && len(runtimeFuncArgs) == 0 { return src.Component{}, &compiler.Error{ diff --git a/internal/compiler/analyzer/component_nodes.go b/internal/compiler/analyzer/component_nodes.go index aa8a9689..14b937fe 100644 --- a/internal/compiler/analyzer/component_nodes.go +++ b/internal/compiler/analyzer/component_nodes.go @@ -60,7 +60,7 @@ func (a Analyzer) analyzeComponentNode(node src.Node, scope src.Scope) (src.Node } } - runtimeMsgArgs, hasRuntimeMsg := node.Directives[compiler.RuntimeFuncMsgDirective] + runtimeMsgArgs, hasRuntimeMsg := node.Directives[compiler.BindDirective] if hasRuntimeMsg && len(runtimeMsgArgs) != 1 { return src.Node{}, src.Interface{}, &compiler.Error{ Err: ErrBindDirectiveArgs, @@ -158,7 +158,7 @@ func (a Analyzer) getResolvedNodeInterface( //nolint:funlen return entity.Interface, nil } - runtimeFuncArgs, isRuntimeFunc := entity.Component.Directives[compiler.RuntimeFuncDirective] + runtimeFuncArgs, isRuntimeFunc := entity.Component.Directives[compiler.ExternDirective] if hasRuntimeMsg && !isRuntimeFunc { return src.Interface{}, &compiler.Error{ @@ -178,7 +178,7 @@ func (a Analyzer) getResolvedNodeInterface( //nolint:funlen iface := entity.Component.Interface - _, hasStructInportsDirective := entity.Component.Directives[compiler.StructInports] + _, hasStructInportsDirective := entity.Component.Directives[compiler.AutoportsDirective] if !hasStructInportsDirective { return iface, nil diff --git a/internal/compiler/contract.go b/internal/compiler/contract.go index 9c5eeabc..6ee78731 100644 --- a/internal/compiler/contract.go +++ b/internal/compiler/contract.go @@ -8,9 +8,9 @@ import ( ) const ( - RuntimeFuncDirective src.Directive = "extern" - RuntimeFuncMsgDirective src.Directive = "bind" - StructInports src.Directive = "struct_inports" + ExternDirective src.Directive = "extern" + BindDirective src.Directive = "bind" + AutoportsDirective src.Directive = "autoports" ) type ( @@ -45,4 +45,10 @@ type ( Backend interface { GenerateTarget(*ir.Program) ([]byte, error) } + + STDLib interface { + Emitter() src.Interface + Destructor() src.Interface + Blocker() src.Interface + } ) diff --git a/internal/compiler/desugarer/component.go b/internal/compiler/desugarer/component.go index 11a7bc8c..771d1932 100644 --- a/internal/compiler/desugarer/component.go +++ b/internal/compiler/desugarer/component.go @@ -45,7 +45,7 @@ func (d Desugarer) desugarComponent( //nolint:funlen continue } - _, ok := entity.Component.Directives[compiler.StructInports] + _, ok := entity.Component.Directives[compiler.AutoportsDirective] if !ok { desugaredNodes[nodeName] = node continue @@ -179,13 +179,23 @@ func (d Desugarer) handleConns( //nolint:funlen desugaredConns = append(desugaredConns, result.connToInsert) } - if conn.SenderSide.Const.Ref != nil { - result, err := d.handleConstSender(conn, scope) - if err != nil { - return handleConnsResult{}, err + if conn.SenderSide.Const != nil { //nolint:nestif + if conn.SenderSide.Const.Ref != nil { + result, err := d.handleConstRefSender(conn, scope) + if err != nil { + return handleConnsResult{}, err + } + nodesToInsert[result.emitterNodeName] = result.emitterNode + conn = result.desugaredConn + } else if conn.SenderSide.Const.Value != nil { + result, err := d.handleLiteralSender(conn, scope) + if err != nil { + return handleConnsResult{}, err + } + nodesToInsert[result.emitterNodeName] = result.emitterNode + conn = result.desugaredConn + constsToInsert[result.constName] = *conn.SenderSide.Const } - nodesToInsert[result.constNodeName] = result.constNode - conn = result.desugaredConstConn } desugaredConns = append(desugaredConns, conn) diff --git a/internal/compiler/desugarer/const_sender.go b/internal/compiler/desugarer/const_sender.go index c7048214..1d7ae9c5 100644 --- a/internal/compiler/desugarer/const_sender.go +++ b/internal/compiler/desugarer/const_sender.go @@ -2,6 +2,7 @@ package desugarer import ( "fmt" + "sync/atomic" "github.com/nevalang/neva/internal/compiler" src "github.com/nevalang/neva/pkg/sourcecode" @@ -13,53 +14,123 @@ var emitterComponentRef = src.EntityRef{ Name: "Emitter", } -type handleConstSenderResult struct { - desugaredConstConn src.Connection - constNodeName string - constNode src.Node +type handleLiteralSenderResult struct { + handleConstRefSenderResult // conceptually incorrrect but convenient to reuse + // constant src.Const + constName string } -func (d Desugarer) handleConstSender(conn src.Connection, scope src.Scope) (handleConstSenderResult, *compiler.Error) { - constTypeExpr, err := d.getConstType(*conn.SenderSide.Const.Ref, scope) +type handleConstRefSenderResult struct { + desugaredConn src.Connection + emitterNodeName string + emitterNode src.Node +} + +// In the future compiler can operate in concurrently +var litSendersCount atomic.Uint32 + +func (d Desugarer) handleLiteralSender( + conn src.Connection, + scope src.Scope, +) ( + handleLiteralSenderResult, + *compiler.Error, +) { + counter := litSendersCount.Load() + litSendersCount.Store(counter + 1) + constName := fmt.Sprintf("literal-%d", counter) + + // we can't call d.handleConstRefSender() + // because our virtual const isn't in the scope + + emitterNodeName := "$" + constName + emitterNode := src.Node{ + Directives: map[src.Directive][]string{ + compiler.BindDirective: {constName}, + }, + EntityRef: emitterComponentRef, + TypeArgs: []ts.Expr{ + conn. + SenderSide. + Const. + Value. + TypeExpr, + }, + } + emitterNodeOutportAddr := src.PortAddr{ + Node: emitterNodeName, + Port: "msg", + } + + return handleLiteralSenderResult{ + constName: constName, + handleConstRefSenderResult: handleConstRefSenderResult{ + desugaredConn: src.Connection{ + SenderSide: src.ConnectionSenderSide{ + PortAddr: &emitterNodeOutportAddr, + Selectors: conn.SenderSide.Selectors, + Meta: conn.SenderSide.Meta, + }, + ReceiverSide: conn.ReceiverSide, + Meta: conn.Meta, + }, + emitterNodeName: emitterNodeName, + emitterNode: emitterNode, + }, + }, nil +} + +func (d Desugarer) handleConstRefSender( + conn src.Connection, + scope src.Scope, +) ( + handleConstRefSenderResult, + *compiler.Error, +) { + constTypeExpr, err := d.getConstTypeByRef(*conn.SenderSide.Const.Ref, scope) if err != nil { - return handleConstSenderResult{}, compiler.Error{ - Err: fmt.Errorf("Unable to get constant type by reference '%v'", *conn.SenderSide.Const.Ref), + return handleConstRefSenderResult{}, compiler.Error{ + Err: fmt.Errorf( + "Unable to get constant type by reference '%v'", + *conn.SenderSide.Const.Ref, + ), Location: &scope.Location, Meta: &conn.SenderSide.Const.Ref.Meta, }.Merge(err) } constRefStr := conn.SenderSide.Const.Ref.String() - constNodeName := fmt.Sprintf("__%v__", constRefStr) - constNode := src.Node{ + + emitterNodeName := "$" + constRefStr + emitterNode := src.Node{ Directives: map[src.Directive][]string{ - compiler.RuntimeFuncMsgDirective: {constRefStr}, + compiler.BindDirective: {constRefStr}, }, EntityRef: emitterComponentRef, TypeArgs: []ts.Expr{constTypeExpr}, } - constNodeOutportAddr := src.PortAddr{ - Node: constNodeName, - Port: "v", + emitterNodeOutportAddr := src.PortAddr{ + Node: emitterNodeName, + Port: "msg", } - return handleConstSenderResult{ - desugaredConstConn: src.Connection{ + return handleConstRefSenderResult{ + desugaredConn: src.Connection{ SenderSide: src.ConnectionSenderSide{ - PortAddr: &constNodeOutportAddr, + PortAddr: &emitterNodeOutportAddr, Selectors: conn.SenderSide.Selectors, Meta: conn.SenderSide.Meta, }, ReceiverSide: conn.ReceiverSide, Meta: conn.Meta, }, - constNodeName: constNodeName, - constNode: constNode, + emitterNodeName: emitterNodeName, + emitterNode: emitterNode, }, nil } -// getConstType is needed to figure out type parameters for Const node -func (d Desugarer) getConstType(ref src.EntityRef, scope src.Scope) (ts.Expr, *compiler.Error) { +// getConstTypeByRef is needed to figure out type parameters for Const node +func (d Desugarer) getConstTypeByRef(ref src.EntityRef, scope src.Scope) (ts.Expr, *compiler.Error) { entity, _, err := scope.Entity(ref) if err != nil { return ts.Expr{}, &compiler.Error{ @@ -78,7 +149,7 @@ func (d Desugarer) getConstType(ref src.EntityRef, scope src.Scope) (ts.Expr, *c } if entity.Const.Ref != nil { - expr, err := d.getConstType(*entity.Const.Ref, scope) + expr, err := d.getConstTypeByRef(*entity.Const.Ref, scope) if err != nil { return ts.Expr{}, compiler.Error{ Location: &scope.Location, diff --git a/internal/compiler/desugarer/struct_selectors.go b/internal/compiler/desugarer/struct_selectors.go index c9586f71..eaaba4f1 100644 --- a/internal/compiler/desugarer/struct_selectors.go +++ b/internal/compiler/desugarer/struct_selectors.go @@ -67,7 +67,7 @@ func (d Desugarer) desugarStructSelectors( //nolint:funlen selectorNode := src.Node{ Directives: map[src.Directive][]string{ // pass selectors down to component through the constant via directive - compiler.RuntimeFuncMsgDirective: {constName}, + compiler.BindDirective: {constName}, }, EntityRef: selectorNodeRef, TypeArgs: src.TypeArgs{lastFIeldType}, // specify selector node's outport type (equal to the last selector) @@ -190,7 +190,7 @@ func (d Desugarer) getSenderType( var selectorNodeTypeArg ts.Expr if senderSide.Const.Ref != nil { var err *compiler.Error - selectorNodeTypeArg, err = d.getConstType(*senderSide.Const.Ref, scope) + selectorNodeTypeArg, err = d.getConstTypeByRef(*senderSide.Const.Ref, scope) if err != nil { return ts.Expr{}, err } diff --git a/internal/compiler/irgen/runtime_func.go b/internal/compiler/irgen/runtime_func.go index 004b1fae..67598e87 100644 --- a/internal/compiler/irgen/runtime_func.go +++ b/internal/compiler/irgen/runtime_func.go @@ -11,7 +11,7 @@ import ( ) func getRuntimeFunc(component src.Component, nodeTypeArgs []ts.Expr) (string, error) { - args, ok := component.Directives[compiler.RuntimeFuncDirective] + args, ok := component.Directives[compiler.ExternDirective] if !ok { return "", nil } @@ -32,7 +32,7 @@ func getRuntimeFunc(component src.Component, nodeTypeArgs []ts.Expr) (string, er } func getRuntimeFuncMsg(node src.Node, scope src.Scope) (*ir.Msg, *compiler.Error) { - args, ok := node.Directives[compiler.RuntimeFuncMsgDirective] + args, ok := node.Directives[compiler.BindDirective] if !ok { return nil, nil } diff --git a/internal/compiler/parser/listener.go b/internal/compiler/parser/listener.go index 6cac5045..b5dc4be0 100644 --- a/internal/compiler/parser/listener.go +++ b/internal/compiler/parser/listener.go @@ -48,7 +48,7 @@ func (s *treeShapeListener) EnterTypeStmt(actx *generated.TypeStmtContext) { if single != nil { typeDef := single.TypeDef() parsedEntity := parseTypeDef(typeDef) - parsedEntity.IsPublic = single.PUB_KW() != nil + parsedEntity.IsPublic = single.PUB_KW() != nil //nolint:nosnakecase name := typeDef.IDENTIFIER().GetText() s.file.Entities[name] = parsedEntity return @@ -57,7 +57,7 @@ func (s *treeShapeListener) EnterTypeStmt(actx *generated.TypeStmtContext) { group := actx.GroupTypeStmt() for i, typeDef := range group.AllTypeDef() { parsedEntity := parseTypeDef(typeDef) - parsedEntity.IsPublic = group.PUB_KW(i) != nil + parsedEntity.IsPublic = group.PUB_KW(i) != nil //nolint:nosnakecase name := typeDef.IDENTIFIER().GetText() s.file.Entities[name] = parsedEntity } @@ -96,7 +96,7 @@ func parseTypeDef(actx generated.ITypeDefContext) src.Entity { func (s *treeShapeListener) EnterSingleConstStmt(actx *generated.SingleConstStmtContext) { constDef := actx.ConstDef() parsedEntity := parseConstDef(constDef) - parsedEntity.IsPublic = actx.PUB_KW() != nil + parsedEntity.IsPublic = actx.PUB_KW() != nil //nolint:nosnakecase name := constDef.IDENTIFIER().GetText() s.file.Entities[name] = parsedEntity } @@ -104,7 +104,7 @@ func (s *treeShapeListener) EnterSingleConstStmt(actx *generated.SingleConstStmt func (s *treeShapeListener) EnterGroupConstStmt(actx *generated.GroupConstStmtContext) { for i, constDef := range actx.AllConstDef() { parsedEntity := parseConstDef(constDef) - parsedEntity.IsPublic = actx.PUB_KW(i) != nil + parsedEntity.IsPublic = actx.PUB_KW(i) != nil //nolint:nosnakecase name := constDef.IDENTIFIER().GetText() s.file.Entities[name] = parsedEntity } @@ -154,7 +154,7 @@ func (s *treeShapeListener) EnterInterfaceStmt(actx *generated.InterfaceStmtCont if single != nil { name := single.InterfaceDef().IDENTIFIER().GetText() s.file.Entities[name] = src.Entity{ - IsPublic: single.PUB_KW() != nil, + IsPublic: single.PUB_KW() != nil, //nolint:nosnakecase Kind: src.InterfaceEntity, Interface: parseInterfaceDef(single.InterfaceDef()), } @@ -165,7 +165,7 @@ func (s *treeShapeListener) EnterInterfaceStmt(actx *generated.InterfaceStmtCont name := interfaceDef.IDENTIFIER().GetText() s.file.Entities[name] = src.Entity{ - IsPublic: group.PUB_KW(i) != nil, + IsPublic: group.PUB_KW(i) != nil, //nolint:nosnakecase Kind: src.InterfaceEntity, Interface: parseInterfaceDef(interfaceDef), } @@ -180,7 +180,7 @@ func (s *treeShapeListener) EnterCompStmt(actx *generated.CompStmtContext) { if single != nil { compDef := single.CompDef() parsedCompEntity := parseCompDef(compDef) - parsedCompEntity.IsPublic = single.PUB_KW() != nil + parsedCompEntity.IsPublic = single.PUB_KW() != nil //nolint:nosnakecase parsedCompEntity.Component.Directives = parseCompilerDirectives( single.CompilerDirectives(), ) @@ -192,7 +192,7 @@ func (s *treeShapeListener) EnterCompStmt(actx *generated.CompStmtContext) { group := actx.GroupCompStmt() for i, compDef := range group.AllCompDef() { parsedCompEntity := parseCompDef(compDef) - parsedCompEntity.IsPublic = group.PUB_KW(i) != nil + parsedCompEntity.IsPublic = group.PUB_KW(i) != nil //nolint:nosnakecase parsedCompEntity.Component.Directives = parseCompilerDirectives( group.CompilerDirectives(i), ) diff --git a/internal/compiler/parser/parser_test.go b/internal/compiler/parser/parser_test.go index 43a17437..181c9f48 100644 --- a/internal/compiler/parser/parser_test.go +++ b/internal/compiler/parser/parser_test.go @@ -28,11 +28,11 @@ func TestParser_ParseFile_Directives(t *testing.T) { } } - #struct_inports + #autoports C3() () #extern(d5) - #struct_inports + #autoports C4() () } `) @@ -42,28 +42,28 @@ func TestParser_ParseFile_Directives(t *testing.T) { got, err := p.ParseFile(text) require.True(t, err == nil) - d1 := got.Entities["C1"].Component.Directives[compiler.RuntimeFuncDirective][0] + d1 := got.Entities["C1"].Component.Directives[compiler.ExternDirective][0] require.Equal(t, "d1", d1) c2 := got.Entities["C2"].Component - d2 := c2.Directives[compiler.RuntimeFuncDirective][0] + d2 := c2.Directives[compiler.ExternDirective][0] require.Equal(t, "d2", d2) - d3 := c2.Nodes["n1"].Directives[compiler.RuntimeFuncMsgDirective][0] + d3 := c2.Nodes["n1"].Directives[compiler.BindDirective][0] require.Equal(t, "d3", d3) - d4 := c2.Nodes["n2"].Directives[compiler.RuntimeFuncMsgDirective][0] + d4 := c2.Nodes["n2"].Directives[compiler.BindDirective][0] require.Equal(t, "d4", d4) c3 := got.Entities["C3"].Component - _, ok := c3.Directives[compiler.StructInports] + _, ok := c3.Directives[compiler.AutoportsDirective] require.Equal(t, true, ok) c4 := got.Entities["C4"].Component - d5, ok := c4.Directives[compiler.RuntimeFuncDirective] + d5, ok := c4.Directives[compiler.ExternDirective] require.Equal(t, true, ok) require.Equal(t, "d5", d5[0]) - _, ok = c4.Directives[compiler.StructInports] + _, ok = c4.Directives[compiler.AutoportsDirective] require.Equal(t, true, ok) }