Skip to content

Commit

Permalink
fix(parser): compiler errs after antlr g4 fix
Browse files Browse the repository at this point in the history
  • Loading branch information
emil14 committed Jan 29, 2024
1 parent db7ecff commit 5c8078c
Show file tree
Hide file tree
Showing 24 changed files with 1,448 additions and 1,830 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
# Neva
# Neva Programming Language

![assets/header.png](assets/header.png)
**Neva** is a general purpose [flow-based](https://en.wikipedia.org/wiki/Flow-based_programming) programming language with static [structural](https://en.wikipedia.org/wiki/Structural_type_system) typing and [implicit parallelism](https://en.wikipedia.org/wiki/Implicit_parallelism) that compiles to machine code. Designed with [visual programming](https://en.wikipedia.org/wiki/Visual_programming_language) in mind. For the era of effortless concurrency.

> On shore abandoned, kissed by wave, he stood, of mighty thoughts the slave.
> WARNING: This project is under heavy development and not production ready yet.
**Neva** (Nevalang) is a general purpose ([flow-based](https://en.wikipedia.org/wiki/Flow-based_programming)) compiled programming language with static [structural](https://en.wikipedia.org/wiki/Structural_type_system) typing and [implicit parallelism](https://en.wikipedia.org/wiki/Implicit_parallelism).
```neva
const {
greeting string 'Hello, World!'
}
Oh, and a [visual programming](https://en.wikipedia.org/wiki/Visual_programming_language) finally done right!
components {
Main(enter any) (exit any) {
nodes { printer Printer<string> }
net {
in:enter -> ($greeting -> printer:msg)
printer:msg -> out:exit
}
}
}
```

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md)

## Architecture

See [ARCHITECTURE.md](./ARCHITECTURE.md)

## FAQ

See [FAQ](./docs/faq.md)
See [FAQ.md](./docs/faq.md)
67 changes: 43 additions & 24 deletions docs/best_practises.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Best Practices
# Style Guide

> This section is under heavy development
Neva language has official style guide and it's described in this document.

## Code Organization

Expand Down Expand Up @@ -54,7 +54,7 @@ components {
}
```

### Between Entities
#### Between Entities

```neva
types {
Expand All @@ -66,7 +66,23 @@ types {

## Design

### Avoid Multiple Parents
### Use generics when necessary

You need generics (type parameters) when you need to preserve data type on the output side.

E.g. `Destructor` component doesn't have outports so it doesn't matter what you passed in. That's why `Destructor` accepts `any` instead of `T`.

On the other hand `Blocker` needs to know the type of `data` on the input so the type of the `data` on the output is preserved. That's why it's `Destructor<T>`.

### Separate downstream flow with outports

When you have _structured_ data data use `struct`, when you want to _separate flow_ - create outports.

Example: `NumParser` sends `res` or `err` but never both at the same time. It's case for outports.

On the other hand when you have e.g. two pieces of data `foo` and `bar`, but their firing condition is always the same - use `struct { foo T1, bar T2 }`. This way user of your component could simply use `struct selector` if needed.

### Avoid Multiple Parents When Possible

When designing component's networks prefer _Pipe_ or _Tree_ structure over graphs where single child have multiple parents. This makes network hard to understand visually. Sometimes it's impossible to avoid that though.

Expand All @@ -76,44 +92,39 @@ Try to keep the **number of inports no bigger than 3** and **outports no bigger

Sometimes we _pass data on_ - we use our outports to not only send the results but also to pass our inputs down the stream so downstream nodes can also use them without being connected to upstream nodes which makes network hard to read both visually and textually.

<!-- Outports are optional, inports are not. This means if you have 5 outports, user of your component might not use all of them. On the other hand if you have 3 or more inports - user of your component will be forced to use them all in order to compile the program. -->

## Naming Conventions

CamelCase is used everywhere except packages and constants.
CamelCase is used everywhere except for package names.

Names are rather short and always start with lowercase except entities.
Names are rather short, but their length depends on their scope.

For camelCase (both lower and upper) traditional style (not Go style) is used. For example it's `xmlHttpRequest` and not `xmlHTTPRequest`.

### Packages

Package names are in `lower_snake_case`. short 1-3 words, perfectly one word. Examples: `http` or `business_logic`. They inherit context from their path so it's `net/http` and not `net_http`.
Package names are short (1-3 words) `lower_snake_case` strings. Examples: `http` or `business_logic`. They inherit context from their path so it's `net/http` and not `net_http`.

### Entities

`CommonCamelCase` is used for types, interfaces and components and `UPPER_SNAKE_CASE` is used for constants.
`CommonCamelCase` is used for _types_, _interfaces_ and _components_ and `lowerCamelCase` for constants.

Entity names should be relatively long (up to 5 words) and descriptive. It's important
because other names (e.g. ports) must be short and names of the entities they represent will serve as a documentation.
Entity names can be relatively long (up to 3 words) and descriptive. It's important because port names must be short and names of the entities they represent will serve as a documentation.

Abbreviation is ok if there is a generally accepted one. Or the name turns out to be extremely long (more than 3 words).
Abbreviation ok if there is a generally accepted one. Or the name turns out to be extremely long (more than 3 words).

For example, `AsynchronousFileReader` is bad because there is generally accepted abbreviation for "Asynchronous", it's "async".

On the other hand `AsyncFRdr` is bad too because words "File" and "Reader" are short enough already and there is no need to shorten them. Besides there's no accepted abbreviation "F" and "Rdr" for them.

Perfect name would be `AsyncFileReader`.

Another bad example would be `GeneralPurposeReadonlyLinuxSocketStream`. It consist of 6 words which is too much, one of them must be omitted. `GeneralReadonlyLinuxSocketStream` is acceptable but given how long this name is, `GeneralReadLinuxSockStream` is better.

#### Types

No special rules for types. Examples: `User`, `UserId`, `OrderDetail`, `HttpResponse`, `DayOfWeek`, `ResponseCode`, `FileType`.
Types are generally CamelCase nouns. Examples: `User`, `UserId`, `OrderDetail`, `HttpResponse`, `DayOfWeek`, `ResponseCode`, `FileType`.

Enum elements are named exactly the same way: `{ Monday, Tuesday, ... }`.

Struct fields are named this way too except they start with the lower case:
Struct fields are named this way except they start with the lower case:

```neva
User struct {
Expand All @@ -132,25 +143,33 @@ Interfaces are named exactly like components except their names are prefixed wit

#### Constants

Examples: `DEFAULT_TIMEOUT`, `API_ENDPOINT`, `STOCK_MARKET_CLOSE_TIME`.
Constants are lowerCamelCase `defaultTimeout`, `apiEndpoint`, `stockMarketTime`.

### Ports

Use short (1-5 characters) names for ports.
Use short (up to 5 chars) names for ports.

Single-letter names are ok when it's obvious what they mean based on context. E.g. `f` is okay when it's type is `File`. You'll find many examples of `s` for `string`, `b` for `bool` or `l` for `list`, etc in stdlib.

Don't shorten names unnecessarily. For example `file` is better than `f` and `value` is bettern than `v`.
It's a good practice though to give meaningful names when possible e.g. `res` instead of `s` when it's not just string but rather result of some operation.

5 characters is not a lot though so you have to shorten most of the time if there's more then 1 word. So `userID` is too long and should be `uid`.
Also try to follow patterns from stdlib like `res, err`, `ok, miss`, `some, none`, etc.

And remember that 5 characters is not a lot so you have to shorten most of the time if there's more than 1 word. So `userID` is too long and should be `uid`.

It's important to have short port names because of the visual programming. When you work with graphs of nodes you'll see a lot of connections. It will become unreadable very quickly if there's a lot of ports per node and/or array ports involved.

### Nodes

Nodes are generally named exactly like components but in `lowerCamelCase`. E.g. for `FileReader` we would have `fileReader`.
Nodes are generally named exactly like their components but in `lowerCamelCase`. E.g. for `FileReader` we would have `fileReader`.

If node is abstract (it's instantiated with interface instead of component) then `I` prefix is omitted. So it's `reader IReader` and not `iReader`.

Except if we have several instances of the same component. Then we must stress out why there's several of them and what is the difference. For instance for a component that needs two Adder instances it could be:

```neva
nodes {
firstAdder Adder<int>
secondAdder Adder<int>
adder1 Adder<int>
adder2 Adder<int>
}
```
2 changes: 2 additions & 0 deletions docs/compiler_directives.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Compiler Directives

Compiler directives are special instructions for compiler. They are not intended to be used on a daily basis by regular user but good nevalang programmer must understand how they work because they are base for how many language features operate.

- `#runtime_func` - directive for _component_ that tells the compiler that given component does not have implementation in source code and instead a runtime function call must be created.
- `#runtime_func_msg` - Directive for _node_ that tells the compiler to insert given message to corresponding runtime function call. Can only be used with nodes that are instantiated with components with `runtime_func` directive.
- `#struct_inports` - tells the compiler that inports for this component are not defined in nevalang source code but instead must be derived from it's type-argument which is the structure. Inports will correspond to structure fields.
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
# Design Principles

## Program fails at startup or never
Nevalang is built on a set of principles. They were rather naturally derived from the development process rather artificially created beforehand.

**This is one of the final goals of the language. Please note that we are not there yet.**
> WARNING: Language is under heavy development and these principles are not guarantees we can give you at the moment, but rather guiding stars for us to keep moving in the right direction
## Program must fail at startup or never

The idea is that most of the errors must be caught by compiler at compile time. And the rest of them, that are hard to catch (without sacrificing compiler's simplicity) are checked in runtime at startup.

If no errors were caught at compile time and startup - then the program is correct and must run successfully. Any (non-logical) error that occurred after startup must be threated like compiler bug.

## Runtime fast, flexible and unsafe
## Runtime must be fast, flexible and unsafe

Runtime won't do any checks after startup. The program that runtime consumes must be correct. Program's correctness must be ensured by compiler. If there's a bug in compiler and runtime consumed invalid program - bad things can happen: deadlocks, memory leaks, freezes and crashes.

## Compiler directives are not for users
## Compiler directives must not be required

Language must allow to implement everything without using of compiler directives.

**Compiler directives are not always unsafe** (analyzer won't always validate their usage - that will make implementation more complicated) and thus must be used by language/stdlib developers or at _for users that know what they are doing_.

It's good for user to understand what compiler directives are and how syntax sugar use them under the hood though.

## There is developer mode (interpreter)
## There is interpreter (backend can be slow)

Compiler must be fast to the point where it generates IR. After that we have generating of target code (e.g. generating Go and then generating machine code with Go compiler) - that part ("backend") doesn't have to be fast. It's more important to keep it simple.

The reason for that is that we have an interpreter that is internally uses compiler (it's impossible to generate IR from invalid program due to lack of type information), but not the whole thing. Just to the point where it generates IR. That's the part of the compiler that is used for development/debugging purposes. That's where we need to be fast.

## There is visual programming

Once we build the good enough tool for visual programming we will switch from text based approach. Text will become supporting tool. To achieve this we must always keep in mind that what we do with the language must be easy to visualize in graph environment.
8 changes: 8 additions & 0 deletions docs/contributing/qa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Questions and Answers

Please note that this is not [FAQ](../faq.md).

This is knowledge base that should help find out why sertain decisions were maid. This is more for questions like "_why_ X works this way?" assuming you already understand what "X" is.

- [Design](./design.md) - why language works the way it is
- [Internal](./internal.md) - why the language internally implemented this way
11 changes: 10 additions & 1 deletion docs/qa/design.md → docs/contributing/qa/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,13 @@ Well, we can do that. But that would lead to situations where you accidentally h

This problem gets bigger when you have `any` or _union_ `... | int` outport that is directed to `out:exit` - you'll have to check whether value isn't an `int`. Otherwise you're at risk of terminating with wrong code.

**Exit codes are important**. Shell scripts and CI/CD depends on that. Most of the time you want your exit code to be `zero`. Non-zero exit code is not happypath, it's more rare. Having corner case like a base design decision is not what we want.
**Exit codes are important**. Shell scripts and CI/CD depends on that. Most of the time you want your exit code to be `zero`. Non-zero exit code is not happypath, it's more rare. Having corner case like a base design decision is not what we want.

## Why structural subtyping?

1. It allowes write less code, especially mappings between records, vectors and maps of records
2. Nominal subtyping doesn't protect from mistake like passing wrong value to type-cast

## Why have `any`?

First of all it's more like Go's `any`, not like TS's `any`. It's similar to TS's `unknown`. It means you can't do anything with `any` except _receive_, _send_ or _store_ it. There are some [critical cases](https://github.com/nevalang/neva/issues/224) where you either make your type-system super complicated or simply introduce any. Keep in mind that unlike Go where generics were introduced almost after 10 years of language release, Neva has type parameters from the beggining. Which means in 90% of cases you can avoid using of `any` and panicking.
File renamed without changes.
Loading

0 comments on commit 5c8078c

Please sign in to comment.