+
Source Code
+
This section provides an overview of Nevalang, focusing on its user and compiler perspectives, excluding the type-system which is covered separately. It doesn’t delve into the execution details of Nevalang programs, but rather explores the abstractions present in the source code and their governing principles.
+
Build
+
Build is set of Nevalang modules. Every module has unique module reference. One of the modules is entry module.
+
Module
+
Module is a set of packages. Every module has its own manifest file.
+
Entry Module
+
Entry module is a root module for compilation. Every entry module must have at least one executable package.
+
Module Manifest
+
File that describes which version of language this module supports and list of its dependencies.
+
Module Dependencies
+
Every module except std
has dependencies, at least std
. Module defines dependencies by specifing dependend module’s path and version. Every dependency module can have local alias.
+
Package
+
Package is a set of files located in the same directory. Name of the package is the path to its directory from module’s root. All entities in a package forms single namespace so they must have unique names across package. An entity can refer to entities described in other files in the same package without imports.
+
Executable Package
+
Package without public entities and with main component
+
File
+
File is a set of imports and entities. Unlike package file is not a namespace itself, but imports declared inside one file are not visible inside another. There’s no restriction on how one should group entities in files inside a package.
+
Imports
+
Imports allow to use entities defined in other packages. Imports declared in one file are not visible inside another. Import consist of module reference and package name. E.g. std:http/net
is an import of package http/net
from module std
. Only public entities can be imported.
+
Entities
+
Entities are abstractions for creating programs. They are either private or public. They are private by default and can be made public by using pub
keyword. Every entity has name that is unique across the package. Entities are referenced by entity references.
+
There are four kinds of entities (from simple to complex):
+
+- Type
+- Interface
+- Constant
+- Component
+
+
Entity Reference
+
Entity reference consist of an optional package name and name of the referenced entity. Package name can be omitted if entity that we reference either exist in the same package or in std/builtin
. If entity in current package has the same name as the one in the builtin, then it shadows it.
+
Type Entity
+
Type entity (type definition) consist of an optional list of type parameters followed by optional type expression that is called body.
+
Base Type
+
Type definition without body means base type. Compiler is aware of base types and will throw error if non-base type has no body. Base types are only allowed inside std/builtin
package. Some base types can be used inside recursive type definitions.
+
Recursive Type Definition
+
If type refers to itself inside its own definition, then it’s recursive definition. Example: type l list<l>
. In this case list
must be base type that supports recursive definitions. Compiler knows which types supports recursion and which do not.
+
Type Parameters (Generics)
+
Every type paremeter has name that must be unique across all other type parameters in this definition and constrant.
+
Type Parameter Constraint
+
Constraint is a type expression that is used as supertype to ensure type compatibility between type argument and type corresponding parameter. If no constrained explicitly defined then any
is implicitly used.
+
Type Parameters and Arguments Compatibility
+
Argument A
compatible with parameter P
if there’s subtyping relation of form A <: C
whereC
is a constraint of P
. If there’s several parameters/arguments, every one of them must be compatible. Count of arguments must always be equal to the count of parameters.
+
Type Expression
+
There is 2 kinds of type expressions:
+
+- Instantiation expressions
+- Literals expressions
+
+
Type expressions can be infinitely nested. Process of reducing the type expression called type resolving. Resolved type expression is an expression that cannot be reduced to a more simple form.
+
Type Instantiation Expression
+
Such expression consist of entity reference (that must refer to existing type definition or type parameter) and optional list of type arguments. Type arguments themselves are arbitrary type expressions (they follows the same rules described above).
+
Literal Type Expression
+
Type expressions that cannot be described in a instantiation form.
+
Interface Definition
+
Interface is a component signature that describes abstract component - its input and output ports and optional type parameters. Interfaces are used with dependency injection and abstract components.
+
Ports
+
Port definition consist of a type expression describing the data-type port expects and a flag that describes whether the port is an array or single port. Type expression can refer to interface’s type parameters. If no type paremeter given then any
is implicitly used.
+
Single Ports
+
Single port is port with one slot. Reference to such ports should not include slot index.
+
Array Ports
+
Array port is port with multiple (up to 255) slots. Such ports must be referenced either via slot indexes or in array-bypass connection expressions.
+
Constant
+
Constant is an entity that consist of either message or entity reference to other constant. Message can include references to other constants. Constant messages can be infinitely nested. Constants may refer imported constants from other packages. Components are only entities that can refer constants, that are not constants themselves - they can refer to constants via compiler directives and from their networks.
+
Component
+
Component always has interface and optional compiler directives, nodes and network. There are two kinds of components: normal and native ones.
+
Main Component
+
Executable package must have component called Main
. This component must follow specific set of rules:
+
+- Must be normal
+- Must be private
+- Must have exactly 1 inport
start
+- Must have exactly one outport
stop
+- Both ports must have type
any
+- Must have no abstract nodes
+
+
Main component doesn’t have to have network but it is usually the case because it’s the root component in the program.
+
Native Components
+
Component without implementation (without nodes and network) must use #extern
directive to refer to runtime function. Such component called native component. Native components normally only exist inside std
module, but there should be no forced restriction for that.
+
Normal Component
+
Normal component is implemented in source code i.e. it has not only interface but also nodes and network, or at least just network. Normal components must never use #extern
directive.
+
Nodes
+
Nodes are things that have inports and outports that can be connected in network. There’s two kinds of nodes:
+
+- IO Nodes
+- Computational nodes
+
+
IO nodes are created implicitly. Every component have one in
and one out
node. Node in
has outports corresponding to component’s interface’s inports. And vice versa - out
node has inports corresponding to component interface’s inports.
+
Computational nodes are nodes that are instantiated from entities - components or interfaces. There’s 2 types of computational nodes: concrete and abstract. Nodes that are instantiated from components are concrete nodes and those that instantiated from interfaces are abstract nodes.
+
Interfaces and component’s interfaces can have type parameters. In this case node must specify type arguments in instantiation expression.
+
Dependency Injection (DI)
+
Normal component can have abstract node that is instantiated from an interface instead of a component. Such components with abstract nodes needs what’s called dependency injection.
+
I.e. if a component has dependency node n
instantiated with interface I
one must provide concrete component that implements this interface.
+
Dependency Injection can be infinitely nested. Component Main
cannot use dependency injection.
+
Component and Interface Compatability (Implementation)
+
Component implements interface (is compatible with it) if type paremeters, inports and outports are compatible.
+
Type parameters are compatible if their count, order and names are equal. Constraints of component’s type parameters must be compatible with the constraints of the corresponding interface’s type parameter’s constraints.
+
Component’s inports are compatible with the interface’s if:
+
+- Amount is exactly equal
+- They have exactly the same names and kind (array or single)
+- Their types are compatible (are subtypes of) with the corresponding interface’s inports
+
+
Outports of a component are compatible with the interface’s if:
+
+- Amount is equal or more (this is only difference with inports)
+- Exactly the same names and kind
+- Their types are compatible
+
+
Network
+
Network is a set of connections. Every connection consist of sender-side and receiver-side. Sender and receiver must be compatible. There is 2 types of connections: normal and array-bypass.
+
Normal Connection
+
Normal connection can have several types of sender-side and receiver-side.
+
Sender-side:
+
+- Port address (traditional)
+- Constant reference
+- Primitive message literal
+
+
Receiver-side:
+
+- List of inport-addresses
+- List of deferred connections
+
+
Sender-side in of a normal connection can also have optional struct selectors.
+
Sender-Side Struct Selectors
+
If (resolved) type of sender-side is structure, then it’s possible to have selectors in it. Selectors are list of strings, where each element means field in a struct. More than one selector means that there is a structure inside structure. Selectors must be type safe. I.e. it must be possible to “unwrap” structure each time we process next selector.
+
Port Address
+
Port Address consist of name of the node, name of the port and optional index of the slot. Slot index must be present only if port address refers to array-port.
+
Constant Reference Sender
+
In normal connection not just port address but also reference to constant entity (that must be available in the scope) could be a sender. This works exactly like if there’s emitter sender with bound constant.
+
Primitive Message Literal Sender
+
This works almost like constant reference sender except instead of referencing some constant we simply use message literal. Only primitive data-types are supported: booleans, numbers, strings and enum members.
+
Array-Bypass Connection
+
Connection that connects all slots of some sender with all slots of some receiver. Sender and receiver must both be array-ports. Component is only allowed to bypass it’s own inports. Such connection always consist of two port addresses without slot indexes.
+
Bound Constant
+
Constant that is referenced inside bind
compiler directive
+
Compiler Directive
+
Special instructions for compiler. Directives that must be supported by the compiler are #extern
, #autoports
and #bind
.
+
Runtime Function Overloading
+
Native components pass several arguments to #extern
directive to utilize overloading. In this case arguments are pairs separated by whitespace, they have form #extern(t1 f1, t2 f2, ... tN fN)
where t
is a type and f
is the name of a runtime function.
+
Component that uses overloading must have exactly one type parameter (it’s name doesn’t matter) of type union
. Types that are referenced inside directive must be members of that union.
+
+