Skip to content

Commit

Permalink
feat(desugarer): implement literal senders
Browse files Browse the repository at this point in the history
  • Loading branch information
emil14 committed Feb 7, 2024
1 parent 7455d8a commit 7c15b57
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 80 deletions.
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
35 changes: 12 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/analyzer/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
6 changes: 3 additions & 3 deletions internal/compiler/analyzer/component_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand All @@ -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
Expand Down
12 changes: 9 additions & 3 deletions internal/compiler/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -45,4 +45,10 @@ type (
Backend interface {
GenerateTarget(*ir.Program) ([]byte, error)
}

STDLib interface {
Emitter() src.Interface
Destructor() src.Interface
Blocker() src.Interface
}
)
24 changes: 17 additions & 7 deletions internal/compiler/desugarer/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
115 changes: 93 additions & 22 deletions internal/compiler/desugarer/const_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package desugarer

import (
"fmt"
"sync/atomic"

"github.com/nevalang/neva/internal/compiler"
src "github.com/nevalang/neva/pkg/sourcecode"
Expand All @@ -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{
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions internal/compiler/desugarer/struct_selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions internal/compiler/irgen/runtime_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
Loading

0 comments on commit 7c15b57

Please sign in to comment.