Merge pull request #4 from TopchetoEU/TopchetoEU/readme-and-contrib
Documentation improvements
This commit is contained in:
commit
ec420a0539
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,6 +28,7 @@
|
|||||||
!scripts/uninstall.bat
|
!scripts/uninstall.bat
|
||||||
|
|
||||||
!LICENSE
|
!LICENSE
|
||||||
|
!CONTRIBUTING.md
|
||||||
!Makefile
|
!Makefile
|
||||||
!README.md
|
!README.md
|
||||||
!.gitignore
|
!.gitignore
|
40
CONTRIBUTING.md
Normal file
40
CONTRIBUTING.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# So you want to contribute?
|
||||||
|
|
||||||
|
Well, first of all, thank you! It means a lot that you find this project worh your time. But of course, you'll need to follow some guidelines and rules with your issues and pull requests.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in this project and this community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
### Our Standards
|
||||||
|
|
||||||
|
**Examples of behavior that contributes to creating a positive environment include:**
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
- Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
**Examples of unacceptable behavior by participants include:**
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
### Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting me at [36534413+TopchetoEU@users.noreply.github.com]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Confidentiality with regard to the reporter of an incident will be maintained. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
TLDR: Keep all interactions civil and constructive, don't be a jerk.
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
It is mandatory that you follow [my code style](https://gist.github.com/TopchetoEU/0d7c411bc983ba6ef7b61d3753aa34bb) in your contributed code, so that this project has a cohesive style and structure.
|
53
README.md
53
README.md
@ -5,53 +5,52 @@ Developed by TopchetoEU
|
|||||||
|
|
||||||
Stage of project: WIP (work in progress)
|
Stage of project: WIP (work in progress)
|
||||||
|
|
||||||
## What is it (++C in a nutshell)?
|
|
||||||
|
|
||||||
This is a low-level, highly-customizable language, semi-OOP, partially-reflected, interpreted and compiled language. Now, lets decipher what each of these means:
|
## How is this language unique (what is ++C)?
|
||||||
|
|
||||||
### Low-level
|
Let's face it, today's programming landscape has an abundance of programming languages, and anyone would have a hard time explaining why adding one more language to this oversaturated market would make any sense, but bare with me. This language is unique in one way: metaprogramming. Metaprogramming is a programming paradigm that has never been shown in its full potential. I hope that with this language I'll be able to do so.
|
||||||
|
|
||||||
This language runs very close to the bare-metal machine, maybe with a single layer separating them (the core library). This means that you can write some pretty low-level stuff in this language, like drivers, operating systems and embedded programs.
|
## Metaprogramming
|
||||||
|
|
||||||
### Highly-customizable
|
In ++C, a lot of emphasis is put on metaprogramming. But first, what is metaprogramming? In short, it means writing code with code, or generating code via more code. This is extensively used in languages like C and C++, and all the UI editors you're using (like the WPF editor), yes, they too are metaprogramming. Although it is a powerful tool, metaprogramming has never been executed properly (in my opinion) - C has the `#define` macros, which is for all intents and purposes complete garbage, C++ has templates (and macros), but it would take a human being about 100 years to fully understand and master the templates (they're too complicated). So far, only Rust has managed to pull off a somewhat OK version of the metaprogramming. Yet, I want to extend on the metaprogramming, by completely integrating it into this language. This is done by adding a modding API to the compiler, which allows any library to extend ++C's syntax.
|
||||||
|
|
||||||
This (I think) is not applicable for most languages: most are stuck with the same primitive types, same core library. In this language, you have close to none primitive types, there are only pointers, arrays, integers and floats, that's it. This allows this language to be whatever you want it to be: maybe you just want the bare-bones statically-linked library for embedded programming, or you want the full-blown dynamically linked, garbage-collected, fully-reflected library? We've got you covered.
|
## Transpiled language
|
||||||
|
|
||||||
### OOP (Object-oriented)
|
Indeed, ++C is a transpiled language. This means that the compiler (transpiler) isn't as complex as other compilers. The unique thing about this transpiler is that the language's core is so simple it could be transpiled to virtually any language (including intermediate languages and WASM). This makes this language even more versatile.
|
||||||
|
|
||||||
This language started with the idea of being a regular OOP language, but as the time went, I've strayed away from the typical OOP languages - no matter what is done, they feel just incomplete to me. This language, instead of being a traditional OOP language, instead is a bunch of makeup on top of traditional procedural languages. What is the impact of this? You still have classes, interface, member methods (member functions), properties, etc. The major difference is that there isn't any inheritance. You might ask 'why?'. Well, inheritance would've made this language's architecture leaps and bounds more complicated, which I didn't want to do. On the other hand, ++C has by far the most advanced interfaces (contracts), more advanced than any language I've seen (so far). In this language, contracts are very special, since you can dynamically create them, make custom cast operators from and to them, etc.
|
## ++C at a glance
|
||||||
|
|
||||||
### Partially-reflected
|
Here's some example code to give you an idea of how the language's syntax works:
|
||||||
|
|
||||||
In this language, by default, you don't get much reflection. But, there's a compiler option that allows the inclusion of reflection metadata (by default its disabled so that no info about the inner workings of executables are leaked, especially because of proprietary software). Still, when that's enabled, you get full self-reflection. Of course, no self-reflection will be allowed for non-exported types (although it is planned to be possible).
|
```
|
||||||
|
import std::io;
|
||||||
|
|
||||||
### Interpreted and compiled
|
export swap(a: int*, b: int*) {
|
||||||
|
var c: int = *b;
|
||||||
|
*b = *a;
|
||||||
|
*a = c;
|
||||||
|
}
|
||||||
|
|
||||||
Now, don't get the idea that this language is interpreted like Python or JavaScript. No. Actually, this language is being compiled down to intermediate language. You have the option to use this as your executable, bundled with a ++C interpreter (recommended when distributing applications). Another option is to compile it even further, down to machine code. This is done when you have to work on a lower level (drivers, OS, embedded, etc.). This option provides a significant performance boost, as well.
|
export main(): void {
|
||||||
|
var a = 0;
|
||||||
|
var b = 0;
|
||||||
|
|
||||||
## What sets this apart from C, C# and C++ (and Java)
|
swap(&a, &b);
|
||||||
|
|
||||||
++C is a very unique language with its extreme versatility: it can be whatever you want it to be. It combines all the good things about the languages I like: C, C++, C# (and Java's architecture, the language itself is nothing to write home about).
|
printf("A: %d, B: %d\n", a, b);
|
||||||
|
}
|
||||||
By far my most favourite language is C#, which actually does a wonderful job of saving me a lot of code milage. Still, good luck with coding in C# outside Windows (it's possible, but I'd rather put my two hands in a toaster instead of doing that).
|
```
|
||||||
|
|
||||||
C++ is my best bet for cross-platform compatibility, but we all know C++: its infamous with its "bloatness". I have taken some ideas from C++, for example the template system of ++C would be much more limited if it weren't for C++. ++C is intended to be the "modern C++, what C++ should've been".
|
|
||||||
|
|
||||||
At last, lets talk about the elephant in the room: Java. I have a love-hate relationship with Java in that I absolutely love to hate it. Java is a very behind-the-times language: it doesn't even have properties, lambda expressions are a complete mess and don't even get me started with generics. Still, I quite like the idea of compiling the code once and running it on every single device that could ever exists. That's why ++C is compiled to an intermediate language. That intermediate then can be either 1. ran with an interpreter or 2. compiled.
|
|
||||||
|
|
||||||
## This project will have a last version
|
|
||||||
|
|
||||||
This language has a really strict set of features, which, when implemented, this project will be considered "completed". Still, I will not abandon this project. If anyone logs any bugs or suggests any new features after that point (that won't take too long to implement), I will be keen on putting out a new version. To be clear, this project will have a finished state at some point.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Check out the [documentation](./doc/index.md) of this project, I've tried to make it as thorough as possible. Still, if something is unclear, please make sure to log it as an issue.
|
Check out the [documentation](./doc/index.md) of this project, I've tried to make it as thorough as possible. Still, if you find something wrong in the documentation, please make sure to log it as an issue.
|
||||||
|
|
||||||
## How to contribute?
|
## How to contribute?
|
||||||
|
|
||||||
Any help is entirely voluntary, yet very much appreciated. If you see any bugs, please don't hesitate to report them (as issue logs) and if you feel like you could add to this project's worth, please do make a pull request.
|
Any help is entirely voluntary, yet very much appreciated. If you see any bugs, please don't hesitate to report them (as issue logs) and if you feel like you could add to this project's worth, please do make a pull request. Just make sure to
|
||||||
|
|
||||||
## How to compile?
|
## How to compile?
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
- Command: `make`
|
- Command: `make`
|
||||||
@ -59,6 +58,7 @@ Any help is entirely voluntary, yet very much appreciated. If you see any bugs,
|
|||||||
- Uninstall command: `make uninstall`
|
- Uninstall command: `make uninstall`
|
||||||
- Output: `bin/++c-linux`
|
- Output: `bin/++c-linux`
|
||||||
- Prerequisites: `gcc`, `make`
|
- Prerequisites: `gcc`, `make`
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
- Command: `make`
|
- Command: `make`
|
||||||
@ -66,6 +66,7 @@ Any help is entirely voluntary, yet very much appreciated. If you see any bugs,
|
|||||||
- Uninstall command: `make uninstall`
|
- Uninstall command: `make uninstall`
|
||||||
- Output: `bin/++c-windows.exe`
|
- Output: `bin/++c-windows.exe`
|
||||||
- Prerequisites: `gcc`, `make` (I use the GnuWin port)
|
- Prerequisites: `gcc`, `make` (I use the GnuWin port)
|
||||||
|
|
||||||
### Mac OS X
|
### Mac OS X
|
||||||
|
|
||||||
For now, there's no official support, but the build scripts should in theory work with any unix-like OS. You can try to use the linux instructions
|
For now, there's no official support, but the build scripts should in theory work with any unix-like OS. You can try to use the linux instructions
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
# Constructs
|
|
||||||
|
|
||||||
The constructs are a unique take on the meta programming concept. A construct will get an access to your source code in the form of parsed tokens (identifiers and operators) and will output one or more constructs in your code. This can be used to define custom type structures (i. e., enums, interfaces, delegates, etc.), define inline functions with compile-time errors, and so on. The possibilites of constructs are pretty much limitless.
|
|
||||||
|
|
||||||
## How to define a construct
|
|
||||||
|
|
||||||
Construct definitions are stored in special files (.cppc, or construct ++C). The name of the file will be the name of the construct. The name of the construct file doesn't matter, but it is recommended that the name matches the keyword of the construct.
|
|
||||||
|
|
||||||
The structure of such a file is like a ++C file, but with a few major differences:
|
|
@ -1,27 +0,0 @@
|
|||||||
# For loops
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
For loops consist of a declaration, condition, assignment and a statement. The declaration is a declaration statement, the condition is a expression of type `bool` or a type from which `bool` can be derived and the assignment may be any expression.
|
|
||||||
|
|
||||||
```c
|
|
||||||
for (declaration; condition; assignment) statement
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
For loops are syntax sugar for while loops, and they can roughly translate to the following code:
|
|
||||||
|
|
||||||
```c
|
|
||||||
declaration;
|
|
||||||
while (condition) {
|
|
||||||
statement;
|
|
||||||
assignment;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Where the only difference is that the declaration is scoped inside the for loop, instead in the outside variable scope.
|
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
If the assignment statement is deemed pure (doesn't affect the outside environment), then it will be omitted. The same optimizations that are made for the while loop are in full action here.
|
|
@ -1,48 +0,0 @@
|
|||||||
# Goto and labels
|
|
||||||
|
|
||||||
**NOTE:** goto statements and labels are not yet supported. They are a planned feature, but will take a long time until they are implemented
|
|
||||||
|
|
||||||
## Labels
|
|
||||||
|
|
||||||
A label is used to name a line of code. It is used as a point to which `goto` can go.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
The syntax of a label is an identifier, followed by a colon. A label must be between the end of one and the start of another statement. For example, this is a valid label:
|
|
||||||
|
|
||||||
```c
|
|
||||||
int a = 10; label: a++;
|
|
||||||
```
|
|
||||||
|
|
||||||
But this is not:
|
|
||||||
|
|
||||||
```
|
|
||||||
int a = label: 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Goto
|
|
||||||
|
|
||||||
A goto statement is used to redirect the flow of execution to another part of the code. It can be used to skip or loop parts of the code.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
A goto statement consists of the `goto` keyword, followed by an identifier (the name of a label). A label may be referenced by a goto statement before or after the label's definition.
|
|
||||||
|
|
||||||
## Possible compiler optimizations
|
|
||||||
|
|
||||||
If the compiler determines that a `goto` statement makes a part of the code inaccessible, for example:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// first example
|
|
||||||
|
|
||||||
goto label;
|
|
||||||
printf("I hate ++C");
|
|
||||||
label:
|
|
||||||
|
|
||||||
// second example
|
|
||||||
|
|
||||||
label:
|
|
||||||
printf("I love ++C");
|
|
||||||
goto label;
|
|
||||||
printf("I hate ++C");
|
|
||||||
```
|
|
@ -1,56 +0,0 @@
|
|||||||
# Ifs and elses
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
Each if statement consists of the `if` keyword, followed by an expression, encapsulated in parenthesis, followed by a statement. Optionally, the statement may be followed by an `else` keyword, which is followed by a statement on its own.
|
|
||||||
|
|
||||||
```c#
|
|
||||||
if (expression) statement
|
|
||||||
// or
|
|
||||||
if (expression) statement1 else statement2
|
|
||||||
```
|
|
||||||
|
|
||||||
No semicolons are required after the body of an if statement, unless the body statement itself requires one.
|
|
||||||
|
|
||||||
## Terminology
|
|
||||||
|
|
||||||
The given expression is called a `condition`, the statement following the condition is the `if body` and the statement following the else keyword is called the `else body`
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
The given expression (condition) may be of any type, and if its non-zero, then the if body will be executed. Else, if one, the else body will be executed instead.
|
|
||||||
|
|
||||||
## Equivalent PPCIL assembly
|
|
||||||
|
|
||||||
For any given if-else statement:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if (condition) if_body
|
|
||||||
else else_body
|
|
||||||
```
|
|
||||||
|
|
||||||
Its equivalent assembly is:
|
|
||||||
|
|
||||||
```
|
|
||||||
;condition
|
|
||||||
jz else
|
|
||||||
; if_body
|
|
||||||
jmp end_else
|
|
||||||
else:
|
|
||||||
;else_body
|
|
||||||
end_else:
|
|
||||||
```
|
|
||||||
|
|
||||||
If the if doesn't have a corresponding else statement, the resulting assembly is as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
;condition
|
|
||||||
jz else
|
|
||||||
; if_body
|
|
||||||
else:
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Possible compiler optimizations
|
|
||||||
|
|
||||||
An if body may be omitted if the condition is determined to be `false`. If the condition is determined to be `true`, then the else body is omitted.
|
|
@ -1,46 +0,0 @@
|
|||||||
# Switch statements
|
|
||||||
|
|
||||||
**NOTE:** Switch statements are not yet supported. They are a planned feature, but will take a long time until they are implemented
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
Switch statements differ quite a lot from other languages, as you can see. First, we have the `switch` keyword, followed by an expression (the value) in parenthesis. Then, we have a series of `case` statements, and optionally, a `default` statement (not necessarily the last one, but the compiler will warn you about it).
|
|
||||||
|
|
||||||
In contrast to other C-like languages, cases in a switch statemnets are more like simplified if statements, so they are written with first the `case` (or `default`) keyword, followed (if a statement) by the value in parenthesis and after that the statement of the case.
|
|
||||||
|
|
||||||
In real code, this would look like this:
|
|
||||||
|
|
||||||
```c#
|
|
||||||
switch (value)
|
|
||||||
case(val1) statement;
|
|
||||||
case(val2) statement;
|
|
||||||
...
|
|
||||||
default statement;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
Behaves just like an if-elseif-else chain. Compares the value to each given case's values, and runs each case which contains the value in its value list.
|
|
||||||
|
|
||||||
## Possible optimizations
|
|
||||||
|
|
||||||
If a value is determined to not be possible, yet is a case in the switch statement, its going to be removed. For example, the following code:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if (val != 3) {
|
|
||||||
switch (val)
|
|
||||||
case (1) printf("1");
|
|
||||||
case (2) printf("2");
|
|
||||||
case (3) printf("3");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Will be converted to:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if (val != 3) {
|
|
||||||
switch (val)
|
|
||||||
case (1) printf("1");
|
|
||||||
case (2) printf("2");
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,67 +0,0 @@
|
|||||||
# Try-catch statements
|
|
||||||
|
|
||||||
**NOTE**: Try-catches are not yet supported. They are a planned feature, but will take a long time until they are implemented
|
|
||||||
|
|
||||||
A try-catch statement will try running the code in the `try` block, and if any exceptions are thrown, they are going to be handled by the `catch` statement (if the types of the exceptions match, of course).
|
|
||||||
|
|
||||||
## Catches
|
|
||||||
|
|
||||||
A catch is a part of the try-catch statement, that consists of a single "parameter" of sorts, that is the type of exception that this catch is going to handle. This can be a contract as well, but due to some inefficiencies in that regard, it's recommended to handle separate classes.
|
|
||||||
|
|
||||||
### Nameless catches
|
|
||||||
|
|
||||||
If a catch doesn't specify the name of the exception it's getting, then the catch will only listen for the exception thrown. Such catches can specify multiple types they're listening for.
|
|
||||||
|
|
||||||
### Typeless catch
|
|
||||||
|
|
||||||
Such catch doesn't have any parenthesis and it listens for all exceptions.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```c++
|
|
||||||
catch (Type name) statement;
|
|
||||||
|
|
||||||
// Or
|
|
||||||
catch (Type1, Type2, Type3, Type3, ... TypeN) statement;
|
|
||||||
|
|
||||||
// Or
|
|
||||||
catch statement;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Order of catch executions
|
|
||||||
|
|
||||||
Since a try-catch can have multiple catches, they will be executed in order. If one of them breaks out of the try-catch statement, via `break`, `goto`, another `throw` or `return`, the chain of catches is going to be broken and the try-catch statement is going to be left.
|
|
||||||
|
|
||||||
## Throw
|
|
||||||
|
|
||||||
The throw statement accepts an expression of any type, or no expression. The throw statement acts like the return statement, but instead of a meaningful return value, it communicates to the caller that something has went wrong, and sends the provided value to the caller. The thrown value propagates the call stack, until a caller that has provided an appropriate catch is reached.
|
|
||||||
|
|
||||||
## Possible optimizations
|
|
||||||
|
|
||||||
Two catches that have the same types will be combined, for example:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void main() {
|
|
||||||
try func();
|
|
||||||
catch (int err) ...;
|
|
||||||
catch (int) ...;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Will result in code which looks like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
void main() {
|
|
||||||
try func();
|
|
||||||
catch (int err) {
|
|
||||||
catch1 ...;
|
|
||||||
catch2 ...;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If a catch catches a type that the function doesn't throw, the catch will be omitted.
|
|
||||||
|
|
||||||
## ++C architecture for catching exceptions
|
|
||||||
|
|
||||||
In the ++C ecosystem, throw-catch works by passing catch callbacks to the function calls inside the `try` statement.
|
|
@ -1,80 +0,0 @@
|
|||||||
# While loops
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
They consist of a `while` keyword, followed by an expression (the condition), in parenthesis, followed by a statement (body).
|
|
||||||
|
|
||||||
```c
|
|
||||||
while (expression) statement
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
The condition can be of any type. The loop will continue looping until the condition becomes zero. The chech whether or not the condition is zero occurs before the execution of the body, so if the condition of a while loop is initially zero, the body will get skipped.
|
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
If the condition is non-zero, any comparison will be omitted, and instead a simple jump function will occur. Anything after a while-true loop will be omitted. If the condition is zero, the while loop is omitted.
|
|
||||||
|
|
||||||
## Equivalent PPCIL assembly
|
|
||||||
|
|
||||||
For any given while loop:
|
|
||||||
|
|
||||||
```c
|
|
||||||
while (condition) body;
|
|
||||||
```
|
|
||||||
|
|
||||||
Its equivalent assembly is:
|
|
||||||
|
|
||||||
```
|
|
||||||
start:
|
|
||||||
; condition
|
|
||||||
jz end
|
|
||||||
; body
|
|
||||||
jmp start
|
|
||||||
end:
|
|
||||||
```
|
|
||||||
|
|
||||||
# Do-while loops
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
They consist of a `do` keyword, followed by a statement, which is followed by a `while` keyword and an expression (condition) in parenthesis. The statement is followed by a semicolon.
|
|
||||||
|
|
||||||
```c
|
|
||||||
do statement while (expression);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
The condition can be of any type.
|
|
||||||
First, the body is executed. Then, the condition is tested. If it's `false`, then execution of the code continues after the loop. Otherwise, the whole process returns
|
|
||||||
|
|
||||||
## Equivalent PPCIL assembly
|
|
||||||
|
|
||||||
For any given do-while loop:
|
|
||||||
|
|
||||||
```c
|
|
||||||
do body while (condition);
|
|
||||||
```
|
|
||||||
|
|
||||||
Its equivalent assembly is:
|
|
||||||
|
|
||||||
```
|
|
||||||
start:
|
|
||||||
; body
|
|
||||||
; condition
|
|
||||||
jnz start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
If the condition is `true`, then the loop is transformed into a while-true one. If the condition is `false`, instead of the do-while loop, the statement is executed just once. This makes for good encapsulation, but since this is one of the few languages that supports block statements as a statement on their own, this feature is deemed useless and just an optimization for bad code.
|
|
||||||
|
|
||||||
### While loop
|
|
||||||
|
|
||||||
The while
|
|
||||||
|
|
||||||
# Difference between the two loops
|
|
||||||
|
|
||||||
The while loop is called a pre-conditional, which means that first the condition is tested, then the body is executed, whereas in the do-while loop the body is executed first and then the condition is tested.
|
|
@ -1,5 +0,0 @@
|
|||||||
# The difference between arguments and parameters
|
|
||||||
|
|
||||||
This is a topic that really has to be covered here so you can continue reading this documentation without any misunderstandings.
|
|
||||||
|
|
||||||
The parameters tell the user what is being expected, for example, the signature of a function contains parameters, not arguments. Arguments on the other side are the things you match parameters with, for example, you pass arguments to a function when you call it, not parameters.
|
|
81
doc/functions.md
Normal file
81
doc/functions.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Functions
|
||||||
|
|
||||||
|
Functions are the main units in ++C, containing executable code. Functions are used to represent code, so are really important for the language. It is important to understand that functions are everything that isn't a field, variable or a type - operators, constructors, methods, etc. are all functions.
|
||||||
|
|
||||||
|
## The difference between arguments and parameters
|
||||||
|
|
||||||
|
Before reading further, you need to understand, before continuing further.
|
||||||
|
|
||||||
|
The parameters are the local variables in the function, that get assigned by the caller, and the arguments are the values you pass via the call. When talking about parameters, we're talking about the callee function, and when talking about arguments. we're talking about a call.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
A function may start with the `export` keyword. After that, is the name of the function, followed by the parameter list. The parameter list is a comma-separated list of the parameters. A parameter consists of its name, a type (`name: type`), and optionally a default value (`name: type = defval`). The parameter list is in parentheses. After the parameter list, a return type is optionally specified (if none is specified, `void` is implied). After that, the body of the function is either written with curly braces, or with a lambda arrow and a return expression following it.
|
||||||
|
|
||||||
|
```c
|
||||||
|
func_name(arg1: type1_t, arg2: type2_t, ... argn: typen_t): return_type_t { statement1; statement2; ... }
|
||||||
|
// or
|
||||||
|
func_name(arg1: type1_t, arg2: type2_t, ... argn: typen_t) { statement1; statement2; ... }
|
||||||
|
// or
|
||||||
|
func_name(arg1: type1_t, arg2: type2_t, ... argn: typen_t): return_type_t => expression;
|
||||||
|
// or
|
||||||
|
func_name(arg1: type1_t, arg2: type2_t, ... argn: typen_t) => expression;
|
||||||
|
```
|
||||||
|
|
||||||
|
The signature of a function is everything up to (and including) the return type.
|
||||||
|
|
||||||
|
## Calling
|
||||||
|
|
||||||
|
Calling of a function is the action of executing the body of a function, by first providing it with the needed context.
|
||||||
|
|
||||||
|
|
||||||
|
A call will jump to the beginning of the callee's (the called function) body, execute the code, and when the function finishes its executing, returns to the location of the call. Calling is a little bit more involved when including arguments.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
|
||||||
|
A call consists of a callable expression (most of the times, a function name), followed by a comma-separated argument list, in parentheses.
|
||||||
|
|
||||||
|
```c
|
||||||
|
func_name(arg1, arg2, arg3, ... argn);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arguments and argument passing
|
||||||
|
|
||||||
|
The arguments of a call are a sequence of values, that match the signature's parameter types. When the call occurs, the parameters get assigned to the respective values, passed by the caller. After that, inside the callee, the parameters act like locals. Modifying their values won't affect the values, passed in by the callee
|
||||||
|
|
||||||
|
```c
|
||||||
|
import std::io;
|
||||||
|
|
||||||
|
func(a: int, b: int) {
|
||||||
|
printf("%d", a + b);
|
||||||
|
}
|
||||||
|
export main() {
|
||||||
|
func1(10, 5); // a = 10; b = 5 // 15
|
||||||
|
func1(10 + 1, 5 - 3); // a = 11; b = 2 // 13
|
||||||
|
func1(10); // Compiler error: mismatch between func's parameter list and passed arguments
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
A function can have a return value. This is used when the function has some data it wants to give back to the callee. When the callee returns, it stops execution and returns to the point at which the callee was called. The call expression will have the value of the return value of the function
|
||||||
|
|
||||||
|
```c++
|
||||||
|
int func(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
void main() {
|
||||||
|
int a = func1(10, 5) + 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The call of `func1` will evaluate like this:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
func1(10, 5) + 1;
|
||||||
|
// Calculate func1 result
|
||||||
|
15 + 1
|
||||||
|
// Perform addition
|
||||||
|
16
|
||||||
|
```
|
||||||
|
The return type in the syntax is the first part of a function, right before the name
|
@ -1,105 +0,0 @@
|
|||||||
# Functions
|
|
||||||
|
|
||||||
Functions are the main units in ++C, containing executable code. Functions are used to represent code, so are really important for the language. It is important to understand that functions are everything that isn't a field, variable or a type - operators, constructors, methods, etc. are all functions.
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
A function is defined like in most C-like languages, but with a few twists:
|
|
||||||
|
|
||||||
```c
|
|
||||||
return_type_t func_name [this this_type_t](type1_t arg1, type2_t arg2, ... typen_t argn [, params type_t variadicArg]) body_statement;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Calling
|
|
||||||
|
|
||||||
Calling of a function is the action of executing the body of a function, by first providing it with the needed context.
|
|
||||||
|
|
||||||
|
|
||||||
A call will jump to the beginning of the callee (the called function)'s body, execute the code, and when the function finishes its executing, returns to the location of the call. Calling is a little bit more involved when including arguments.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
In ++C, calling is done via the standard C-like syntax:
|
|
||||||
|
|
||||||
```c
|
|
||||||
func_name(arg1, arg2, arg3, ... argn);
|
|
||||||
```
|
|
||||||
|
|
||||||
## `this` context
|
|
||||||
|
|
||||||
A `this` context is required by some functions, that will be called in the format `noun.verb(arguments)`. In practice, a pointer to the object upon which the function is called is just passed as a first argument to the function. The caller is responsible for providing an adequate pointer to the object, for example in the following case:
|
|
||||||
|
|
||||||
```c
|
|
||||||
(10).myFunc();
|
|
||||||
```
|
|
||||||
|
|
||||||
The integer `10` upon which `myFunc` is being called has no real representation in the heap, nor the stack. In such cases, the compiler will allocate space for the integer somewhere, and then will free it.
|
|
||||||
|
|
||||||
### Calling functions with a `this` context
|
|
||||||
|
|
||||||
The `this` context is passed last on the stack, or first as an argument. Note that a function may be called upon a `null` object, which may break some functions. Also note that when calling a function without a `this` context (or a `this void` context), nothing is passed for the `this` context.
|
|
||||||
|
|
||||||
|
|
||||||
## Parameters
|
|
||||||
|
|
||||||
The parameters of a function are a set of values, which are being passed by the caller to the callee, which can then be used as local variables inside the callee. Any modifications to the parameters don't affect the passed values by the caller, although generally it's a bad practice to assign values to the parameters. In the following example:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
import System.Console;
|
|
||||||
|
|
||||||
void func(int a, int b) {
|
|
||||||
WriteLine(a + b);
|
|
||||||
}
|
|
||||||
void main() {
|
|
||||||
func1(10, 5); // 15
|
|
||||||
func1(10 + 1 /* 11 */, 5 - 3/* 2 */); // 13
|
|
||||||
func1(10); // Compiler error: mismatch between func's parameter list and passed arguments
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Parameters can also be looked at like template values, that are being replaced by the given values by the caller, before the code is executed. Of course, this is not exactly like that, but is a nice analogy to keep in mind for beginners.
|
|
||||||
|
|
||||||
In the syntax, parameters consist of a comma-separated list of type-name pairs, encapsulated in parenthesis.
|
|
||||||
|
|
||||||
### Calling with arguments
|
|
||||||
|
|
||||||
All rules for calling are followed, but before jumping to the callee, the caller will push to the call stack the parameters in reverse order. Then, the function will be executed. In the end, the caller is responsible for cleaning up anything that was required to call the function, for example the stack.
|
|
||||||
|
|
||||||
One important part of this specification is that arguments may not exceed the native register's size. If so, the callee is responsible for allocating space for the argument, passing a pointer instead and then freeing the memory (the memory may be taken from the stack and is what this compiler will do).
|
|
||||||
|
|
||||||
## Return value
|
|
||||||
|
|
||||||
A function can return a value too, which means that the callee is going to perform some calculations, and then, the last thing it does is putting the calculated value in stack and jumping back to the caller. The caller then can use this value however he pleases.
|
|
||||||
|
|
||||||
A return takes an expression, that is being first evaluated, THEN passed to the caller.
|
|
||||||
|
|
||||||
When a function returns a value, the call of that function can take part in an expression. When the expression is being evaluated, the return value of the function is going to "replace" the call to the function, like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
int func(int a, int b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
void main() {
|
|
||||||
int a = func1(10, 5) + 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The call of `func1` will evaluate like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
func1(10, 5) + 1;
|
|
||||||
// Calculate func1 result
|
|
||||||
15 + 1 // Note that it doesn't get replaced by 10 + 5, but with 15, because the expression in the return statement evaluates before being returned
|
|
||||||
// Perform addition
|
|
||||||
16
|
|
||||||
```
|
|
||||||
|
|
||||||
### Calling with return value
|
|
||||||
|
|
||||||
The return value of a function will be pushed to the stack by the callee, and that will be the last thing it does, before returning to the caller. The caller is responsible to handle the return value adequately, whether or not that means to ignore it, or store it somewhere. In all cases, the return value's size must be accounted for when freeing the stack. If the return value exceeds the register size, the callee will allocate a dangling pointer that may be used only once to copy the data from it to somewhere safe. Usually, this is from a part of the stack that is already freed, so don't rely on the given pointer.
|
|
||||||
|
|
||||||
The return type in the syntax is the first part of a function, right before the name
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
More of this part of the documentation is to be written soon, there is more to be said
|
|
@ -1,9 +0,0 @@
|
|||||||
# Function parameters
|
|
||||||
|
|
||||||
A function in ++C, as any other C-like language, can accept parameters. Parameters in ++C act as function-scope local variables, that can be assigned to.
|
|
||||||
|
|
||||||
## Variadic parameters
|
|
||||||
|
|
||||||
**NOTE**: This is not yet implemented, although it is a planned feature.
|
|
||||||
|
|
||||||
In ++C, variadic parameters are supported. That means
|
|
@ -1,88 +0,0 @@
|
|||||||
# Generics
|
|
||||||
|
|
||||||
In ++C, generics are a language feature (they don't have an assembly representation). For each combination of generic parameters, basically a new type is being created (most compilers, including this one, will link the newly created type statically, so if two libraries use a definition with the same generic arguments, the template code produced will be present in both libraries). In the final assembly, no generic functions exist, neither do any generic constants. Only definition templates are being exported (don't worry, decompiling them is as hard as any other language).
|
|
||||||
|
|
||||||
The generics have two major groups: generic types and generic constants.
|
|
||||||
|
|
||||||
## Generic types
|
|
||||||
|
|
||||||
The generic types are template types that, in the scope of the template definition, behave as regular types. They are used as placeholders to any type the user may pass to the generic type parameter.
|
|
||||||
|
|
||||||
## Generic constants
|
|
||||||
|
|
||||||
Generic constants are quite interesting. They are basically a private const field / variable for the type / function they're defined in, so they may be referenced as constants anywhere where a constant would be referenced. Generic consts' values may be only pure expressions (one that can be resolved compile-time). Still, calls to functions that are pure is allowed.
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
Constraints may be put to generic types and constants. They define what may and may not be passed as a generic argument. Depending on the constraints, the language is going to allow the usage of some specific API of the types.
|
|
||||||
|
|
||||||
### Generic types
|
|
||||||
|
|
||||||
For generic types, constraints can specify what operators the type must provide, what contracts the type must implement. Depending on that, you will be able to use the specified required API.
|
|
||||||
|
|
||||||
### Generic constants
|
|
||||||
|
|
||||||
For constants, constraints consist of a pure boolean expression, which specified whether or not the constant is allowed. This allows for great flexibility when using generic constants.
|
|
||||||
|
|
||||||
### Multiple definitions with non-overlapping constraints
|
|
||||||
|
|
||||||
In ++C (like in C++), you can define a definition multiple times, if the generic constraints never overlap. This is more or less like overloading functions, but on steroids. This makes the generic system turing complete (by the way), and allows for some interesting stuff.
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
In ++C, the generics syntax is similar to the C# syntax. For example, you'd go about making a generic function like this:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
export void myfunc<T1, T2><int const1, int const2>()
|
|
||||||
where T1: { void increase(); }
|
|
||||||
where T2: IEnumerable
|
|
||||||
where (const1 < 10)
|
|
||||||
where (const2 % 2 == 0) {
|
|
||||||
// Do some stuff
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And calling this function in the following manner:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
class temp {
|
|
||||||
int val;
|
|
||||||
export int Val { get val; }
|
|
||||||
|
|
||||||
void increase() {
|
|
||||||
val++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void main() {
|
|
||||||
myfunc<temp, int[]><5, 8>();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Exporting a template definition
|
|
||||||
|
|
||||||
This will basically construct a template with the specified generic arguments and export it to the public API. This can be a really good optimization when you're making an API and know which generic arguments are going to be used the most. Still, don't overdo it. For example, a theoretical list implementation would do the following:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
class list<T> { ... }
|
|
||||||
|
|
||||||
export list<float>;
|
|
||||||
export list<int>;
|
|
||||||
export list<object>; // object => export contract object;
|
|
||||||
```
|
|
||||||
|
|
||||||
Of course, as any definition, this could as well not be exported, but that will be useless, since this will just keep the template definitions local to your assembly, so at best they will all be used, and worse scenarios would be that these templates won't be used and will add bloat to your code. The compiler will warn you that just because you can do it, doesn't mean you should and will automatically remove such definitions.
|
|
||||||
|
|
||||||
## Compile-time optimizations you need to be aware of
|
|
||||||
|
|
||||||
Most generics-related optimizations are to do with limiting the amount of templates that have to be constructed. To achieve this, a compiler will cut out some templates it deems unnececeary.
|
|
||||||
|
|
||||||
If there are passed const parameters, they will just be converted to constructor parameters, if they aren't used in the size specifier of an array.
|
|
||||||
|
|
||||||
## Waning
|
|
||||||
|
|
||||||
Please, don't overuse the templates. They are a really powerful tool, but if you make too much code templated, you will blow up your binary size exponentially. They are made with the idea to copy-paste the code for you, but be aware that behind the scenes, for each generic combination, a new definition is being constructed.
|
|
||||||
|
|
||||||
## Optimizations the programmer can do
|
|
||||||
|
|
||||||
1. Using templates with less parameters (to decrease possible combinations)
|
|
||||||
2. Exporting commonly used generic definitions (be careful and don't overdo it)
|
|
13
doc/index.md
13
doc/index.md
@ -2,18 +2,11 @@
|
|||||||
|
|
||||||
This is the official documentation for the ++C language. This documentation describes in detail everything there is to know about the language ++C and the ++C ecosystem.
|
This is the official documentation for the ++C language. This documentation describes in detail everything there is to know about the language ++C and the ++C ecosystem.
|
||||||
|
|
||||||
NOTE: this documentation is still a WOP (work in progress)
|
NOTE: this documentation is severely incomplete and is still a work-in-progress.
|
||||||
|
|
||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
1. [Control flow and statements](./control-flow/index.md)
|
1. [Control flow and statements](./statements/index.md)
|
||||||
2. [Type system](./type-system/index.md)
|
2. [Type system](./type-system/index.md)
|
||||||
3. [Functions](./functions.md)
|
3. [Functions](./functions.md)
|
||||||
5. [Generics](./generics.md)
|
4. [Versions](./versions.md)
|
||||||
6. [Versions](./versions.md)
|
|
||||||
7. [Intermediate](./intermediate.md)
|
|
||||||
|
|
||||||
The following are from a previous iteration of the documentation, so it may be more unstructured
|
|
||||||
|
|
||||||
2. [Metadata](./ppc-metadata.md)
|
|
||||||
3. [Type system (old)](./ppc-type_architecture.md)
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# ++C Intermediate Lang (PPCIL 1.0)
|
# ++C Intermediate Lang (PPCIL 1.0)
|
||||||
|
|
||||||
|
**NOTE: this currently is unused**
|
||||||
|
|
||||||
This is a read-only binary format, used in the ++C ecosystem to represent executable code. It has an assembly-like structure (opcode, followed by operands), so that a translation to a native instruction set can be more robust. Still, this IL's instruction set implements instructions, not found in some instruction sets. The idea behind that decision is to use native optimizations where possible (for example, using the square root instruction of the x86_64 instruction set, instead of a common algorithm for all instruction sets). One important thing to note is that PPCIL is not just an itermediate for ++C. A compiler could be written that converts any language to PPCIL, for example: Java, C#, C++, C, etc.
|
This is a read-only binary format, used in the ++C ecosystem to represent executable code. It has an assembly-like structure (opcode, followed by operands), so that a translation to a native instruction set can be more robust. Still, this IL's instruction set implements instructions, not found in some instruction sets. The idea behind that decision is to use native optimizations where possible (for example, using the square root instruction of the x86_64 instruction set, instead of a common algorithm for all instruction sets). One important thing to note is that PPCIL is not just an itermediate for ++C. A compiler could be written that converts any language to PPCIL, for example: Java, C#, C++, C, etc.
|
||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
# The ++C metadata file format
|
|
||||||
|
|
||||||
## What is this?
|
|
||||||
|
|
||||||
Basically, ++C stores data about its type structure, so that reflection can be provided. Each ++C binary exports a segment in its binary, called `ppc_meta`. If the segment is not present, obviously, this is not a ++C library / executable. Still, the compiler has the option to not include this metadata, which is applicable for embedded, but if you decide to do that, you won't be able to use this library in other ++C libraries / executables. If you want to hide your API, this is a great way to do so.
|
|
||||||
|
|
||||||
## General concepts
|
|
||||||
|
|
||||||
The goal of this format is for it to be as straightforward to parse as possible, so this will be pretty close to the structures, representing the definitions.
|
|
||||||
|
|
||||||
In this document, we're going to use C-like syntax to represent structures.
|
|
||||||
|
|
||||||
It is also important to note that all pointers are relative to the beginning of the metadata, so that copying of the metadata can happen with `memcpy`.
|
|
||||||
|
|
||||||
## The version structure
|
|
||||||
|
|
||||||
You will see this quite often throughout this specification, so let me explain it:
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct version_t {
|
|
||||||
ushort major;
|
|
||||||
ushort minor;
|
|
||||||
uint revision;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
A version is a 64-bit digit, consisting of a major, minor and revision components. If the major components don't match, the two versions are incompatible. A change in the major version signifies a breaking change (the major version is appended at the end of library file names, so there are no conflicts between them). The minor version signifies incremental changes (additions to the library), so the provided version's minor version must be bigger or equal to the target minor version for compatibility. The revision component specifies changes, that don't modify the exposed API, nor the behavior. The revision is ignored when checking version compatibility.
|
|
||||||
|
|
||||||
Any of the version's components may be -1 (the max value of the type), it is altogether ignored (a library may have only a major and a revision component)
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
o describe the metadata's byte layout in order
|
|
||||||
|
|
||||||
Each table will consist of the
|
|
||||||
The following sections are going t following:
|
|
||||||
|
|
||||||
```c
|
|
||||||
size_t n; // The amount of entries
|
|
||||||
size_t size; // The byte size of everything that follows
|
|
||||||
```
|
|
||||||
|
|
||||||
### Binary attributes
|
|
||||||
|
|
||||||
This will specify all needed attributes for the binary, like version, name, author, etc.
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct attributes_t {
|
|
||||||
version_t version;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### String literals
|
|
||||||
|
|
||||||
This table contains all string literals, used in the metadata. They're recorded like this:
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct literals_t {
|
|
||||||
size_t n; // Amount of literals
|
|
||||||
size_t size; // The byte size of the following array
|
|
||||||
char data[size];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Definition table
|
|
||||||
|
|
||||||
Since a definition can appear only once (overloads don't add definitions), you can have a full record of all definitions. This is an alphabetically sorted table in which to find the address of a definition
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct entry_t {
|
|
||||||
const char *name; // From the literals table
|
|
||||||
void *def_ptr; // The pointer to the definition (relative to the pointer)
|
|
||||||
};
|
|
||||||
struct entry_table_t {
|
|
||||||
size_t n;
|
|
||||||
size_t size; // The byte size of the following array
|
|
||||||
entry_t entries[n];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module table
|
|
||||||
|
|
||||||
This table contains all the modules, that are included in the executable. They will be referenced afterwards.
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct module_t {
|
|
||||||
const char *name;
|
|
||||||
version_t version;
|
|
||||||
};
|
|
||||||
struct module_table_t {
|
|
||||||
size_t n, size;
|
|
||||||
module_t modules[size];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type table
|
|
||||||
|
|
||||||
The type table contains all type identifier that are going to be used throughout the metadata. They consist of the following structure:
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct type_id_t {
|
|
||||||
const char *name; // This is the full name, with the namespace and name
|
|
||||||
module_t *module; // The module from which this type originates
|
|
||||||
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Function notations
|
|
||||||
|
|
||||||
Each function notation is done in the following way:
|
|
||||||
|
|
||||||
| Name | Description | Syntax |
|
|
||||||
|------------|-------------|--------|
|
|
||||||
| this | Specifies which is the this type. | T\[type]
|
|
||||||
| return | Specifies the return value | R\[value]
|
|
||||||
| args | Specifies the arguments | A\[type1]\[type2]...\[typeN]$
|
|
||||||
|
|
||||||
## Declarations
|
|
||||||
Each declaration ends in the following way:
|
|
||||||
|
|
||||||
| Name | Syntax | Description |
|
|
||||||
|------------|-------------|-------------|
|
|
||||||
| Identifier | namespace.name | The namespace of the definition
|
|
||||||
| Name | name\| | The name of the declarable. Includes just the name, without the namespace and the type-container. It is terminated with a pipe char |
|
|
||||||
| Custom flags | ... | The declaration-specific custom flags. Exactly 2 chars. Most will fill that space with underscores |
|
|
||||||
| Children | [child1,child2,...] | Not mandatory, some declarations may replace this with some other data |
|
|
||||||
| End | ; |
|
|
||||||
|
|
||||||
| Letter | Name | Flags | Description |
|
|
||||||
|--------|----------------|--------|-------------|
|
|
||||||
| R | Reference type | | Declares a type that is passed by its reference. |
|
|
||||||
| V | Value type | | Declares a type that is passed by its value |
|
|
||||||
| C | Contract | | A contract type, containing method and property declarations. |
|
|
||||||
| E | Enum | | A type, containing constants of a certain type. |
|
|
||||||
| D | Delegate | | A type, specifying a function pointer. It has the same declaration as a function |
|
|
||||||
| P | Property | ?S, R/W | Declares a property, may be static or instance, may be read-only or read-write. Instead of children, a single type specifier is given |
|
|
||||||
| F | Function | ?S/E, ?V | Declares a function, may be static or not. It has no children list, instead, it has a return type specifier, followed by its parameters as type specifiers. The last argument is a variadic argument if V flag is specified. If E is specified, the function is a static extension function |
|
|
||||||
| I | Field | ?S, ?C | Declares a field, which may be static or not, depending on whether or not S or I flag is specified. It may be constant if the flag C is specified. Instead of a body, it has a type specifier, specifying the type of the field |
|
|
||||||
| N | Event | ?S, A/R | Declares an event, that may be static, if S is specified. If A flag is specified handlers may be just added, if R is specified, handlers may be removed as well. The children list is replaced by a delegate type specifier |
|
|
||||||
|
|
||||||
**NOTE:** Before the children list of all type declarations, except for enums and delegates, a list of all base classes is defined in the following manner: `[base1, base2, ...]`
|
|
||||||
|
|
||||||
## General structure
|
|
||||||
|
|
||||||
Each metadata file/sector follows the following structure:
|
|
||||||
|
|
||||||
| Type | Name | Description |
|
|
||||||
|-------------|---------------|-------------|
|
|
||||||
| int16_t | majorVersion | The major version of the metadata format used |
|
|
||||||
| int16_t | minorVersion | The minor version of the metadata format used |
|
|
||||||
| declaration... | declarations | A list of all present declarations, separated by new lines '\n' and terminated by two new lines ('\n\n') |
|
|
@ -1,161 +0,0 @@
|
|||||||
# Type architecture
|
|
||||||
|
|
||||||
**NOTE: This is dated and the first iteration of the type system.**
|
|
||||||
|
|
||||||
This document describes how the ++C types work. The ++C's type system is made to be as simple as possible so the compiler can be simpler, hence faster. That's wh this is (I think) a type system unlike any other
|
|
||||||
|
|
||||||
## Members
|
|
||||||
Each type may contain only fields. A field has all the common modifier that each definition has (public, private, static, etc), as well as a `readonly` modifier, type and a name. Fields must have a static type.
|
|
||||||
|
|
||||||
Note that nonsealed methods are basically readonly fields and each time they are overrided the constructor assigns a new value to them
|
|
||||||
|
|
||||||
### Methods
|
|
||||||
Methods are instance functions, which basically are either: delegates that take as a first argument a reference to the instance or a static function that takes a first argument reference to the instance. The first are real methods and the seconds are instance functions, or as I like to call them - 'demimethods'. For the most part, we use demimethods, unless when a method is marked abstract or virtual. Then, we use real methods - delegate fields. So in a sence, the following class:
|
|
||||||
```cs
|
|
||||||
public abstract class Shape {
|
|
||||||
public abstract float Perimeter { get; }
|
|
||||||
public abstract float Area { get; }
|
|
||||||
|
|
||||||
public override sealed string ToString() {
|
|
||||||
return $"Perimeter: {Perimeter}, Area: {Area}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Would actually be
|
|
||||||
```cs
|
|
||||||
public class Shape {
|
|
||||||
public delegate float PerimeterGetter(Shape shape);
|
|
||||||
public delegate float AreaGetter(Shape shape);
|
|
||||||
|
|
||||||
public readonly PerimeterGetter Perimeter_Get = null;
|
|
||||||
public readonly AreaGetter Area_Get = null;
|
|
||||||
|
|
||||||
public static string ToString(Shape shape) {
|
|
||||||
return $"Perimeter: {Perimeter}, Area: {Area}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Events
|
|
||||||
Events are a whole another level of mess, that for this edition of ++C we won't get into. Otherwise, the compiler would refer to a user-defined class `$_event`, which would be used for event handling. For now, compilers are allowed to scream at the programmer for using events
|
|
||||||
|
|
||||||
## Type classification
|
|
||||||
|
|
||||||
### Kinds of types
|
|
||||||
There are again two categories by which we classify types:
|
|
||||||
- By value / by reference (struct or class)
|
|
||||||
- Is it callable
|
|
||||||
Since both are pretty much stored as booleans, we can have normal classes that implement a callee, as well as structures implementing a callee and event interfaces that implement a callee. Because of that, ++C delegates work like Java's functional interfaces and delegates just cover up that mess. Still, all pointers are callable and the return type depends on the type that the pointer points to. Each pointer may be called with any arguments. This is how jumps are executed
|
|
||||||
|
|
||||||
### Simple, complex and dynamic types
|
|
||||||
|
|
||||||
This concept is introduced to allow the programmer to get the most out of the type architecture of ++C. In some contextes we can't use references or pointers, or sometimes we outright need a constant value, so that's why we made two categories in which to classify types:
|
|
||||||
- By contained references:
|
|
||||||
- Simple - the type must be a by-value structure that is not a pointer and contain no fields that are complex
|
|
||||||
- Complex - all types that aren't simple
|
|
||||||
- By their size:
|
|
||||||
- Static - their size is known compile-time
|
|
||||||
- Dynamic - their size is unknown, for example, the `void` type. For now, no other dynamic types exist
|
|
||||||
|
|
||||||
There is also a jargon, called a 'primitive' type. That basically means that the type is a language feature, like `$int_...`, `void`, etc...
|
|
||||||
|
|
||||||
Combinations of all 4 categories can be seen: simple dynamic types are types that contain no references, yet we don't know the size of.
|
|
||||||
|
|
||||||
All of the above-mentioned types may be used as generic parameter constraints (`simple`, `complex` and `dynamic`). By default, all types have a constraint `complex dynamic`, which covers all types. It is important to understand that simple types can be used in contexts where dynamic types are required
|
|
||||||
|
|
||||||
#### What about C# dynamic types
|
|
||||||
|
|
||||||
Since C#'s architecture is leaps and bounds more complex than ++C's one, we can't have dynamic types without a rework of the compiler from the ground up. Future plans for libraries being able to inject code into the compiler are a feature which may be implemented in the very far future, but for now, it is impossible to have C#-like dynamic objects. For now, the way is to use the `object` and `any` types or to use a library that supports full reflection.
|
|
||||||
|
|
||||||
## The purge
|
|
||||||
|
|
||||||
I like calling the point in which the user-friendly syntax-sugary code to what the compiler modules after the abstract tree constructor consider 'ledgible'. No enums, methods, constants or interfaces are safe from THE PURGE. Jokes aside, it is an important step in order to make the compiler a much simpler engine overall. The purge can be compared to the following: imagine a cup of mixed sand and sugar. The purge is pouring water over the whole thing - all the 'sugar of syntax' is being destroyed and only the sand remains.
|
|
||||||
|
|
||||||
## Arrays
|
|
||||||
|
|
||||||
In ++C, arrays are probably the most complicated feature. They are a language feature (primitive types), so you can rely on their existence. Still, due to the philosophy of ++C, these classes provide just enough API so that a core library can be as flexible as possible. There are two kinds of arrays: static and dynamic.
|
|
||||||
|
|
||||||
### Static arrays
|
|
||||||
|
|
||||||
A static array is an array with a type-fixed size (the array's size is a generic constant). They act like C and C++ arrays and can be used so that the compiler activates some of its optimizations. Static arrays are simple primitive static types (have a fixed size, are a language feature and don't include any pointers) The static array can be represented by the following code:
|
|
||||||
```cs
|
|
||||||
public sealed struct StaticArray<T, int size> where T: static {
|
|
||||||
public sealed T this(size_t i) { get; set; }
|
|
||||||
public sealed T* operator implicit() => ...;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Since there can be a static array of static arrays, we've decided that the static arrays won't be multidimensional. That means that in order to make a multidimensional array you need to do the following:
|
|
||||||
```cs
|
|
||||||
int[][] arr = new int[10][10];
|
|
||||||
```
|
|
||||||
Still, there is the syntactic sugar of `new T[n,m]`, which will basically get replaced by the above syntax.
|
|
||||||
|
|
||||||
The only limitation of these arrays is that their size can be only static, which is what we're going to fix with dynamic arrays.
|
|
||||||
|
|
||||||
### Dynamic arrays
|
|
||||||
The dynamic arrays are pretty much like C#'s arrays - they don't have a compile-time fixed size, which means that it is mandatory to allocate their memory runtime. Dynamic arrays are initialized as dynamic only when a non-constant size is used. The other major difference is that dynamic arrays can be multidimensional. This time, the core library must provide a `$_dynarr` type, which follows the following structure:
|
|
||||||
```cs
|
|
||||||
public sealed class $_dynarr<T><size_t dims = 1> where T: static {
|
|
||||||
public T this(params size_t[dims] indexes) { get; set; }
|
|
||||||
public DynamicArray(params size_t[dims] indexes) => ...;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The compiler is going to call the initialized when needed and is going to use the `this[]` indexer to fill the array with data. The dynamic initializer is only called when an array with a non-constant size is initialized. The compiler will try to call the `$_dynarr` initializer as little as possible.
|
|
||||||
|
|
||||||
## Enums
|
|
||||||
As it has been already discussed, this compiler's aim is to have as simplistic architecture as possible. Because of that, a lot of generalization must happen behind the scenes, and that doesn't leave any room for the enum type. Still, you'd be hard-pressed to find any language without an enum construct, so ++C has one, too. Still, an enum in the compiled code is basically a struct that contains just one field (of the specified type), a ToString and casters. An enum may contain constants of any simple static type (by default it is `int`, but can be specified by specifying what type the enum 'extends'). In ++C enums are strikingly similar to the ones in Java, although in Java they are somewhat more flexible.
|
|
||||||
|
|
||||||
The following code:
|
|
||||||
```cs
|
|
||||||
struct Vec2I {
|
|
||||||
public int X;
|
|
||||||
public int Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MyEnum: Vec2I {
|
|
||||||
Up = new Vec2I(0, -1),
|
|
||||||
Down = new Vec2I(0, 1),
|
|
||||||
Left = new Vec2I(-1, 0),
|
|
||||||
Right = new Vec2I(1, 0),
|
|
||||||
Zero = new Vec2I(0, 0)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
would be equivalent to:
|
|
||||||
```cs
|
|
||||||
struct Vec2I { ... }
|
|
||||||
class MyEnum {
|
|
||||||
public const Vec2I Up = new Vec2I(0, -1);
|
|
||||||
public const Vec2I Down = new Vec2I(0, 1);
|
|
||||||
public const Vec2I Left = new Vec2I(-1, 0);
|
|
||||||
public const Vec2I Right = new Vec2I(1, 0);
|
|
||||||
public const Vec2I Zero = new Vec2I(0, 0);
|
|
||||||
|
|
||||||
private Vec2I val;
|
|
||||||
|
|
||||||
public Vec2I operator implicit() => val;
|
|
||||||
public static MyEnum operator explicit(Vec2I vec) => new MyEnum(vec);
|
|
||||||
|
|
||||||
private MyEnum(Vec2I vec) {
|
|
||||||
val = vec;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
switch (val) {
|
|
||||||
case Up: return "Up";
|
|
||||||
case Down: return "Down";
|
|
||||||
case Left: return "Left";
|
|
||||||
case Right: return "Right";
|
|
||||||
case Zero: return "Zero";
|
|
||||||
default: return val.ToString();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If having this `ToString` method autogenerated is not wanted, it can be turned off in the compiler.
|
|
||||||
Unlike its C# counterpart, in ++C you can define properties and methods in enums, but they can't be virtual, nor abstract.
|
|
||||||
|
|
||||||
## Interfaces
|
|
||||||
|
|
||||||
Basically classes but all methods abstract and shit
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
|||||||
# References
|
|
||||||
|
|
||||||
A big part of what makes ++C tick are references. They are very similar to Rust's idea of references, and that's the language, from which inspiration for the references has been taken.
|
|
||||||
|
|
||||||
## What are they?
|
|
||||||
|
|
||||||
References are basically pointers, but have no arithmetic representation. They are used to point to an object of any type and any size. Usual use cases for references are to mutate an object with a reflection in all "users" of the reference. A practical example would be the following:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
import System.Console; // System.Console, in contrast to C#, is a namespace
|
|
||||||
|
|
||||||
// Classes are ref by default, if this was a struct,
|
|
||||||
// ref would have to be specified in front of MyClass
|
|
||||||
class MyClass {
|
|
||||||
int Field1 = 0;
|
|
||||||
int Field2 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
List<MyClass> a = new var(), b = new var(); // Types are implied, like in C#
|
|
||||||
MyClass obj = new var();
|
|
||||||
|
|
||||||
a.Add(obj);
|
|
||||||
b.Add(obj);
|
|
||||||
|
|
||||||
WriteLine(a[0].Field1); // 0
|
|
||||||
WriteLine(a[0].Field2); // 0
|
|
||||||
|
|
||||||
obj.Field1 = 10;
|
|
||||||
|
|
||||||
WriteLine(a[0].Field1); // 10
|
|
||||||
WriteLine(a[0].Field2); // 10
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Another reason to use references are to output multiple values or to change a value that is in the callee's scope:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
import System;
|
|
||||||
import System;
|
|
||||||
|
|
||||||
void split(float number, ref int whole, ref float fractional) {
|
|
||||||
whole = (int)number;
|
|
||||||
fractional = number - whole;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float mynum = CLI.AskFloat("Input a number"); // The built in CLI library
|
|
||||||
|
|
||||||
// When a ref is required, new variables can be passed as well
|
|
||||||
split(mynum, int whole, float fractional);
|
|
||||||
|
|
||||||
CLI.Print("Whole:", whole);
|
|
||||||
CLI.Print("Fractional:", fractional);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Difference between references and values
|
|
||||||
|
|
||||||
Although references are used to make pointers work more like values, they are still different in a major way: after you've passed a reference to a function and that function takes ownership of that reference (or in Rust slang, "borrows" it), you can no longer use it. This is so because someone (after all) has to clean up the memory of the reference, and that is the current owner of the reference: once it falls out of the function scope (the function returns and it still owns the reference), the reference will be freed.
|
|
||||||
|
|
||||||
## Sharing references
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
# Roadmap
|
|
||||||
|
|
||||||
Since ++C is such a big project, I want to take my time with implementing it. Here's the general plan of how development process is going to go:
|
|
||||||
|
|
||||||
- Tokenization (done)
|
|
||||||
- Abstract syntax tree (done)
|
|
||||||
- Structs
|
|
||||||
- $_int_x types
|
|
||||||
- Parameterless void functions
|
|
||||||
- Variables
|
|
||||||
- Assignments
|
|
||||||
- Calls
|
|
||||||
- Void functions with parameters
|
|
||||||
- Non-void functions with parameters
|
|
||||||
- Function members
|
|
||||||
- Operators
|
|
||||||
- Constructors
|
|
||||||
- Pointers
|
|
||||||
- Static and dynamic arrays
|
|
22
doc/statements/ifs-and-elses.md
Normal file
22
doc/statements/ifs-and-elses.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Ifs and elses
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
Each if statement consists of the `if` keyword, followed by an condition expression in parenthesis, followed by an else body statement. Optionally, the statement may be followed by an `else` keyword, which is followed by a body statement on its own.
|
||||||
|
|
||||||
|
```c#
|
||||||
|
if (condition) body
|
||||||
|
// or
|
||||||
|
if (condition) body else else_body
|
||||||
|
```
|
||||||
|
No semicolons are required after the body of an if/else statement, unless the body statement itself requires one.
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
The condition may be of any integral type.
|
||||||
|
|
||||||
|
First, the condition is checked. If it's non-zero, the body is executed. If it's a zero (and there's an else body), the else body gets executed.
|
||||||
|
|
||||||
|
## Optimizations
|
||||||
|
|
||||||
|
The compiler may either 1. omit condition checking and the else body, and just keep the body, if the condition is determined to be non-zero, or 2. omit the condition check and the body, if the condition is determined to be zero.
|
@ -3,7 +3,4 @@
|
|||||||
Statements are an important part of each C-like language, including this one. As in every single C-like language under the sun, the statements are more or less the same. Still, there are some differences that set apart ++C and C-like langs.
|
Statements are an important part of each C-like language, including this one. As in every single C-like language under the sun, the statements are more or less the same. Still, there are some differences that set apart ++C and C-like langs.
|
||||||
|
|
||||||
1. [Ifs and elses](ifs-and-elses.md)
|
1. [Ifs and elses](ifs-and-elses.md)
|
||||||
2. [Whiles and do-whiles](whiles-and-do-whiles.md)
|
2. [Whiles and do-whiles](whiles-and-do-whiles.md)
|
||||||
3. [For loops](for-loops.md)
|
|
||||||
4. [Switch statements](switch-case.md)
|
|
||||||
4. [Throw-try-catch statements](try-catch.md)
|
|
40
doc/statements/whiles-and-do-whiles.md
Normal file
40
doc/statements/whiles-and-do-whiles.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# While loops
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
A while loop starts with the `while` keyword. After it, the condition is put in parentheses, and then the while statement ends with a body statement. The while statement doesn't require an ending semicolon, if the body statement doesn't require one.
|
||||||
|
|
||||||
|
```c
|
||||||
|
while (expression) statement
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
The condition may be of any integral type.
|
||||||
|
|
||||||
|
The execution of the loop will start from checking whether or not the condition is non-zero. If so, The body of the loop will be executed, and the check will be performed again. This will be done, until the condition becomes zero. If the condition is initially zero, the body won't get executed.
|
||||||
|
|
||||||
|
## Optimizations
|
||||||
|
|
||||||
|
The compiler may either 1. omit the while loop if the condition is determined to be a zero, or 2. omit the condition check and everything after the loop if the condition is determined to be non-zero.
|
||||||
|
|
||||||
|
|
||||||
|
# Do-while loops
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
A while loop starts with the `do` keyword, followed by the body statement. After it, there is a `while` keyword, followed by the condition, in parentheses, followed by a semicolon.
|
||||||
|
|
||||||
|
```c
|
||||||
|
do statement while (expression);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
The condition may be of any integral type.
|
||||||
|
|
||||||
|
The execution of the loop will start from executing the body. Then, if the condition is non-zero, the whole process will repeat. If not, execution continues after the loop.
|
||||||
|
|
||||||
|
## Optimizations
|
||||||
|
|
||||||
|
The compiler may either 1. omit condition checking, and just keep the body, if the condition is determined to be a zero, or 2. omit the condition check and everything after the loop if the condition is determined to be non-zero.
|
@ -1,11 +0,0 @@
|
|||||||
# About inheritance
|
|
||||||
|
|
||||||
Now, you might be wondering why is there no inheritance in ++C. There's a really simple reason: I don't think an OOP language needs it. It makes your code very rigid and generally harder to understand. But that's not the only reason: the ++C's architecture is made to be simple, and the inheritance, architecturally, make the architecture very complicated.
|
|
||||||
|
|
||||||
# How to use inheritance patterns
|
|
||||||
|
|
||||||
There is a saying in the programmer community: composition over inheritance. What does this mean? In short, it means that instead of inheritance, you'll have a reference to your super object as a field. An equivalent of the abstract method would be to pass the `this` (as a contract) to the super object.
|
|
||||||
|
|
||||||
# Class-contract hybrid
|
|
||||||
|
|
||||||
What does this mean? This means that there will be an option to define `contract` functions and properties, which will be exported in a separate contract type. That type will be exported, but it can't be referenced, unless inside the class-contract hybrid. Then, the contract can be accessed with the `contract` keyword, which usually is invalid in a class definition. This will be useful for accepting the contract as a constructor parameter, which will be useful for composition (over inheritance).
|
|
@ -1,40 +0,0 @@
|
|||||||
# Contracts
|
|
||||||
|
|
||||||
Contracts are like interfaces in other languages: they are a list of functions, that a type must implement in order to "comply" with the contract. Still, there are a few differences, that will be cleared later in this document.
|
|
||||||
|
|
||||||
**ONE IMPORTANT THING**: Unlike most languages, contracts are their own data structure, much like `struct` and `class`. They are not used to "cover up" specific about an object, they are used to keep the functions of an object we need, basically a list of delegates. Contracts are slower than regular class function calls, so using contracts sparingly is recommended.
|
|
||||||
|
|
||||||
## Syntax and naming conventions
|
|
||||||
|
|
||||||
The syntax of the contract is similar to the C#'s syntax, but instead of `interface`, the keyword `contract` is used.
|
|
||||||
|
|
||||||
```c#
|
|
||||||
[export] contract (name) [: (contract1), (contract2), ...] {
|
|
||||||
(definition1);
|
|
||||||
(definition2);
|
|
||||||
...
|
|
||||||
(definitionN);
|
|
||||||
}
|
|
||||||
// or
|
|
||||||
[export] contract (name) [: (contract1), (contract2), ...];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dynamic contracts
|
|
||||||
|
|
||||||
A major difference that sets apart the contracts from the interfaces is that contracts can be "dynamically" created. That is so, because contracts just contain function pointers and the instance of the contractor. That means that even if the contractor doesn't know about the contract, it still can comply with it, so a contract can still be derived from it. That is done compile-time, despite the implied dynamicity of the dynamic contracts. Basically, if a type has all the functions a contract requires, a contract can still be instantiated, via an explicit cast. Dynamic contracts are particularly useful for requiring any sort of operators.
|
|
||||||
|
|
||||||
## Determining if a type complies with a contract
|
|
||||||
|
|
||||||
It is important to know how the compiler knows if a type compiles with a contract. A core ideology of this compiler is that there should be no unnecessary limitations, so keep that in mind:
|
|
||||||
|
|
||||||
1. The target type has to contain all the names of the functions that the contract requires
|
|
||||||
2. The overload count of each function should be matched
|
|
||||||
3. The target function has to have the exact same signature as the contract function
|
|
||||||
|
|
||||||
## In-memory structure
|
|
||||||
|
|
||||||
A contract consists of two "fields": a pointer to the `this` context and a pointer to an ordered array of all the function pointers that the contract requires. When a static cast occurs, the contractor has already allocated a space in the executable for its contract function pointers (CFP), so it only needs to give a pointer to itself and to the CFP.
|
|
||||||
|
|
||||||
## Contract reflection
|
|
||||||
|
|
||||||
Since a contract hides an object behind it, we only know the type of the contract, not the contractor. Still, there's an option to enable full reflection, which will create an additional field in the contract, that will keep the contractor's type ID. Then, whenever you use `typeof contract`, instead of getting the contract type, you'll get the type of the contractor.
|
|
13
doc/type-system/fields.md
Normal file
13
doc/type-system/fields.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Fields
|
||||||
|
|
||||||
|
In ++C, structs may have only field members. Fields contain data of arbitrary type (except a type that references the struct itself). A field can be const (the memory behind the field might still be settable, but the field itself cannot be assigned to). ++C makes no guarantees for the order of fields or their alignment.
|
||||||
|
|
||||||
|
An example structure with fields:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
export struct my_struct_t {
|
||||||
|
number: float;
|
||||||
|
integer: int;
|
||||||
|
array: char[];
|
||||||
|
}
|
||||||
|
```
|
@ -1,10 +1,8 @@
|
|||||||
# Type system
|
# Type system
|
||||||
|
|
||||||
In this language, types are much simpler and harder than you'd think at the same time. Still, there are a lot of similarities with C# and Java-like languages, so for anyone who has any Java or C#-related background, getting used to this language's type system won't be too much of a hassle.
|
In ++C types are extremely simple so that transpilation to any language can be made easier. Types in ++C are strongly inspired by C, so there are only structs and internal numeric types.
|
||||||
|
|
||||||
Since this lang originally was meant to be a one for one clone of C#, the type system is stronly influenced by OOP, but still, ++C's type system is not 100% OOP, since there's no inheritance, and there won't be any for the forseeable future, so this is basically like the C's type system with generics and polymorphism (from contracts)
|
|
||||||
|
|
||||||
Table of contents:
|
Table of contents:
|
||||||
1. [Members](./members.md)
|
1. [Fields](./fields.md)
|
||||||
2. [Contracts](./contracts.md)
|
2. [Integrated types](./integrated-types.md)
|
||||||
4. [Integral types](./integral-types.md)
|
3. [The void type](./void-type.md)
|
@ -1,28 +0,0 @@
|
|||||||
# Integral types
|
|
||||||
|
|
||||||
Despite what you might think, the usual `char`, `int`, `short`, etc. are not integral types in ++C. Rather, there's a special type, called an integral type.
|
|
||||||
|
|
||||||
## Basics
|
|
||||||
|
|
||||||
An integral type is an integer, made up of any amount of bytes. It will by default define all required integral operators.
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
The name of this type in-lang is `$_int_x` (signed) and `$_uint_x` (unsigned), where x is any integer, bigger than 0, that is a multiple of 8. Any specified int will be automatically created.
|
|
||||||
|
|
||||||
```
|
|
||||||
$_int_16 myShort = 25000;
|
|
||||||
$_int_24 weird = 2000;
|
|
||||||
```
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
Behind the scenes, those types don't actually exist. Rather, they will be compiled to their according native instructions. In [PPCIL](../citation-needed.md), this will be outputted to their dedicated instructions (`ADD`, `SUB`, `MUL`, `DIV`, `MOD`, etc.), and then will be either interpreted, or translated to their according assembly instructions.
|
|
||||||
|
|
||||||
## Limits
|
|
||||||
|
|
||||||
Integral types of absurd sizes (for example, 69 bytes) are allowed and won't throw an error, but when the code is either 1. compiled to native code or 2. interpreted usage of such absurd integral types will result in a runtime / compilation error or the type will get truncated to the system's maximum integer size. The max size of an integral type is the longest integer a processor provides native instructions for. Still, the integral types `$_int_8`, `$_int_16`, `$_int_32`, `$_int_64` (and their respective unsigned counterparts) are always present, no matter what the system is.
|
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
The compiler might decide to increase the width of an integer, for performance sake. Most compilers will have an option to disable that, but its good to keep that in mind. This is especially true about integers of absurd sizes (`$_int_56`, for example). However, normal integral types may be widened, too: `$_int_8` might be widened to a 16-bit number to better match the underlying system's word size.
|
|
38
doc/type-system/integrated-types.md
Normal file
38
doc/type-system/integrated-types.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Integrated types
|
||||||
|
|
||||||
|
In ++C, there are several integrated types:
|
||||||
|
- `void`
|
||||||
|
- `int8_t`, `int16_t`, ... `int256_t`
|
||||||
|
- `fl16_t`, `fl32_t`, `bigfl_t`
|
||||||
|
|
||||||
|
## Void type
|
||||||
|
|
||||||
|
The void type is for all intents and purposes an empty structures. All other empty structs are convertible from and to a `void`. There is also a literal `null`, that represents a value of type `void`. All types that want to implement a default value will implement an explicit cast from `void`. `void` is the return value of all procedures (functions with no return values). All `void` variables/arguments will be omitted.
|
||||||
|
|
||||||
|
## Integral types
|
||||||
|
|
||||||
|
An integral type is an integer, made up of any amount of bytes. It will by default define all integral operators:
|
||||||
|
- Addition / subtraction
|
||||||
|
- Multiplication / division / modulo
|
||||||
|
- Negating
|
||||||
|
- Bitwise AND, OR, XOR and flipping
|
||||||
|
- Comparisons
|
||||||
|
|
||||||
|
Although the underlying language/architecture might not support a certain size of integers, the translator will guarantee that these types will work as expected.
|
||||||
|
|
||||||
|
## Float types
|
||||||
|
|
||||||
|
A float type is a number with a floating fraction point. Although it is not required, it is almost guaranteed that the float types are an implementation of the IEEE float standard. Still, some platforms that don't support float maths will have to implement functions to do so, which leaves it up to the translator author to decide how the floats will work. Of course, the float type implements all algebraic operations for integers, including some float-specific:
|
||||||
|
- Floor, ceiling, round
|
||||||
|
- Square root
|
||||||
|
- Logarithm
|
||||||
|
- Exponentiation
|
||||||
|
- Sine, cosine, tangent
|
||||||
|
- Int to float conversion
|
||||||
|
- Float to int conversion
|
||||||
|
|
||||||
|
There are tree variants of the float: `fl32_t`, `fl64_t` and `bigfl_t`. `fl32_t` is the closest representation of the IEEE single float that the system can support. `fl64_t` is the same as `single`, but for double floats. `bigfl_t` is the biggest float that the system supports. Usually, `fl64_t` and `bigfl_t` are the same, but sometimes `fl32_t`, `fl64_t` and `bigfl_t` overlap. `fl32_t` and `fl64_t` are guaranteed to be 32-bit and 64-bit respectively. `bigfl_t` is used for systems that natively work with bigger floats (for example, the x86_64 architecture supports 80-bit floats).
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
These types don't actually exist, but rather are converted to special statements (native operators), that later will be transpiled to the underlying language's counterparts.
|
@ -1,69 +0,0 @@
|
|||||||
# Members
|
|
||||||
|
|
||||||
In ++C, there are two main types of members: fields and functions. Anything else is derived from those types of members, so they'll be the first ones to be included in this document:
|
|
||||||
|
|
||||||
## Fields
|
|
||||||
|
|
||||||
The fields are the simplest types of members, they basically contain any type of data in them. They are stored in the structure (class or struct) sequentially, and are the first members to be stored in a structure.
|
|
||||||
|
|
||||||
Fields can be readonly, which means that although the memory that keeps the field can be set, it won't be set by the ++C lang (no guarantee for anything outside ++C).
|
|
||||||
|
|
||||||
Fields that are readonly and are set to a constant (compile-time know value), are considered a const. This means that they won't be included in the memory layout of the structure.
|
|
||||||
|
|
||||||
Example fields:
|
|
||||||
|
|
||||||
```c#
|
|
||||||
export class MyClass {
|
|
||||||
float field1;
|
|
||||||
int field2;
|
|
||||||
char[] field3;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
|
|
||||||
Functions are more in-depth explained [here](../functions.md).
|
|
||||||
|
|
||||||
Functions as members have a `this` specifier, where the `this` type is the container type for the function.
|
|
||||||
|
|
||||||
```c#
|
|
||||||
import System.Console;
|
|
||||||
|
|
||||||
export class MyClass {
|
|
||||||
export int x, y;
|
|
||||||
export void myFunc() {
|
|
||||||
WriteLine("%d", x + y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Adding a `this` specifier to a member function is illegal, as you can see:
|
|
||||||
|
|
||||||
```c#
|
|
||||||
export class MyClass {
|
|
||||||
void myfunc this int() {
|
|
||||||
// ^^^^^^^^ Error here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
Operators, like functions, take as a `this` specifier the container type. Operators are used in expressions
|
|
||||||
|
|
||||||
## Constructors
|
|
||||||
|
|
||||||
Constructors in ++C are nameless member functions. They're called when an object has been allocated, but not yet instantiated. The constructor's job is to initialize the object, according to the provided parameters.
|
|
||||||
|
|
||||||
Example constructor:
|
|
||||||
```ts
|
|
||||||
export class MyClass {
|
|
||||||
export int x, y;
|
|
||||||
export (int x, int y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is one of the many major differences from most languages: in ++C, constructors don't have a name, as well as a return type. In result, this makes the constructor just an argument list with a body
|
|
@ -1,21 +0,0 @@
|
|||||||
# Standard type notation (STN)
|
|
||||||
|
|
||||||
This is a system, by which each type in the ++C language can be recognized and is used on a very low level to identify types quickly.
|
|
||||||
|
|
||||||
## Structure layout
|
|
||||||
|
|
||||||
Name | Type | Description
|
|
||||||
----------|--------|-------------
|
|
||||||
name | char[] | A null-terminated string, containing the name of the type. This must be a legal ++C identifier (citation needed)
|
|
||||||
genTypes | STN[] | An array, containing the same amount of STNs, as type parameters in the specified type
|
|
||||||
genConsts | any[] | An array, of undefined length, containing the same amount of tightly-packed constants (in their little-endian byte representation) as const parameters in the specified type
|
|
||||||
|
|
||||||
## In-language representation
|
|
||||||
|
|
||||||
In ++C, there's a special type, called `$_stn`, that is basically a dynamic byte array (`$uint_8[]`), that keeps a byte-wise
|
|
||||||
|
|
||||||
## Metadata representation
|
|
||||||
|
|
||||||
In metadata, types of fields, properties, functions and arguments are represented in this way.
|
|
||||||
|
|
||||||
Also, in metadata, there's a sorted index of all names, followed by an offset pointer to the associated definition (might not be a type)
|
|
7
doc/type-system/void-type.md
Normal file
7
doc/type-system/void-type.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# The `void` type
|
||||||
|
|
||||||
|
In ++C, there's an integrated `void` type. The `void` type is an empty structure, and has a special way of passing around: it doesn't get passed around, since there's only one possible value of `void`. It can be converted from any value, and to any value. The conversion of a `void` to a type results in a value of that type, filled with zeroes.
|
||||||
|
|
||||||
|
# The `null` constant
|
||||||
|
|
||||||
|
This constant is the only possible value of `null`. It can be returned by `void` functions. `null` can be used to initialize a value to a zeroed-out version of itself. If a struct wants to implement a different default value, it should implement a conversion from `void` to itself.
|
@ -1,61 +1,27 @@
|
|||||||
# Versions in ++C
|
# Versions in ++C
|
||||||
|
|
||||||
This version format will be used all troughout the ++C ecosystem, so it is important to be familiar with this
|
This version format will be used all throughout the ++C ecosystem, so it is important to be familiar with this
|
||||||
|
|
||||||
|
## What is a breaking change?
|
||||||
|
|
||||||
|
A breaking change is a change in the code, that makes previous code, using the API, broken. There are two types of breaking changes: big ones and linking ones. A linking breaking change is a breaking change that only prevents the API to be linked to a project, requiring an older version, so a linking breaking change requires a recompilation. A big breaking change makes old code incompatible, so it requires any amount of tweaking to get the old code running again.
|
||||||
|
|
||||||
|
## What is an incremental change in ++C?
|
||||||
|
|
||||||
|
An incremental change is a change in the code base that doesn't affect compilation or linking of older code in no capacity. Such changes may expose new API, but never break old one.
|
||||||
|
|
||||||
## Segments
|
## Segments
|
||||||
|
|
||||||
A version in ++C is made up of three segments: major, minor and revision.
|
A version in ++C is made up of three segments: major (release), minor (breaking) and revision (incremental).
|
||||||
|
|
||||||
### Major
|
### Major (release)
|
||||||
|
|
||||||
The major segment is a 16-bit unsigned integer. It is used to represent breaking changes, which means that 2.0.0, 1.0.0 and 3.0.0 are incompatible. It is reccomended that a project starts from major version 0 (alpha), until a stable state is achieved. Then, release all breaking changes in big groups, and increase the major version.
|
The major segment is a 16-bit unsigned integer. It is used to represent big breaking changes, usually complete overhauls of the public API. This version segment has to be equal to the target version, so that the target is compatible. This version segment rarely changes.
|
||||||
|
|
||||||
#### What is a breaking change in ++C?
|
### Minor (breaking)
|
||||||
|
|
||||||
A breaking change is a behavioural change (a change in the body of a function that is 1. not a bug fix or 2. a fix of a bug that the users relied upon, or 3. a change that leads to different outputs with the same given inputs), change of the signature of a function, removing definitions.
|
The minor segment is a 16-bit unsigned integer. It is used to represent linking breaking changes. All code, compiled with a previous minor version must be compatible with the current minor version, but liking to a different minor version will be broken.
|
||||||
|
|
||||||
### Minor
|
### Revision (incremental)
|
||||||
|
|
||||||
the minor segment is a 16-bit unsigned integer. It is used to represent incremental changes, so bigger minor versions are compatible with bigger requirements (if a reqirement is 0.1.0, then 0.5.0 and 0.1.0 are compatible, but 0.0.0 is not). It is reccomended that after an increase of the major component this is reset (to 0), and increased after each major incremental addition to your project.
|
The revision segment is a 32-bit unsigned integer. It represents incremental changes. All code, compiled with a previous minor version must be compatible with the current minor version, and linking to a newer revision version must be possible.
|
||||||
|
|
||||||
#### What is an incremental change in ++C?
|
|
||||||
|
|
||||||
An incremental change is the addition of a definition, without that breaking previous code, for example, adding an overload that will overtake a previous one:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
bool func(short a) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
// Added function:
|
|
||||||
void func(int a) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
here, if someone called `func(10)`, previously, `bool func(short)` would get called, but now, the shorter cast route is `void func(int)`, which leads to a breaking change, because 1. there might be behavioural differences between the two functions and 2. the return types of the two functions differ, so the following code:
|
|
||||||
|
|
||||||
```c++
|
|
||||||
import std;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
printf("%d", func(10));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Will break, because `func(int)` doesn't return a value.
|
|
||||||
|
|
||||||
Regardless, if the introduced overload doesn't break compatibility and doesn't introduce a behavioural change, the change is considered incremental.
|
|
||||||
|
|
||||||
### Revision
|
|
||||||
|
|
||||||
The revision segemnt of a version is a 32-bit unsigned integer. It represents either a build version or a bug fix index. Regardless, this segments represents a change that introduces no change to the public API or its behaviour (you could change the whole inner workings of a project in one revision, but as long as it introduces no breaking changes, that is fine).
|
|
||||||
|
|
||||||
When checking for compatibility between versions, the revision segment is completely igonred, so if the requirement is 5.45.8, 5.45.15 and 5.45.0 are compatible.
|
|
||||||
|
|
||||||
#### When to use revision changes?
|
|
||||||
|
|
||||||
There are three ways to use this segments:
|
|
||||||
|
|
||||||
1. Set it to 0 and don't use it
|
|
||||||
2. Use it as a build index (increase it on each build)
|
|
||||||
3. Use it as a bug fix index (increase it on each bug fixed)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user