Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(std): rename operators/reducers ports nameing from acc/el to lef… #745

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
05737ab
feat(std): rename operators/reducers ports nameing from acc/el to lef…
emil14 Nov 2, 2024
1fb78fa
feat(parser): switch
emil14 Nov 5, 2024
cd39352
feat(parser:net): switch
emil14 Nov 5, 2024
82717aa
feat(parser:tests): switch
emil14 Nov 6, 2024
0a6b7da
feat(analyzer:network): switch
emil14 Nov 6, 2024
ba1577a
feat(desugarer:network): switch
emil14 Nov 6, 2024
4c94c0a
feat(std): while, pass and relay
emil14 Nov 7, 2024
1e4d3eb
feat(examples:99_bottles): rewamped with while and relay
emil14 Nov 7, 2024
31e87f8
feat(compiler,examples,e2e,docs): switch default case
emil14 Nov 10, 2024
7db24f1
feat(std,examples:99bottles): re-implement `For` with back-pressure t…
emil14 Nov 10, 2024
58fbf9b
fix(examples/99_bottles): using `For` more properly
emil14 Nov 10, 2024
34ee946
fix(parser): `?` is last part of node expr now
emil14 Nov 10, 2024
c61e720
feat(std): add err outport to println and printf, also create fmt pkg…
emil14 Nov 10, 2024
176cd12
tmp(std,examples): remove err outport from For and Println
emil14 Nov 11, 2024
6f449ce
fix(e2e,examples): add import of fmt pkg
emil14 Nov 11, 2024
aa7bad6
fix(e2e,examples): imports
emil14 Nov 12, 2024
4e62fde
fix(compiler): several fixes after adding switch
emil14 Nov 12, 2024
dd139a2
feat(compiler): make switch default required; feat(std): relay -> tap…
emil14 Nov 13, 2024
4ac9c05
fix(analyzer:network:port_addr): do not return resolved port-addr (in…
emil14 Nov 13, 2024
8a8decf
fix: multiple fixes in examples and e2e tests
emil14 Nov 13, 2024
f01ff2c
fix(parser:range): parsing of negative members
emil14 Nov 13, 2024
ad70368
fix(parser): return error from `walkTree` func, even if it's not *com…
emil14 Nov 14, 2024
ba82d1e
fix(e2e,examples): add missing fmt import, type params (soon they wil…
emil14 Nov 14, 2024
b6fabdf
fix(e2e,examples): everything fixed except filter list example
emil14 Nov 14, 2024
af72936
fix(examples:filter_list): return accidentially removed implementatio…
emil14 Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"mode": "auto",
"program": "${workspaceFolder}/cmd/neva",
"cwd": "${workspaceFolder}/examples",
"args": ["run", "add_numbers"]
"args": ["run", "--trace", "99_bottles"]
},
{
"name": "DEBUG CODEGEN",
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,16 @@ Hello, World!
If you open `my_awesome_project/src/main.neva` with your favorite IDE you'll see this

```neva
import { fmt }

def Main(start) (stop) {
Println
fmt.Println
---
:start -> { 'Hello, World!' -> println -> :stop }
}
```

The `Main` component has `start` inport and `stop` outport, with a `println` node (instance of stdlib's `Println`). The network after `---` shows: on `start` message, `"Hello, World!"` is sent to `println`, then program terminates via `stop` signal.
The `import { fmt }` statement imports the standard library's `fmt` package which provides common formatting and printing functionality. The `Main` component has `start` inport and `stop` outport, with a `println` node (instance of stdlib's `fmt.Println`). The network after `---` shows: on `start` message, `"Hello, World!"` is sent to `println`, then program terminates via `stop` signal.

### What's Next?

Expand Down
2 changes: 1 addition & 1 deletion docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Overloaded native components use a modified extern directive: `#extern(t1 f1, t2

```neva
#extern(int int_add, float float_add, string string_add)
pub def Add<T int | float | string>(acc T, el T) (res T)
pub def Add<T int | float | string>(left T, right T) (res T)
```

Usage:
Expand Down
8 changes: 4 additions & 4 deletions docs/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const one int = 1
def Inc(data int) (res int) {
Add
---
$one -> add:acc
:data -> add:el
$one -> add:left
:data -> add:right
add -> :res
}
```
Expand All @@ -43,8 +43,8 @@ You can omit explicit constants; the compiler will create and refer to them impl
def Inc(data int) (res int) {
Add
---
1 -> add:acc
:data -> add:el
1 -> add:left
:data -> add:right
add -> :res
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Native components can be overloaded using `#extern(t1 f1, t2 f2, ...)`. These co

```neva
#extern(int int_add, float float_add, string string_add)
pub def Add<T int | float | string>(acc T, el T) (res T)
pub def Add<T int | float | string>(left T, right T) (res T)
```

## `#bind`
Expand Down
16 changes: 8 additions & 8 deletions docs/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,34 @@ Interfaces in Nevalang are used for:
`any` is too generic; specific types are often needed. Let's define an interface for an integer adder:

```neva
interface IAdd(acc int, el int) (res int)
interface IAdd(left int, right int) (res int)
```

What if we want to add not just integers but also support `float` and `string`? You could use `union` for that

```neva
type addable int | float | string
interface IAdd(acc addable, el addable) (res addable)
interface IAdd(left addable, right addable) (res addable)
```

This solution is problematic because it allows mixing types, e.g., `int` for `:acc` and `float` for `:el`. The `:res` message will have a union type `int | float | string`, making the code complex. To maintain type-safety, use type-parameters in the interface definition:
This solution is problematic because it allows mixing types, e.g., `int` for `:left` and `float` for `:right`. The `:res` message will have a union type `int | float | string`, making the code complex. To maintain type-safety, use type-parameters in the interface definition:

```neva
interface IAdd<T>(acc T, el T) (res T)
interface IAdd<T>(left T, right T) (res T)
```

This ensures `:acc` and `:el` receive compatible types, and `:res` matches their type. However, our current definition allows any type, including `IAdd<bool, bool>`, which we don't want. To fix this, we can explicitly constrain `T` using our union:
This ensures `:left` and `:right` receive compatible types, and `:res` matches their type. However, our current definition allows any type, including `IAdd<bool, bool>`, which we don't want. To fix this, we can explicitly constrain `T` using our union:

```neva
interface IAdd<T int | float | string>(acc T, el T) (res T)
interface IAdd<T int | float | string>(left T, right T) (res T)
```

Now only `IAdd<int, int>`, `IAdd<float, float>` or `IAdd<string, string>` (and their compatible variants) are possible. `IAdd:res` will always be `int`, `float`, or `string`.

Type-expressions in interface definitions follow type-system rules, so you can pass `T` to other type-expressions. Example:

```
interface IAppend<T>(lst list<T>, el T) (list<T>)
interface IAppend<T>(lst list<T>, right T) (list<T>)
```

These expressions can be complex and nested, but we'll keep this section brief.
Expand All @@ -63,7 +63,7 @@ These expressions can be complex and nested, but we'll keep this section brief.
Let's return to the original `IAdd` without type-parameters for simplicity

```neva
interface IAdd(acc int, el int) (res int)
interface IAdd(left int, right int) (res int)
```

It's suitable for combining 2 sources, but what if we need to combine any number of sources? Chaining multiple `IAdd` instances is tedious and sometimes impossible. Let's look at the array-inports solution:
Expand Down
78 changes: 71 additions & 7 deletions docs/networks.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@ Each connection always has a sender and receiver side. There are 3 types of each

### Sender Side

There are 3 sender-side forms:
There are 5 sender-side forms:

1. Port address
2. Constant reference
3. Message literal
4. Binary expression
5. Ternary expression

#### Port Address Sender

Expand All @@ -152,21 +154,21 @@ It acts as an infinite loop, repeatedly sending the same message at the receiver

One way to work with them is to have a node with multiple inports, where at least one is connected to a port, not a constant. This limits the constants' speed to that of the port. Here's a simple example:

We'll create a component that increments a number using an addition component with 2 inports: `:acc` and `:el`. We'll use a constant `$one` for `:acc`, while `:el` receives dynamic values:
We'll create a component that increments a number using an addition component with 2 inports: `:left` and `:right`. We'll use a constant `$one` for `:left`, while `:right` receives dynamic values:

```neva
const one int = 1

def Inc(data int) (res int) {
Add
---
$one -> add:acc
:data -> add:el
$one -> add:left
:data -> add:right
add -> :res
}
```

In this example, `add:acc` and `add:el` are synchronized. When `:data -> add:el` has a message, `add:acc` can receive. If the parent of `Inc` sends `1, 2, 3`, `add` will receive `acc=1 el=1; acc=1 el=2; acc=1 el=3` and produce `2, 3, 4` respectively.
In this example, `add:left` and `add:right` are synchronized. When `:data -> add:right` has a message, `add:left` can receive. If the parent of `Inc` sends `1, 2, 3`, `add` will receive `left=1 right=1; left=1 right=2; left=1 right=3` and produce `2, 3, 4` respectively.

Another way to synchronize constants with real data is to use deferred connections. We'll explore this in the receiver-side forms section.

Expand Down Expand Up @@ -205,8 +207,8 @@ Sometimes it's convenient to refer to message values directly in the network wit
def Inc(data int) (res int) {
Add
---
1 -> add:acc
:data -> add:el
1 -> add:left
:data -> add:right
add -> :res
}
```
Expand All @@ -218,6 +220,68 @@ Only primitive data-types (`bool`, `int`, `float`, `string` and `enum`) can be u
- float: `42.0 -> ...`
- enum: `Day::Friday ->`

#### Binary Expression

#### Binary Expression

Binary expression is an easy way to perform arithmetic, comparison, logic or bitwise operation with two operands. Syntax of binary expression is infix notation with binary operator in the middle and operands on left and right. Binary expression is always wrapped in `()` braces (so there's no precedence in Nevalang).

Examples:

```neva
// arithmetic
(5 + 3) -> println // addition: outputs 8
(5 - 3) -> println // subtraction: outputs 2
(5 * 3) -> println // multiplication: outputs 15
(6 / 2) -> println // division: outputs 3
(7 % 3) -> println // modulo: outputs 1
(2 ** 3) -> println // power: outputs 8

// comparison
(5 == 5) -> println // equal: outputs true
(5 != 3) -> println // not equal: outputs true
(5 > 3) -> println // greater than: outputs true
(5 < 8) -> println // less than: outputs true
(5 >= 5) -> println // greater or equal: outputs true
(5 <= 8) -> println // less or equal: outputs true

// logic
(true && true) -> println // AND: outputs true
(true || false) -> println // OR: outputs true

// bitwise
(5 & 3) -> println // AND: outputs 1
(5 | 3) -> println // OR: outputs 7
(5 ^ 3) -> println // XOR: outputs 6
```

> Bitwise left and right shifts (`<<` and `>>`) are not yet implemented.

Operands of a binary-expression are senders themselves. In example above they are message literals but they could be any senders:

```neva
(5 + node:port)
($some_const && false)
(someNode == 'some string')
```

Binary expressions could be infinetely nested. Example: `((a + b) * (c - d)) -> receiver`.

Both operands (their resolved versions) must be of the same type. Type of a valid binary expression is the same as types of its operands. Type-compatibility between binary expression sender and its receiver-side is resolved the same way as with any other sender and receiver sides.

#### Tenrary Expression

Similar to binary, but there are 3 operands instead of 2 and first operand is always of `bool` type. Just like binary, ternary expression sender is always wrapped into `()` braces, supports any senders as operands and could be infintely nested (and mixed with binary expressions). Examples:

```neva
(a ? : b : c) // simple
(a + (b ? c : d)) // as operand in binary expression
(cond ? (a + b) : (c * d)) // with binary expressions as brances
((a == b) ? c : d) // with binary expression as condition
```

Compiler with ensure that condition operand resolves to `bool` type and that both branch-operands are compatible with the receiver-side.

### Receivers Side

There are 3 types of receiver side:
Expand Down
18 changes: 7 additions & 11 deletions docs/program_structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ neva: 0.26.0
deps:
github.com/nevalang/x:
path: github.com/nevalang/x
version: 0.0.13
version: 0.0.14
```

The `deps` field is a map where each dependency has an alias. When adding dependencies via CLI (e.g., `neva get github.com/nevalang/x`), the package manager automatically inserts a key-value pair. Third-party dependencies must have a valid git-clone path and a fixed semver version. The package manager uses git to download the repo and looks for the corresponding git-tag. The alias typically defaults to the module's path, but custom aliases allow multiple versions of the same module:
Expand All @@ -55,7 +55,7 @@ neva: 0.26.0
deps:
github.com/nevalang/x@0-0-12:
path: github.com/nevalang/x
version: 0.0.13
version: 0.0.14
github.com/nevalang/x@0-0-11:
path: github.com/nevalang/x
version: 0.0.11
Expand All @@ -71,7 +71,7 @@ Module references uniquely identify modules in a build, used by the compiler to

```yaml
path: github.com/nevalang/x
version: 0.0.13
version: 0.0.14
```

### Entry Module
Expand Down Expand Up @@ -287,12 +287,8 @@ pub const p int = 3.14
// foo/bar/bar.neva
import { @/foo }

pub def AddP(el float) (res float) {
Add<float>
---
$foo.p -> add:acc
:el -> add:el
add -> :res
pub def AddP(data float) (res float) {
(:data + $foo.p) -> :res
}

// main/main.neva
Expand All @@ -304,7 +300,7 @@ import {
def Main(start) (stop) {
bar.AddP, Println
---
:start -> { $foo.p -> addP:el -> println -> :stop }
:start -> { $foo.p -> addP:right -> println -> :stop }
}
```

Expand All @@ -316,7 +312,7 @@ Third-party imports are imports of packages located in third-party modules - mod
deps:
github.com/nevalang/x:
path: github.com/nevalang/x
version: 0.0.13
version: 0.0.14
```

Then when you `import { github.com/nevalang/x }` compiler will know exactly path and version of the module you are referring to.
Expand Down
6 changes: 5 additions & 1 deletion docs/qna.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,8 @@ elseValue -> ternary:else
ternary:res -> println
```

And guess what? This is exactly how desugared `Ternary` component works! But these are 4 (!) connections! Compare it with `(condition ? thenValue : elseValue) -> println`.
And guess what? This is exactly how desugared `Ternary` component works! But these are 4 (!) connections! Compare it with `(condition ? thenValue : elseValue) -> println`.

## Why operators and reducers have `left` and `right` naming for ports?

Operators should follow same pattern for simplicity of desugarer and usage by user and they also also should be able to be used as reducers by `Reduce`. It means we need to choose between `left/right` which is convinient for operators and `acc/el` for reduce. Binary expressions (infix form) are more common than reduce operations so desicion was made to sacrifice reduce clarity a little bit.
16 changes: 8 additions & 8 deletions docs/style_guide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Style Guide

This guide sets standards for Nevalang code organization, formatting, and naming conventions to ensure consistency and readability.
This guide sets standards for Nevalang code to ensure consistency and readability.

## Formatting

Expand All @@ -10,7 +10,6 @@ Keep lines under 80 characters.

- Comfortable for split-screen viewing
- Accommodates larger font sizes without horizontal scrolling
- Better for visual accessibility
- Leaves room for IDE features (code lens, git blame, inline-hints, etc.)
- Enables reading full lines with eye movement alone

Expand All @@ -19,7 +18,6 @@ Keep lines under 80 characters.
Use tabs over spaces.

- Tabs allow users to customize indentation width according to their preferences
- More accessible for users with visual impairments who may need larger indentation
- Tabs are more efficient in terms of file size

### Imports
Expand All @@ -33,17 +31,19 @@ Names should inherit context from parent scope. Good naming eliminates need for
- **Packages/Files**: lower_snake_case up to 3 words
- **Types**: CamelCase up to 3 words
- **Interfaces**: CamelCase with `I` prefix up to 3 words
- **Constants**: lowerCase up to 3 words
- **Constants**: lower_snake_case up to 3 words
- **Components**: CamelCase noun up to 3 words
- **Nodes**: lowerCamelCase up to 3 words
- **Ports**: lowercase, 1 word up to 5 letters

## Interfaces

- Use type-parameters when need to preserve type information between input and output.
- Limit to 3 inports and outports max.
- Use outports to separate data flows, not for destructuring
- Use `data` for input with payload, `sig` for input without payload (trigger), `res` for output with payload, `sig` for output without payload (success), and `err` for failures.
- `err` outport must be `error` type. `sig` inport must be `any` type for flexibility. For outports, `sig` should be `struct{}` in concrete components and `any` in interfaces. Never use `any` for `res` outport.
- Don't send input data downstream - parent already knows it
- Use type-parameters when need to preserve type info between input and output

## Networks

- Prefer simple topologies over complex networks.
- Omit port names when possible
- Omit port names when possible. It enables renaming of ports without updating the networks.
Loading
Loading