Initial commit
This commit is contained in:
commit
5f778db5c2
30
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG] Title of the bug"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Source code**
|
||||||
|
A minimal reproducable ++C source code, that causes the issue
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
Either an intermediate language output, or a binary output. Perferably, both
|
||||||
|
|
||||||
|
**Run output**
|
||||||
|
What is outputted, when the program is ran, if a GUI app, attach screenshots
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. Manjaro 21.2.4]
|
||||||
|
- Compiler version [e.g. 1.0.2]
|
||||||
|
|
||||||
|
**Used libraries:**
|
||||||
|
A list of all used libraries, with their versions
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
21
.github/ISSUE_TEMPLATE/documentation-issue.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/documentation-issue.md
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
name: Documentation issue
|
||||||
|
about: An issue you found with the documentation (README.md, anything from ./doc)
|
||||||
|
title: "[DOC] Title here"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Type**
|
||||||
|
What is the general type of the issue:
|
||||||
|
- misspell - you've found a misspelling somewhere
|
||||||
|
- citation needed - somewhere, citation is needed
|
||||||
|
- unclear - somewhere, the documentation is worded unclearly
|
||||||
|
- not enough info - somewhere in the documentation doesn't provide enough information
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
A short description of what the issue with the documetnation is
|
||||||
|
|
||||||
|
**Location**
|
||||||
|
If applicable, what is the file(s) in which this issue addresses
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
20
.github/workflows/c-cpp.yml
vendored
Normal file
20
.github/workflows/c-cpp.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: C/C++ CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup git repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup toolchain
|
||||||
|
run: sudo apt update -y; sudo apt install -y gcc-multilib g++-multilib valgrind
|
||||||
|
- name: Build
|
||||||
|
run: make profile=release
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
*
|
||||||
|
|
||||||
|
!.github
|
||||||
|
!.github/**
|
||||||
|
|
||||||
|
!doc
|
||||||
|
!doc/**/
|
||||||
|
!doc/**/*.md
|
||||||
|
|
||||||
|
!include
|
||||||
|
!include/**/
|
||||||
|
!include/**/*.h
|
||||||
|
!include/**/*.hh
|
||||||
|
|
||||||
|
!src
|
||||||
|
!src/**/
|
||||||
|
!src/*/**/*.c
|
||||||
|
!src/*/**/*.cc
|
||||||
|
!src/*/**/*.h
|
||||||
|
!src/*/**/*.hh
|
||||||
|
!src/*/proj.txt
|
||||||
|
!src/lsproj.cc
|
||||||
|
|
||||||
|
!scripts
|
||||||
|
!scripts/common.mak
|
||||||
|
!scripts/install.bat
|
||||||
|
!scripts/uninstall.bat
|
||||||
|
|
||||||
|
!LICENSE
|
||||||
|
!Makefile
|
||||||
|
!README.md
|
||||||
|
!.gitignore
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2021 TopchetoEU
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
109
Makefile
Normal file
109
Makefile
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
export MAKEFLAGS += --silent -r
|
||||||
|
export flags=-std=c++17 -Wall -Wno-main -Wno-trigraphs -Wno-missing-braces -Wno-stringop-overflow -m32
|
||||||
|
export ldflags=-L$(bin)/$(profile)
|
||||||
|
export lib=ppc$(version-major)-
|
||||||
|
export profile=release
|
||||||
|
|
||||||
|
############# SETTINGS ############
|
||||||
|
export bin = bin
|
||||||
|
export src = src
|
||||||
|
export inc = include
|
||||||
|
export output = ppc
|
||||||
|
export mainmodule = main
|
||||||
|
export version-major=0
|
||||||
|
export version-minor=0
|
||||||
|
###################################
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
export os = Windows
|
||||||
|
export OS = WINDOWS
|
||||||
|
else
|
||||||
|
export os = $(shell uname)
|
||||||
|
export OS = $(shell echo '$(os)' | tr '[:lower:]' '[:upper:]')
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: build debug leak lint clear install uninstall version
|
||||||
|
|
||||||
|
ifeq ($(profile),release)
|
||||||
|
flags += -O3
|
||||||
|
else ifeq ($(profile),debug)
|
||||||
|
flags += -g
|
||||||
|
ldflags+= -Wl,-rpath=bin/debug
|
||||||
|
endif
|
||||||
|
oldbin := bin
|
||||||
|
export bin := $(bin)/$(profile)
|
||||||
|
|
||||||
|
ifeq ($(os),Windows)
|
||||||
|
|
||||||
|
export mkdir=if not exist "$$1" mkdir "$$1"
|
||||||
|
export rmdir=if exist "$$1" rmdir /s /q "$$1"
|
||||||
|
export echo=echo "$$1"
|
||||||
|
export so=.dll
|
||||||
|
export exe=.exe
|
||||||
|
export CC=gcc
|
||||||
|
export CXX=g++
|
||||||
|
|
||||||
|
export version-build=$(if $(wildcard build.version),$(shell type build.version),0)
|
||||||
|
export binary = $(bin)/$(output)$(version-major)-windows.exe
|
||||||
|
|
||||||
|
build: version
|
||||||
|
echo ======================== Compiling =========================
|
||||||
|
make -f scripts/common.mak
|
||||||
|
if exist $(bin)\++c-windows.exe del $(bin)\++c-windows.exe
|
||||||
|
mklink /H $(bin)\$(output).exe "$(subst /,\,$(binary))" > NUL
|
||||||
|
|
||||||
|
clear:
|
||||||
|
if exist $(subst /,\,$(oldbin)) rmdir /s /q $(subst /,\,$(oldbin))
|
||||||
|
|
||||||
|
.ONESHELL:
|
||||||
|
install: build
|
||||||
|
powershell -Command "start-process cmd -verb runas -args '/K pushd %CD%&set bin=$(bin)&set output=$(output)&.\scripts\install.bat&exit'"
|
||||||
|
# .\scripts\install.bat
|
||||||
|
uninstall:
|
||||||
|
.\scripts\uninstall.bat
|
||||||
|
version:
|
||||||
|
cmd /c "set /a $(version-build) + 1 > build.version"
|
||||||
|
|
||||||
|
else ifeq ($(os),Linux)
|
||||||
|
|
||||||
|
ldflags += -lpthread
|
||||||
|
|
||||||
|
export mkdir=mkdir -p $$1
|
||||||
|
export rmdir=rm -rf $$1
|
||||||
|
export echo=echo "$$1"
|
||||||
|
export so=.so
|
||||||
|
export version-build=$(if $(wildcard build.version),$(shell cat build.version),0)
|
||||||
|
export binary = $(bin)/$(output)$(version-major)-linux
|
||||||
|
|
||||||
|
build: version
|
||||||
|
echo ======================== Compiling =========================
|
||||||
|
make -f scripts/common.mak
|
||||||
|
ln -f $(binary) $(bin)/$(output)
|
||||||
|
|
||||||
|
clear:
|
||||||
|
rm -r $(oldbin)
|
||||||
|
|
||||||
|
install: build
|
||||||
|
echo Installing ++C compiler to your system...
|
||||||
|
sudo cp $(bin)/*.so /usr/lib
|
||||||
|
sudo cp $(bin)/$(output)-linux /usr/bin/$(output)
|
||||||
|
sudo chmod +777 /usr/bin/$(output)
|
||||||
|
sudo chmod +777 $(patsubst $(bin)/%,/usr/lib/%,$(wildcard $(bin)/*.so))
|
||||||
|
uninstall:
|
||||||
|
echo Uninstalling ++C compiler from your system...
|
||||||
|
sudo rm $(patsubst $(bin)/%.so,/usr/lib/%*.so,$(bin)/*.so)
|
||||||
|
sudo rm /usr/bin/$(output)
|
||||||
|
version:
|
||||||
|
echo $$(($(version-build) + 1)) > build.version
|
||||||
|
|
||||||
|
leak: build
|
||||||
|
echo ====================== Leak scanning =======================
|
||||||
|
$(if $(filter $(os),Windows),cmd /C echo.,echo)
|
||||||
|
valgrind --tool=memcheck --leak-check=full --track-origins=yes ./$(binary) $(patsubst %,./%,$(wildcard *.ppc))
|
||||||
|
lint:
|
||||||
|
echo ========================= Linting ==========================
|
||||||
|
cppcheck $(src) --track-origins=yes --suppress=unusedFunction --suppress=missingInclude --enable=all
|
||||||
|
|
||||||
|
else
|
||||||
|
$(error Unknown OS)
|
||||||
|
endif
|
73
README.md
Normal file
73
README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
# ++C (ppc) Lang
|
||||||
|
|
||||||
|
Developed by TopchetoEU
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
### Low-level
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Highly-customizable
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### OOP (Object-oriented)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Partially-reflected
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### Interpreted and compiled
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## What sets this apart from C, C# and C++ (and Java)
|
||||||
|
|
||||||
|
++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).
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## How to compile?
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
- Command: `make`
|
||||||
|
- Install command (after build): `make install`
|
||||||
|
- Uninstall command: `make uninstall`
|
||||||
|
- Output: `bin/++c-linux`
|
||||||
|
- Prerequisites: `gcc`, `make`
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
- Command: `make`
|
||||||
|
- Install command (after build): `make install`
|
||||||
|
- Uninstall command: `make uninstall`
|
||||||
|
- Output: `bin/++c-windows.exe`
|
||||||
|
- Prerequisites: `gcc`, `make` (I use the GnuWin port)
|
||||||
|
### Mac OS X
|
||||||
|
|
||||||
|
Fuck you, get a normal OS
|
||||||
|
|
||||||
|
(just kidding, I can't be bothered to add Mac support)
|
3
doc/citation-needed.md
Normal file
3
doc/citation-needed.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Citation needed
|
||||||
|
|
||||||
|
This means that this leads to a part of the documentation, that hasn't been written yet. Please, log an issue if there isn't already one
|
9
doc/constructs.md
Normal file
9
doc/constructs.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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:
|
27
doc/control-flow/for-loops.md
Normal file
27
doc/control-flow/for-loops.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# 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.
|
48
doc/control-flow/goto.md
Normal file
48
doc/control-flow/goto.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# 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");
|
||||||
|
```
|
56
doc/control-flow/ifs-and-elses.md
Normal file
56
doc/control-flow/ifs-and-elses.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 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.
|
9
doc/control-flow/index.md
Normal file
9
doc/control-flow/index.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Statements and control flow
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
46
doc/control-flow/switch-case.md
Normal file
46
doc/control-flow/switch-case.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# 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");
|
||||||
|
}
|
||||||
|
```
|
67
doc/control-flow/try-catch.md
Normal file
67
doc/control-flow/try-catch.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# 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.
|
80
doc/control-flow/whiles-and-do-whiles.md
Normal file
80
doc/control-flow/whiles-and-do-whiles.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# 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.
|
5
doc/difference-args-params.md
Normal file
5
doc/difference-args-params.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 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.
|
105
doc/functions/index.md
Normal file
105
doc/functions/index.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# 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
|
9
doc/functions/parameters.md
Normal file
9
doc/functions/parameters.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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
|
88
doc/generics.md
Normal file
88
doc/generics.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# 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)
|
19
doc/index.md
Normal file
19
doc/index.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# ++C lang - full documentation
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
1. [Control flow and statements](./control-flow/index.md)
|
||||||
|
2. [Type system](./type-system/index.md)
|
||||||
|
3. [Functions](./functions.md)
|
||||||
|
5. [Generics](./generics.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)
|
145
doc/intermediate.md
Normal file
145
doc/intermediate.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# ++C Intermediate Lang (PPCIL 1.0)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The format of a PPCIL blob is the following:
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct ppcil_t {
|
||||||
|
version_t version;
|
||||||
|
uint64_t consts_size;
|
||||||
|
uint8_t consts[consts_size];
|
||||||
|
uint64_t instructions_n;
|
||||||
|
uint8_t instructions[instructions_n];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version section
|
||||||
|
|
||||||
|
The first thing in a PPCIL blob is the version. The version format is identical with [the one](./versions.md) used ++C ecosystem. It is the first 64-bits in a PPCIL blob. It is used to specify the PPCIL format version used in the blob. An interpreter with an incompatible version can try to interpret the given PPCIL, but should give a warning if it will do so.
|
||||||
|
|
||||||
|
### Constants section
|
||||||
|
|
||||||
|
This section is immediately after the version section. It is used to contain the constants, used by the code. The first part of it is an unsigned QWORDs integer, indicating the amount of bytes, following it, used to keep the constant data, used by the code.
|
||||||
|
|
||||||
|
### Code section
|
||||||
|
|
||||||
|
This section is immediately after the constants section. It is used to contain the actual code. The first part of it is an unsigned QWORDs integer, indicating the count of instructions, following it. The code itself is an array of instructions (unsigned 8-bit integers).
|
||||||
|
|
||||||
|
## The stack and the heap
|
||||||
|
|
||||||
|
The memory layout of a PPCIL interpreter is the usual stack/heap layout, with the only differnce that the stack is a part of the heap, so addresses from the stack can be obtained, like in the heap (if you work with the stack via pointers, all stack guarantees are overriden).
|
||||||
|
|
||||||
|
The stack contains the variables and any temporary calculation data. The heap contains all the constants, static variables and dynamically allocated memory.
|
||||||
|
|
||||||
|
## Variables and parameters
|
||||||
|
|
||||||
|
The variables are contained on the stack and are used to contain temporary data druing the execution of the program. Parameters are the first n variables, that are automatically created and initialized with values by the interpreter. The parameters are passed by the caller of the piece of code. The parameters are the first n variables (where n is the amount of parameters that the function accepts). In some cases, the first variable (parameter) is the `this` context.
|
||||||
|
|
||||||
|
## Instructions set
|
||||||
|
|
||||||
|
The instructions in PPCIL are stack-based. This is so to increase parsing speed of the intermediate language and to bring ++C and PPCIL closer. An instruction consists of an opcode, which is an unsigned 8-bit integer. The following table contains all the operators in PPCIL:
|
||||||
|
|
||||||
|
**NOTE:** the operand notation is as follows: `size(name)`, so a 64-bit operand named count would be notated as follows: `8(count)`
|
||||||
|
|
||||||
|
### Commons
|
||||||
|
|
||||||
|
| Name | Opcode | Operands | Description
|
||||||
|
|-------|--------|----------|-------------
|
||||||
|
| NOP | 0x00 | | Does nothing
|
||||||
|
| PAD | 0x01 | | Pops two variables: a, b and if b < sizeof a, the last sizeof a - b bytes get trimmed from a. Else, b - sizeof a bytes get added at the end of a. The result is pushed
|
||||||
|
| PADC | 0x02 | 8(n) | Executes `PAD` with b = n
|
||||||
|
| EOF | 0xFF | | Ends execution
|
||||||
|
|
||||||
|
### Arithmetics (operandless)
|
||||||
|
|
||||||
|
| Name | Opcode | Description
|
||||||
|
|-------|--------|-------------
|
||||||
|
| ADD | 0x10 | Pops two variables, adds them and pushes the result
|
||||||
|
| SUB | 0x11 | Pops two variables, subtracts them and pushes the result
|
||||||
|
| NEG | 0x12 | Flips the sign of the top variable in the stack
|
||||||
|
| MUL | 0x13 | Pops two variables, multiplies them and pushes the result
|
||||||
|
| IMUL | 0x14 | Pops two variables, multiplies them (signed) and pushes the result
|
||||||
|
| DIV | 0x15 | Pops two variables, divides them and pushes the remainder, then the result
|
||||||
|
| IDIV | 0x16 | Pops two variables, divides them (signed) and pushes the remainder, then the result
|
||||||
|
| AND | 0x17 | Pops two variables, ANDs (a & b) them and pushes the remainder, then the result
|
||||||
|
| OR | 0x18 | Pops two variables, ORs (a | b) them and pushes the remainder, then the result
|
||||||
|
| XOR | 0x19 | Pops two variables, XORs (a ^ b) them and pushes the remainder, then the result
|
||||||
|
| NOT | 0x1A | Flips the bits of the top variable
|
||||||
|
| SL | 0x1B | Pops two variables: a (first popped), b and performs the operation `a >> b`. Pushes the result back on the stack
|
||||||
|
| SR | 0x1C | Pops two variables: a (first popped), b and performs the operation `a << b`. Pushes the result back on the stack
|
||||||
|
| INC | 0x1D | Adds one to the top variable
|
||||||
|
| DEC | 0x1E | Subtracts one from the top variable
|
||||||
|
|
||||||
|
**NOTE:** All operations pick the size of the bigger variable, and do not expand it if an overflow occurs.
|
||||||
|
|
||||||
|
### Float arithmetics (operandless)
|
||||||
|
|
||||||
|
| Name | Opcode | Description
|
||||||
|
|-------|--------|-------------
|
||||||
|
| ADDF | 0x20 | Pops two variables, adds them as floats, and pushes the result
|
||||||
|
| SUBF | 0x21 | Pops two variables, subtracts them as floats, and pushes the result
|
||||||
|
| MULF | 0x23 | Pops two variables, multiplies them as floats, and pushes the result
|
||||||
|
| DIVF | 0x24 | Pops two variables, divides them as floats, and pushes the result
|
||||||
|
| SQRT | 0x25 | Pops a variable and pushes its square root
|
||||||
|
| ITOF | 0x26 | Converts the top variable from a signed int to a 64-bit float
|
||||||
|
| FTOI | 0x27 | Converts the top variable from a signed float to an int
|
||||||
|
| F32 | 0x28 | Converts the top variable from a 64-bit float to a 32-bit float
|
||||||
|
| F64 | 0x29 | Converts the top variable from a 32-bit float to a 64-bit float (first `PAD 4` is executed)
|
||||||
|
|
||||||
|
**NOTE:** All instructions with floats first execute `PAD 8`, unless specified otherwise
|
||||||
|
|
||||||
|
### Stack operations
|
||||||
|
|
||||||
|
| Name | Opcode | Operands | Description
|
||||||
|
|-------|--------|----------|-------------
|
||||||
|
| PUSH | 0x30 | 8(n) | Allocates n bytes on the stack
|
||||||
|
| PUSHC | 0x31 | 8(size), size(val) | Pushes val to the stack
|
||||||
|
| PUSHA | 0x32 | 8(size), size(val) | Pushes val to the stack
|
||||||
|
| LOAD | 0x33 | | Pops a variable = a and pushes `*a`
|
||||||
|
| LOADC | 0x34 | 8(addr) | Executes `LOAD` with a = addr
|
||||||
|
| PUSHW | 0x35 | 2(val) | Executes `PUSHC 2 val`
|
||||||
|
| PUSHD | 0x36 | 4(val) | Executes `PUSHC 4 val`
|
||||||
|
| PUSHQ | 0x37 | 8(val) | Executes `PUSHC 8 val`
|
||||||
|
| VADDR | 0x38 | 8(n) | Pushes the addres of the variable with index n
|
||||||
|
| CADDR | 0x39 | 8(n) | Calculates `n + consts`, where `consts` is the start of the constants blob and pushes that address
|
||||||
|
| DUP | 0x3A | | Duplicates the top variable in the stack
|
||||||
|
| POP | 0x3B | | Pops the last allocated batch of memory from the stack
|
||||||
|
| MOVA | 0x3C | | Pops two variables: a, b and executes `*b = a`
|
||||||
|
| MOVAC | 0x3D | 8(addr) | Executes `SAVE` with b = addr
|
||||||
|
| MOVV | 0x3E | 8(var) | Executes `SAVE` with b = address of var
|
||||||
|
|
||||||
|
### Function operations
|
||||||
|
|
||||||
|
| Name | Opcode | Operands | Description
|
||||||
|
|-------|--------|----------|-------------
|
||||||
|
| CALL | 0x40 | 8(ptr) | Considering ptr points to the body of a function, calls the function
|
||||||
|
| CALLP | 0x41 | | Pops a variable = a an executes `CALL a`
|
||||||
|
| CALLC | 0x43 | | Pops a variable = a and calls a native function in the cdecl convention.
|
||||||
|
| RET | 0x44 | | Pops a variable = a, executes the ++C function end procedure and pushes a to the stack
|
||||||
|
|
||||||
|
### Jumps (operandless)
|
||||||
|
|
||||||
|
| Name | Opcode | Description
|
||||||
|
|-------|--------|-------------
|
||||||
|
| JMP | 0x50 | Pops a variable: n, and offsets the code execution n amount of bytes (relative to the end of the instruction)
|
||||||
|
| JEQ | 0x51 | Pops two variables, and if they're equal, executes `JMP`
|
||||||
|
| JNE | 0x52 | Pops two variables, and if they're not equal, executes `JMP`
|
||||||
|
| JL | 0x53 | Pops two variables, and if the first one is less than the second, executes `JMP`
|
||||||
|
| JG | 0x54 | Pops two variables, and if the first one is greater than the second, executes `JMP`
|
||||||
|
| JLE | 0x55 | Pops two variables, and if the first one is less or equal to the second, executes `JMP`
|
||||||
|
| JGE | 0x56 | Pops two variables, and if the first one is greater or equal to the second, executes `JMP`
|
||||||
|
| JLU | 0x57 | Pops two variables, and if the first one is less than the second (unsigned), executes `JMP`
|
||||||
|
| JGU | 0x58 | Pops two variables, and if the first one is greater than the second (unsigned), executes `JMP`
|
||||||
|
| JLEU | 0x59 | Pops two variables, and if the first one is less or equal to the second (unsigned), executes `JMP`
|
||||||
|
| JGEU | 0x5A | Pops two variables, and if the first one is greater or equal to the second (unsigned), executes `JMP`
|
||||||
|
| JZ | 0x5B | Pops a variable, and if it's zero, executes `JMP`
|
||||||
|
| JNZ | 0x5C | Pops a variable, and if it's not a zero, executes `JMP`
|
||||||
|
|
||||||
|
**NOTE:** All comparasion jumps will first pad the smaller variable to the size of the bigger, then compare them like integers.
|
||||||
|
|
||||||
|
**NOTE2:** All jumps must be within the boundaries of the PPCIL blob.
|
||||||
|
|
||||||
|
**NOTE3:** All jumps are preformed after the address has been padded to the native pointer size
|
151
doc/ppc-metadata.md
Normal file
151
doc/ppc-metadata.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# 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') |
|
161
doc/ppc-type_architecture.md
Normal file
161
doc/ppc-type_architecture.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# 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
|
||||||
|
|
63
doc/references.md
Normal file
63
doc/references.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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
|
||||||
|
|
19
doc/roadmap.md
Normal file
19
doc/roadmap.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
11
doc/type-system/about-inheritance.md
Normal file
11
doc/type-system/about-inheritance.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# 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).
|
40
doc/type-system/contracts.md
Normal file
40
doc/type-system/contracts.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# 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.
|
10
doc/type-system/index.md
Normal file
10
doc/type-system/index.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
1. [Members](./members.md)
|
||||||
|
2. [Contracts](./contracts.md)
|
||||||
|
4. [Integral types](./integral-types.md)
|
28
doc/type-system/integral-types.md
Normal file
28
doc/type-system/integral-types.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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.
|
69
doc/type-system/members.md
Normal file
69
doc/type-system/members.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# 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
|
21
doc/type-system/standard-type-notation.md
Normal file
21
doc/type-system/standard-type-notation.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 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)
|
61
doc/versions.md
Normal file
61
doc/versions.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Versions in ++C
|
||||||
|
|
||||||
|
This version format will be used all troughout the ++C ecosystem, so it is important to be familiar with this
|
||||||
|
|
||||||
|
## Segments
|
||||||
|
|
||||||
|
A version in ++C is made up of three segments: major, minor and revision.
|
||||||
|
|
||||||
|
### Major
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### What is a breaking change in ++C?
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Minor
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### 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)
|
6
include/compiler.hh
Normal file
6
include/compiler.hh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef PPC_COMPILER_H
|
||||||
|
#define PPC_COMPILER_H 1
|
||||||
|
|
||||||
|
#include "compiler/treeifier.hh"
|
||||||
|
|
||||||
|
#endif
|
11
include/compiler/treeifier.hh
Normal file
11
include/compiler/treeifier.hh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef PPC_TREEIFIER_H
|
||||||
|
#define PPC_TREEIFIER_H 1
|
||||||
|
|
||||||
|
#include "utils/message.hh"
|
||||||
|
|
||||||
|
namespace ppc::compiler {
|
||||||
|
// bool treeify(ppc::messages::msg_stack_t &msg_stack, std::string const &filename, std::string const &source, namespace_t *pout);
|
||||||
|
// bool treeify_file(std::string const &filename, ppc::messages::msg_stack_t &pmsg_stack, namespace_t *pout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
27
include/compiler/treeifier/lexer.hh
Normal file
27
include/compiler/treeifier/lexer.hh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/location.hh"
|
||||||
|
#include "utils/message.hh"
|
||||||
|
|
||||||
|
namespace ppc::comp::tree::lex {
|
||||||
|
struct token_t {
|
||||||
|
enum kind_t {
|
||||||
|
NONE,
|
||||||
|
IDENTIFIER,
|
||||||
|
OPERATOR,
|
||||||
|
BIN_LITERAL,
|
||||||
|
OCT_LITERAL,
|
||||||
|
DEC_LITERAL,
|
||||||
|
HEX_LITERAL,
|
||||||
|
FLOAT_LITERAL,
|
||||||
|
STRING_LITERAL,
|
||||||
|
CHAR_LITERAL,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
ppc::location_t location;
|
||||||
|
|
||||||
|
static std::vector<token_t> parse_file(ppc::messages::msg_stack_t &msg_stack, std::string const &filename, std::istream &f);
|
||||||
|
static std::vector<token_t> parse_many(ppc::messages::msg_stack_t &msg_stack, std::string const &filename, std::string const &src);
|
||||||
|
};
|
||||||
|
}
|
189
include/compiler/treeifier/tokenizer.hh
Normal file
189
include/compiler/treeifier/tokenizer.hh
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/location.hh"
|
||||||
|
#include "utils/message.hh"
|
||||||
|
#include "compiler/treeifier/lexer.hh"
|
||||||
|
|
||||||
|
namespace ppc::comp::tree::tok {
|
||||||
|
enum operator_t {
|
||||||
|
LESS_THAN,
|
||||||
|
GREATER_THAN,
|
||||||
|
LESS_THAN_EQUALS,
|
||||||
|
GREATER_THAN_EQUALS,
|
||||||
|
EQUALS,
|
||||||
|
NOT_EQUALS,
|
||||||
|
DOUBLE_AND,
|
||||||
|
DOUBLE_OR,
|
||||||
|
|
||||||
|
SHIFT_LEFT,
|
||||||
|
SHIFT_RIGHT,
|
||||||
|
XOR,
|
||||||
|
AND,
|
||||||
|
OR,
|
||||||
|
NOT,
|
||||||
|
BITWISE_NEGATIVE,
|
||||||
|
|
||||||
|
INCREASE,
|
||||||
|
DECREASE,
|
||||||
|
|
||||||
|
ADD,
|
||||||
|
SUBTRACT,
|
||||||
|
DIVIDE,
|
||||||
|
MULTIPLY,
|
||||||
|
MODULO,
|
||||||
|
|
||||||
|
CONDITIONAL,
|
||||||
|
NULL_COALESCING,
|
||||||
|
|
||||||
|
ASSIGN,
|
||||||
|
ASSIGN_ADD,
|
||||||
|
ASSIGN_SUBTRACT,
|
||||||
|
ASSIGN_MULTIPLY,
|
||||||
|
ASSIGN_DIVIDE,
|
||||||
|
ASSIGN_MODULO,
|
||||||
|
ASSIGN_SHIFT_LEFT,
|
||||||
|
ASSIGN_SHIFT_RIGHT,
|
||||||
|
ASSIGN_XOR,
|
||||||
|
ASSIGN_AND,
|
||||||
|
ASSIGN_OR,
|
||||||
|
ASSIGN_DOUBLE_AND,
|
||||||
|
ASSIGN_DOUBLE_OR,
|
||||||
|
ASSIGN_NULL_COALESCING,
|
||||||
|
|
||||||
|
PTR_MEMBER,
|
||||||
|
DOT,
|
||||||
|
COMMA,
|
||||||
|
SEMICOLON,
|
||||||
|
COLON,
|
||||||
|
|
||||||
|
LAMBDA,
|
||||||
|
|
||||||
|
BRACKET_OPEN,
|
||||||
|
BRACKET_CLOSE,
|
||||||
|
BRACE_OPEN,
|
||||||
|
BRACE_CLOSE,
|
||||||
|
PAREN_OPEN,
|
||||||
|
PAREN_CLOSE,
|
||||||
|
|
||||||
|
VAL,
|
||||||
|
REF,
|
||||||
|
SIZEOF,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct token_t {
|
||||||
|
private:
|
||||||
|
enum kind_t {
|
||||||
|
NONE,
|
||||||
|
IDENTIFIER,
|
||||||
|
OPERATOR,
|
||||||
|
INT,
|
||||||
|
FLOAT,
|
||||||
|
CHAR,
|
||||||
|
STRING,
|
||||||
|
} kind;
|
||||||
|
union data_t {
|
||||||
|
std::string *identifier;
|
||||||
|
operator_t _operator;
|
||||||
|
std::uint64_t int_literal;
|
||||||
|
double float_literal;
|
||||||
|
char char_literal;
|
||||||
|
std::vector<char> *string_literal;
|
||||||
|
} data;
|
||||||
|
public:
|
||||||
|
ppc::location_t location;
|
||||||
|
|
||||||
|
bool is_identifier() { return kind == IDENTIFIER; }
|
||||||
|
bool is_operator() { return kind == OPERATOR; }
|
||||||
|
bool is_int_lit() { return kind == INT; }
|
||||||
|
bool is_float_lit() { return kind == FLOAT; }
|
||||||
|
bool is_char_lit() { return kind == CHAR; }
|
||||||
|
bool is_string_lit() { return kind == STRING; }
|
||||||
|
|
||||||
|
auto const &identifier() {
|
||||||
|
if (!is_identifier()) throw std::string { "Token is not an identifier." };
|
||||||
|
else return *data.identifier;
|
||||||
|
}
|
||||||
|
auto _operator() {
|
||||||
|
if (!is_operator()) throw std::string { "Token is not an operator." };
|
||||||
|
else return data._operator;
|
||||||
|
}
|
||||||
|
auto int_lit() {
|
||||||
|
if (!is_int_lit()) throw std::string { "Token is not an int literal." };
|
||||||
|
else return data.int_literal;
|
||||||
|
}
|
||||||
|
auto float_lit() {
|
||||||
|
if (!is_float_lit()) throw std::string { "Token is not a float literal." };
|
||||||
|
else return data.float_literal;
|
||||||
|
}
|
||||||
|
auto char_lit() {
|
||||||
|
if (!is_char_lit()) throw std::string { "Token is not a char literal." };
|
||||||
|
else return data.char_literal;
|
||||||
|
}
|
||||||
|
auto const &string_lit() {
|
||||||
|
if (!is_string_lit()) throw std::string { "Token is not a string literal." };
|
||||||
|
else return *data.string_literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_operator(operator_t op) { return is_operator() && _operator() == op; }
|
||||||
|
bool is_identifier(std::string &&val) { return is_identifier() && identifier() == val; }
|
||||||
|
|
||||||
|
token_t() { kind = NONE; }
|
||||||
|
token_t(std::string const &identifier, location_t loc = NO_LOCATION) {
|
||||||
|
kind = IDENTIFIER;
|
||||||
|
data.identifier = new std::string { identifier };
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(operator_t op, location_t loc = NO_LOCATION) {
|
||||||
|
kind = OPERATOR;
|
||||||
|
data._operator = op;
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(std::uint64_t val, location_t loc = NO_LOCATION) {
|
||||||
|
kind = INT;
|
||||||
|
data.int_literal = val;
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(double val, location_t loc = NO_LOCATION) {
|
||||||
|
kind = FLOAT;
|
||||||
|
data.float_literal = val;
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(char c, location_t loc = NO_LOCATION) {
|
||||||
|
kind = CHAR;
|
||||||
|
data.char_literal = c;
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(std::vector<char> const &val, location_t loc = NO_LOCATION) {
|
||||||
|
kind = STRING;
|
||||||
|
data.string_literal = new std::vector<char> { val };
|
||||||
|
location = loc;
|
||||||
|
}
|
||||||
|
token_t(token_t const &tok) {
|
||||||
|
kind = tok.kind;
|
||||||
|
switch (kind) {
|
||||||
|
case NONE: break;
|
||||||
|
case IDENTIFIER: data.identifier = new std::string { *tok.data.identifier }; break;
|
||||||
|
case OPERATOR: data._operator = tok.data._operator; break;
|
||||||
|
case INT: data.int_literal = tok.data.int_literal; break;
|
||||||
|
case FLOAT: data.float_literal = tok.data.float_literal; break;
|
||||||
|
case CHAR: data.char_literal = tok.data.char_literal; break;
|
||||||
|
case STRING: data.string_literal = new std::vector<char> { *tok.data.string_literal }; break;
|
||||||
|
}
|
||||||
|
location = tok.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
~token_t() {
|
||||||
|
switch (kind) {
|
||||||
|
case IDENTIFIER: delete data.identifier; break;
|
||||||
|
case STRING: delete data.string_literal; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static tok::token_t parse(messages::msg_stack_t &msg_stack, lex::token_t token);
|
||||||
|
static std::vector<token_t> parse_many(messages::msg_stack_t &msg_stack, std::vector<lex::token_t> tokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
operator_t operator_find(std::string const &text);
|
||||||
|
std::string const &operator_stringify(operator_t kw);
|
||||||
|
}
|
101
include/data.hh
Normal file
101
include/data.hh
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace ppc::data {
|
||||||
|
class map_t;
|
||||||
|
class array_t;
|
||||||
|
using number_t = float;
|
||||||
|
using string_t = std::string;
|
||||||
|
using bool_t = bool;
|
||||||
|
|
||||||
|
class value_t {
|
||||||
|
private:
|
||||||
|
// Variant and the C++ comittee can go fuck themselves
|
||||||
|
union val_t {
|
||||||
|
map_t *map;
|
||||||
|
array_t *arr;
|
||||||
|
number_t num;
|
||||||
|
string_t *str;
|
||||||
|
bool_t bl;
|
||||||
|
} val;
|
||||||
|
|
||||||
|
enum type_t {
|
||||||
|
Null,
|
||||||
|
Map,
|
||||||
|
Arr,
|
||||||
|
Num,
|
||||||
|
Str,
|
||||||
|
Bool,
|
||||||
|
} type;
|
||||||
|
public:
|
||||||
|
bool is_null() const;
|
||||||
|
bool is_map() const;
|
||||||
|
bool is_array() const;
|
||||||
|
bool is_number() const;
|
||||||
|
bool is_string() const;
|
||||||
|
bool is_bool() const;
|
||||||
|
|
||||||
|
bool array(array_t &out) const;
|
||||||
|
bool map(map_t &out) const;
|
||||||
|
bool number(number_t &out) const;
|
||||||
|
bool string(string_t &out) const;
|
||||||
|
bool boolean(bool_t &out) const;
|
||||||
|
|
||||||
|
array_t const &array() const;
|
||||||
|
map_t const &map() const;
|
||||||
|
number_t number() const;
|
||||||
|
string_t const &string() const;
|
||||||
|
bool_t boolean() const;
|
||||||
|
|
||||||
|
// value_t &operator=(value_t const &other);
|
||||||
|
|
||||||
|
~value_t();
|
||||||
|
value_t();
|
||||||
|
value_t(array_t const &val);
|
||||||
|
value_t(map_t const &val);
|
||||||
|
value_t(number_t val);
|
||||||
|
value_t(string_t const &val);
|
||||||
|
value_t(bool_t val);
|
||||||
|
value_t(value_t const &other);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class map_t {
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, value_t> values;
|
||||||
|
public:
|
||||||
|
value_t &operator [](std::string name) {
|
||||||
|
if (values.find(name) == values.end()) {
|
||||||
|
values.emplace(name, value_t { });
|
||||||
|
}
|
||||||
|
|
||||||
|
return values[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const { return values.size(); }
|
||||||
|
|
||||||
|
auto begin() const { return values.begin(); }
|
||||||
|
auto end() const { return values.end(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class array_t {
|
||||||
|
private:
|
||||||
|
std::vector<value_t> values;
|
||||||
|
public:
|
||||||
|
value_t &operator [](std::size_t i) { return values[i]; }
|
||||||
|
|
||||||
|
auto begin() { return values.begin(); }
|
||||||
|
auto end() { return values.end(); }
|
||||||
|
|
||||||
|
void push(value_t const &val) { values.push_back(val); }
|
||||||
|
void insert(value_t const &val, std::size_t i = 0) { values.insert(begin() + i, val); }
|
||||||
|
void pop() { values.pop_back(); }
|
||||||
|
void remove(std::size_t i = 0) { values.erase(begin() + i); }
|
||||||
|
|
||||||
|
std::size_t size() const { return values.size(); }
|
||||||
|
};
|
||||||
|
}
|
19
include/lang/common.hh
Normal file
19
include/lang/common.hh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/message.hh"
|
||||||
|
#include "utils/location.hh"
|
||||||
|
|
||||||
|
namespace ppc::lang {
|
||||||
|
struct namespace_name_t {
|
||||||
|
std::vector<std::string> segments;
|
||||||
|
ppc::location_t location;
|
||||||
|
|
||||||
|
bool operator ==(namespace_name_t const &other);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_identifier_valid(messages::msg_stack_t &msg_stack, ppc::location_t location, std::string const &name);
|
||||||
|
inline bool is_identifier_valid(std::string const &name) {
|
||||||
|
messages::msg_stack_t ms { };
|
||||||
|
return is_identifier_valid(ms, { }, name);
|
||||||
|
}
|
||||||
|
}
|
38
include/lang/namespace.hh
Normal file
38
include/lang/namespace.hh
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lang/common.hh"
|
||||||
|
#include "lang/types.hh"
|
||||||
|
#include "lang/version.hh"
|
||||||
|
|
||||||
|
namespace ppc::lang {
|
||||||
|
// A structure, containing all the definitions of a namespace
|
||||||
|
struct namespace_t {
|
||||||
|
// The name of the namespace
|
||||||
|
namespace_name_t name;
|
||||||
|
// The version of the namespace
|
||||||
|
version_t version;
|
||||||
|
// A list of all the defined types inside the namespace
|
||||||
|
std::vector<ppc::lang::type_def_t> types;
|
||||||
|
// A list of all the defined fields inside the namespace
|
||||||
|
std::vector<field_t> fields;
|
||||||
|
|
||||||
|
bool contains_def(std::string const &name, location_t &ploc) const;
|
||||||
|
inline bool contains_def(std::string const &name) const {
|
||||||
|
location_t ploc;
|
||||||
|
return contains_def(name, ploc);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static bool contains_def(T namespaces, namespace_name_t const &def_nmsp, std::string const &name, location_t &ploc) {
|
||||||
|
for (namespace_t const &nmsp : namespaces) {
|
||||||
|
if (nmsp.name == def_nmsp && nmsp.contains_def(name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
static inline bool contains_def(T namespaces, namespace_name_t const &def_nmsp, std::string const &name) {
|
||||||
|
location_t ploc;
|
||||||
|
return contains_def(namespaces, def_nmsp, name, ploc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
57
include/lang/operator.hh
Normal file
57
include/lang/operator.hh
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
enum operator_t {
|
||||||
|
OPERATOR_NONE,
|
||||||
|
OPERATOR_LESS_THAN,
|
||||||
|
OPERATOR_GREATER_THAN,
|
||||||
|
OPERATOR_LESS_THAN_EQUALS,
|
||||||
|
OPERATOR_GREATER_THAN_EQUALS,
|
||||||
|
OPERATOR_EQUALS,
|
||||||
|
OPERATOR_NOT_EQUALS,
|
||||||
|
OPERATOR_BOOLEAN_AND,
|
||||||
|
OPERATOR_BOOLEAN_OR,
|
||||||
|
|
||||||
|
OPERATOR_SHIFT_LEFT,
|
||||||
|
OPERATOR_SHIFT_RIGHT,
|
||||||
|
OPERATOR_BINARY_XOR,
|
||||||
|
OPERATOR_BINARY_AND,
|
||||||
|
OPERATOR_BINARY_OR,
|
||||||
|
OPERATOR_BOOLEAN_NOT,
|
||||||
|
OPERATOR_BITWISE_NEGATIVE,
|
||||||
|
|
||||||
|
OPERATOR_INCREASE,
|
||||||
|
OPERATOR_DECREASE,
|
||||||
|
|
||||||
|
OPERATOR_POST_INCREASE,
|
||||||
|
OPERATOR_POST_DECREASE,
|
||||||
|
|
||||||
|
OPERATOR_ADD,
|
||||||
|
OPERATOR_SUBTRACT,
|
||||||
|
OPERATOR_DIVIDE,
|
||||||
|
OPERATOR_MULTIPLY,
|
||||||
|
OPERATOR_MODULO,
|
||||||
|
|
||||||
|
OPERATOR_POSITIVE,
|
||||||
|
OPERATOR_NEGATIVE,
|
||||||
|
|
||||||
|
OPERATOR_CONDITIONAL,
|
||||||
|
OPERATOR_NULL_COALESCING,
|
||||||
|
|
||||||
|
OPERATOR_IMPLICIT,
|
||||||
|
OPERATOR_EXPLICIT,
|
||||||
|
|
||||||
|
OPERATOR_NEW,
|
||||||
|
OPERATOR_VAL,
|
||||||
|
|
||||||
|
OPERATOR_ASSIGN,
|
||||||
|
|
||||||
|
OPERATOR_PTR_MEMBER,
|
||||||
|
OPERATOR_MEMBER,
|
||||||
|
|
||||||
|
OPERATOR_REFERENCING,
|
||||||
|
OPERATOR_DEREFERENCING,
|
||||||
|
|
||||||
|
OPERATOR_CALL,
|
||||||
|
};
|
51
include/lang/types.hh
Normal file
51
include/lang/types.hh
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/location.hh"
|
||||||
|
#include "lang/common.hh"
|
||||||
|
#include "lang/version.hh"
|
||||||
|
|
||||||
|
namespace ppc::lang {
|
||||||
|
enum def_type_kind_t {
|
||||||
|
TYPE_NONE,
|
||||||
|
TYPE_STRUCT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct type_def_t;
|
||||||
|
|
||||||
|
// A structure, representing a ++C type notation
|
||||||
|
struct type_t {
|
||||||
|
// The type definition of which this type is
|
||||||
|
// If NULL, then this type is unknown
|
||||||
|
// Feel free to scream at the user if this is NULL
|
||||||
|
type_def_t *def;
|
||||||
|
};
|
||||||
|
// A structure, representing a field in a ++C struct
|
||||||
|
struct field_t {
|
||||||
|
// The type of which this field is
|
||||||
|
type_t type;
|
||||||
|
// The name of the field
|
||||||
|
const char *name;
|
||||||
|
// Whether or not the field is exported
|
||||||
|
bool is_exported;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A structure, representing a ++C type (structs, classes, delegates and interfaces)
|
||||||
|
struct type_def_t {
|
||||||
|
// Whether or not this definition is exported
|
||||||
|
bool is_exported;
|
||||||
|
// The namespace of the type
|
||||||
|
namespace_name_t _namespace;
|
||||||
|
// The name of the type
|
||||||
|
const char *name;
|
||||||
|
// The version of the type (inherited from the module version)
|
||||||
|
version_t version;
|
||||||
|
// The location of the definition
|
||||||
|
location_t location;
|
||||||
|
|
||||||
|
// The alignment padding from the start of the structures, in bytes
|
||||||
|
// Used either for efficiency sake or to store private fields (or both)
|
||||||
|
std::size_t align_size;
|
||||||
|
// A list of all the type's fields
|
||||||
|
std::vector<field_t> fields;
|
||||||
|
};
|
||||||
|
}
|
40
include/lang/version.hh
Normal file
40
include/lang/version.hh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ppc {
|
||||||
|
// A structure, representing all versions, used troughout the ++C language. 64 bits
|
||||||
|
struct version_t {
|
||||||
|
/* The major component of the version
|
||||||
|
Used to indicate breaking changes.
|
||||||
|
|
||||||
|
If -1 (65535), then is ignored
|
||||||
|
|
||||||
|
Must match target version in order to be usable */
|
||||||
|
std::uint16_t major;
|
||||||
|
/* The minor component of the version
|
||||||
|
Used to indicate non-breaking changes (added features).
|
||||||
|
|
||||||
|
If -1 (65535), then is ignored
|
||||||
|
|
||||||
|
Target version's minor version must be bigger or equals to this in order to be usable */
|
||||||
|
std::uint16_t minor;
|
||||||
|
/* The revision component of the version
|
||||||
|
Used to indicate minor fixes and changes that don't affect the API in any capacity
|
||||||
|
|
||||||
|
If -1 (4 294 967 295), then is ignored
|
||||||
|
|
||||||
|
Ignored when testing for compliancy */
|
||||||
|
std::uint32_t revision;
|
||||||
|
|
||||||
|
bool is_compliant(version_t dependency) const;
|
||||||
|
|
||||||
|
// Checks whether or not two versions equal each other (ignoring -1 components)
|
||||||
|
bool operator ==(version_t other) const;
|
||||||
|
inline bool operator !=(version_t other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
version_t(uint16_t major, uint16_t minor, uint32_t revision) : major { major }, minor { minor }, revision { revision } { }
|
||||||
|
version_t(uint16_t major, uint16_t minor) : version_t { major, minor, -1u } { }
|
||||||
|
version_t(uint16_t major) : version_t { major, -1u, -1u } { }
|
||||||
|
};
|
||||||
|
}
|
10
include/ppc.hh
Normal file
10
include/ppc.hh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef PPC_H
|
||||||
|
#define PPC_H 1
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
|
||||||
|
static const int VERSION_MAJOR = PPC_VERSION_MAJOR;
|
||||||
|
static const int VERSION_MINOR = PPC_VERSION_MINOR;
|
||||||
|
static const int VERSION_BUILD = PPC_VERSION_BUILD;
|
||||||
|
|
||||||
|
#endif
|
27
include/utils/location.hh
Normal file
27
include/utils/location.hh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ppc {
|
||||||
|
struct location_t {
|
||||||
|
std::size_t line;
|
||||||
|
std::size_t start;
|
||||||
|
std::size_t length;
|
||||||
|
std::size_t code_start;
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
location_t intersect(location_t other) const;
|
||||||
|
|
||||||
|
location_t();
|
||||||
|
location_t(std::string filename);
|
||||||
|
location_t(std::size_t line, std::size_t start);
|
||||||
|
location_t(std::string filename, std::size_t line, std::size_t start);
|
||||||
|
location_t(std::size_t line, std::size_t start, std::size_t code_start);
|
||||||
|
location_t(std::string filename, std::size_t line, std::size_t start, std::size_t code_start);
|
||||||
|
location_t(std::size_t line, std::size_t start, std::size_t code_start, std::size_t length);
|
||||||
|
location_t(std::string filename, std::size_t line, std::size_t start, std::size_t code_start, std::size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
static const location_t NO_LOCATION = { };
|
||||||
|
}
|
37
include/utils/message.hh
Normal file
37
include/utils/message.hh
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include "utils/location.hh"
|
||||||
|
|
||||||
|
namespace ppc::messages {
|
||||||
|
struct message_t {
|
||||||
|
enum level_t {
|
||||||
|
DEBUG,
|
||||||
|
SUGGESTION,
|
||||||
|
INFO,
|
||||||
|
WARNING,
|
||||||
|
ERROR,
|
||||||
|
} level;
|
||||||
|
location_t location;
|
||||||
|
std::string content;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
bool is_severe() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct msg_stack_t {
|
||||||
|
private:
|
||||||
|
std::vector<message_t> messages;
|
||||||
|
public:
|
||||||
|
inline auto begin() { return messages.begin(); }
|
||||||
|
inline auto end() { return messages.end(); }
|
||||||
|
|
||||||
|
void push(message_t const &msg) { messages.push_back(msg); }
|
||||||
|
void clear() { messages.clear(); }
|
||||||
|
|
||||||
|
bool is_failed() const;
|
||||||
|
void print(std::ostream &output, messages::message_t::level_t threshold = messages::message_t::DEBUG, bool color_output = false) const;
|
||||||
|
};
|
||||||
|
}
|
15
include/utils/strings.hh
Normal file
15
include/utils/strings.hh
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ppc::str {
|
||||||
|
std::vector<std::string> split(std::string const &splittable, std::initializer_list<char> splitters, bool remove_empty_entries = false);
|
||||||
|
std::string trim(std::string splittable, std::initializer_list<char> splitters = { ' ', '\n', '\t', '\r' });
|
||||||
|
inline bool begins_with(std::string const &str, std::string const &other) {
|
||||||
|
return !str.find(other);
|
||||||
|
}
|
||||||
|
inline bool ends_with(std::string const &str, std::string const &other) {
|
||||||
|
return str.find_last_of(other) == (str.length() - other.length());
|
||||||
|
}
|
||||||
|
}
|
39
include/utils/threading.hh
Normal file
39
include/utils/threading.hh
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef WINDOWS
|
||||||
|
#define THREAD __stdcall
|
||||||
|
#else
|
||||||
|
#define THREAD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ppc::threading {
|
||||||
|
template <class T>
|
||||||
|
using thread_func_t = int (THREAD *)(T *data);
|
||||||
|
using empty_thread_func_t = int (THREAD *)();
|
||||||
|
|
||||||
|
struct thread_t {
|
||||||
|
private:
|
||||||
|
void *handle;
|
||||||
|
|
||||||
|
static thread_t start_impl(void *func, void *args);
|
||||||
|
public:
|
||||||
|
int join() const;
|
||||||
|
|
||||||
|
thread_t(void *handle) { this->handle = handle; }
|
||||||
|
template <class T>
|
||||||
|
inline static thread_t start(thread_func_t<T> func, T const &args) {
|
||||||
|
T _args = args;
|
||||||
|
return start_impl((void*)func, &_args);
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
inline static thread_t start(thread_func_t<T> func, T &args) {
|
||||||
|
return start_impl((void*)func, &args);
|
||||||
|
}
|
||||||
|
inline static thread_t start(empty_thread_func_t func) {
|
||||||
|
return start<int>((thread_func_t<int>)func, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~thread_t();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
56
scripts/common.mak
Normal file
56
scripts/common.mak
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
$(shell $(call mkdir,$(bin)))
|
||||||
|
$(shell $(CXX) $(src)/lsproj.cc -o $(bin)/lsproj$(exe))
|
||||||
|
|
||||||
|
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
|
||||||
|
|
||||||
|
uniq=$(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
|
||||||
|
modoutput=$(shell ./$(bin)/lsproj$(exe) $(src) $1 output)
|
||||||
|
deps=$(strip \
|
||||||
|
$(foreach dep, $(shell ./$(bin)/lsproj$(exe) $(src) $1 deps),\
|
||||||
|
$(if $(wildcard src/$(dep)),\
|
||||||
|
$(dep),\
|
||||||
|
$(error The module '$(dep)' (dependency of '$1') doesn't exist)\
|
||||||
|
)\
|
||||||
|
)\
|
||||||
|
)
|
||||||
|
rdeps=$(call uniq,$(strip \
|
||||||
|
$(foreach dep, $(call deps,$1),\
|
||||||
|
$(call rdeps,$(dep))\
|
||||||
|
$(dep)\
|
||||||
|
)\
|
||||||
|
))
|
||||||
|
|
||||||
|
fdeps=$(foreach dep,$(call deps,$1),$(bin)/lib$(lib)$(call modoutput,$(dep))$(so))
|
||||||
|
frdeps=$(foreach dep,$(call rdeps,$1),$(bin)/lib$(lib)$(call modoutput,$(dep))$(so))
|
||||||
|
|
||||||
|
ldeps=$(foreach dep,$(call deps,$1),-l$(lib)$(call modoutput,$(dep)))
|
||||||
|
lrdeps=$(foreach dep,$(call rdeps,$1),-l$(lib)$(call modoutput,$(dep)))
|
||||||
|
|
||||||
|
modules = $(patsubst $(src)/%/,$(bin)/lib$(lib)%$(so),$(filter-out $(src)/$(mainmodule)/,$(wildcard $(src)/*/)))
|
||||||
|
sources = $(call rwildcard,$(src)/$1,*.cc)
|
||||||
|
headers = $(call rwildcard,$(inc),*.h)
|
||||||
|
binaries = $(patsubst $(src)/%.cc,$(bin)/%.o,$(call sources,$1))
|
||||||
|
|
||||||
|
flags += "-I$(inc)" -D$(OS) -DPPC_VERSION_MAJOR=$(version-major) -DPPC_VERSION_MINOR=$(version-minor) -DPPC_VERSION_BUILD=$(version-build)
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
build: $(binary)
|
||||||
|
|
||||||
|
.SECONDEXPANSION:
|
||||||
|
$(binary): $$(call frdeps,$(mainmodule)) $$(call sources,$$*)
|
||||||
|
$(call mkdir,$(dir $@))
|
||||||
|
echo Compiling executable '$(notdir $(binary))'...
|
||||||
|
$(CXX) $(flags) $(call sources,$(mainmodule)) -o $@ $(ldflags) $(call ldeps,$(mainmodule)) -L$(bin) "-I$(inc)"
|
||||||
|
$(call rmdir,$(bin)/lsproj$(exe))
|
||||||
|
|
||||||
|
.SECONDEXPANSION:
|
||||||
|
$(bin)/lib$(lib)%$(so): $$(call sources,$$*) $(headers)
|
||||||
|
$(call mkdir,$(bin))
|
||||||
|
echo Compiling library '$(notdir $@)'...
|
||||||
|
$(CXX) -shared -fPIC $(flags) $(call sources,$*) -o $@ $(ldflags) $(call ldeps,$*) -L$(bin) "-I$(inc)"
|
||||||
|
|
||||||
|
# $(bin)/%.o: $(src)/%.cc $(headers)
|
||||||
|
# echo - Compiling '$*.cc'...
|
||||||
|
# $(call mkdir,$(dir $@))
|
||||||
|
# $(CXX) -fPIC -c $(flags) $< -o $@
|
16
scripts/install.bat
Normal file
16
scripts/install.bat
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
set location=C:\bin
|
||||||
|
@set /p location=Location of install (%location%):
|
||||||
|
|
||||||
|
mkdir "%location%" 2> NUL
|
||||||
|
|
||||||
|
echo Installing libraries...
|
||||||
|
xcopy /Q /Y "bin\*.dll" "%location%" > NUL
|
||||||
|
|
||||||
|
echo Installing ++C...
|
||||||
|
echo F | xcopy /Q /Y "%bin%\%output%-windows.exe" "%location%\++c.exe" > NUL
|
||||||
|
|
||||||
|
choice /N /M "Add location to PATH? (y/n) "
|
||||||
|
|
||||||
|
if %errorlevel% equ 1 powershell -Command "[Environment]::SetEnvironmentVariable('path',\"%location%;$([Environment]::GetEnvironmentVariable('path','Machine'))\",'Machine');"
|
20
scripts/uninstall.bat
Normal file
20
scripts/uninstall.bat
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
set location=C:\bin
|
||||||
|
@set /p location=Location of install (%location%):
|
||||||
|
|
||||||
|
mkdir "%location%" 2> NUL
|
||||||
|
|
||||||
|
choice /N /M "Remove everything (additional data included)? (y/n) "
|
||||||
|
|
||||||
|
if %errorlevel% equ 1 echo Deleting data...
|
||||||
|
echo Uninstalling libraries...
|
||||||
|
del "%location%\libppc-*.dll" 2> NUL
|
||||||
|
echo Uninstalling ++C...
|
||||||
|
del "%location%\++c.exe" 2> NUL
|
||||||
|
@REM else set everything=no
|
||||||
|
|
||||||
|
|
||||||
|
@REM choice /N /M "Add location to PATH? (y/n) "
|
||||||
|
|
||||||
|
@REM if %errorlevel% equ 1 setx PATH "%PATH%;%location%"
|
2
src/compiler/proj.txt
Normal file
2
src/compiler/proj.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
compiler
|
||||||
|
utils
|
402
src/compiler/treeifier/lexer.cc
Normal file
402
src/compiler/treeifier/lexer.cc
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
#include <sstream>
|
||||||
|
#include "compiler/treeifier/lexer.hh"
|
||||||
|
#include "utils/message.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
using namespace comp::tree::lex;
|
||||||
|
|
||||||
|
struct lexlet_t {
|
||||||
|
bool(*is_valid)(char curr);
|
||||||
|
struct process_res_t {
|
||||||
|
bool ended;
|
||||||
|
bool repeat;
|
||||||
|
bool dont_add;
|
||||||
|
const lexlet_t *new_parselet;
|
||||||
|
bool has_message;
|
||||||
|
messages::message_t msg;
|
||||||
|
};
|
||||||
|
process_res_t (*process)(char curr);
|
||||||
|
token_t::kind_t type;
|
||||||
|
};
|
||||||
|
|
||||||
|
using process_res_t = lexlet_t::process_res_t;
|
||||||
|
|
||||||
|
extern const lexlet_t LEXLET_DEFAULT;
|
||||||
|
extern const lexlet_t LEXLET_IDENTIFIER;
|
||||||
|
extern const lexlet_t LEXLET_OPERATOR;
|
||||||
|
extern const lexlet_t LEXLET_ZERO;
|
||||||
|
extern const lexlet_t LEXLET_FLOAT;
|
||||||
|
extern const lexlet_t LEXLET_BIN;
|
||||||
|
extern const lexlet_t LEXLET_OCT;
|
||||||
|
extern const lexlet_t LEXLET_DEC;
|
||||||
|
extern const lexlet_t LEXLET_HEX;
|
||||||
|
extern const lexlet_t LEXLET_STRING_LITERAL;
|
||||||
|
extern const lexlet_t LEXLET_CHAR_LITERAL;
|
||||||
|
extern const lexlet_t LEXLET_COMMENT;
|
||||||
|
extern const lexlet_t LEXLET_MULTICOMMENT;
|
||||||
|
|
||||||
|
static bool is_digit(char c) {
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
static bool is_oct(char c) {
|
||||||
|
return c >= '0' && c <= '7';
|
||||||
|
}
|
||||||
|
static bool is_hex(char c) {
|
||||||
|
return is_digit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' || c <= 'f');
|
||||||
|
}
|
||||||
|
static bool is_lower(char c) {
|
||||||
|
return c >= 'a' && c <= 'z';
|
||||||
|
}
|
||||||
|
static bool is_upper(char c) {
|
||||||
|
return c >= 'A' && c <= 'Z';
|
||||||
|
}
|
||||||
|
static bool is_letter(char c) {
|
||||||
|
return is_lower(c) || is_upper(c);
|
||||||
|
}
|
||||||
|
static bool is_alphanumeric(char c) {
|
||||||
|
return is_letter(c) || is_digit(c);
|
||||||
|
}
|
||||||
|
static bool is_any(char c, std::string chars) {
|
||||||
|
return chars.find(c) != -1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static process_res_t lexer_switch(const lexlet_t *lexlet) {
|
||||||
|
return {
|
||||||
|
.ended = false,
|
||||||
|
.repeat = false,
|
||||||
|
.new_parselet = lexlet,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static process_res_t lexer_repeat_switch(const lexlet_t *lexlet) {
|
||||||
|
return (process_res_t) {
|
||||||
|
.ended = false,
|
||||||
|
.repeat = true,
|
||||||
|
.new_parselet = lexlet,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static process_res_t lexer_end() {
|
||||||
|
return (process_res_t) {
|
||||||
|
.ended = true,
|
||||||
|
.repeat = true,
|
||||||
|
.new_parselet = nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static process_res_t lexer_none() {
|
||||||
|
return (process_res_t) {
|
||||||
|
.ended = false,
|
||||||
|
.repeat = false,
|
||||||
|
.new_parselet = nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static process_res_t default_process(char curr) {
|
||||||
|
if (LEXLET_STRING_LITERAL.is_valid(curr)) return lexer_switch(&LEXLET_STRING_LITERAL);
|
||||||
|
if (LEXLET_CHAR_LITERAL.is_valid(curr)) return lexer_switch(&LEXLET_CHAR_LITERAL);
|
||||||
|
if (LEXLET_OPERATOR.is_valid(curr)) return lexer_switch(&LEXLET_OPERATOR);
|
||||||
|
if (LEXLET_ZERO.is_valid(curr)) return lexer_switch(&LEXLET_ZERO);
|
||||||
|
if (LEXLET_DEC.is_valid(curr)) return lexer_switch(&LEXLET_DEC);
|
||||||
|
if (LEXLET_FLOAT.is_valid(curr)) return lexer_switch(&LEXLET_FLOAT);
|
||||||
|
if (LEXLET_IDENTIFIER.is_valid(curr)) return lexer_switch(&LEXLET_IDENTIFIER);
|
||||||
|
else return (process_res_t) {
|
||||||
|
.ended = true,
|
||||||
|
.repeat = false,
|
||||||
|
.new_parselet = nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool identifier_is_valid(char curr) {
|
||||||
|
return is_letter(curr) || curr == '_' || curr == '@' || curr == '$';
|
||||||
|
}
|
||||||
|
static process_res_t identifier_process(char curr) {
|
||||||
|
bool valid = (is_alphanumeric(curr) || curr == '_' || curr == '@' || curr == '$');
|
||||||
|
return (process_res_t) {
|
||||||
|
.ended = !valid,
|
||||||
|
.repeat = !valid,
|
||||||
|
.new_parselet = &LEXLET_IDENTIFIER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool last_escape = false;
|
||||||
|
static bool literal_ended = false;
|
||||||
|
|
||||||
|
static bool string_is_valid(char curr) {
|
||||||
|
last_escape = false;
|
||||||
|
literal_ended = false;
|
||||||
|
return curr == '"';
|
||||||
|
}
|
||||||
|
static process_res_t string_process(char curr) {
|
||||||
|
if (last_escape) {
|
||||||
|
last_escape = false;
|
||||||
|
return lexer_none();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr == '\\') {
|
||||||
|
last_escape = true;
|
||||||
|
}
|
||||||
|
else if (curr == '"') {
|
||||||
|
literal_ended = true;
|
||||||
|
}
|
||||||
|
else if (literal_ended) return lexer_end();
|
||||||
|
return lexer_none();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool char_is_valid(char curr) {
|
||||||
|
last_escape = false;
|
||||||
|
literal_ended = false;
|
||||||
|
return curr == '\'';
|
||||||
|
}
|
||||||
|
static process_res_t char_process(char curr) {
|
||||||
|
if (last_escape) {
|
||||||
|
last_escape = false;
|
||||||
|
return lexer_none();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr == '\\') {
|
||||||
|
last_escape = true;
|
||||||
|
}
|
||||||
|
else if (curr == '\'') {
|
||||||
|
literal_ended = true;
|
||||||
|
}
|
||||||
|
else if (literal_ended) return lexer_end();
|
||||||
|
return lexer_none();
|
||||||
|
}
|
||||||
|
|
||||||
|
static char first_op;
|
||||||
|
static int op_i = 0;
|
||||||
|
|
||||||
|
static bool operator_is_valid(char curr) {
|
||||||
|
if (is_any(curr, "=!<>+-*/%&|^?:,.(){}[];")) {
|
||||||
|
first_op = curr;
|
||||||
|
op_i = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
static process_res_t operator_process(char curr) {
|
||||||
|
bool failed = true;
|
||||||
|
if (first_op == curr && op_i == 1 && is_any(curr, "+-&|?<>")) failed = false;
|
||||||
|
if (curr == '=') {
|
||||||
|
if (op_i == 1 && is_any(first_op, "<>=!+-/*%")) failed = false;
|
||||||
|
if (op_i == 2 && is_any(first_op, "<>?")) failed = false;
|
||||||
|
}
|
||||||
|
if (first_op == '-' && curr == '>' && op_i == 1) failed = false;
|
||||||
|
|
||||||
|
if (first_op == '/' && op_i == 1) {
|
||||||
|
if (curr == '/') return lexer_switch(&LEXLET_COMMENT);
|
||||||
|
else if (curr == '*') return lexer_switch(&LEXLET_MULTICOMMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
op_i++;
|
||||||
|
|
||||||
|
if (failed) return lexer_end();
|
||||||
|
else return lexer_none();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool zero_is_valid(char curr) {
|
||||||
|
return curr == '0';
|
||||||
|
}
|
||||||
|
static process_res_t zero_process(char curr) {
|
||||||
|
if (curr == '.') return lexer_switch(&LEXLET_FLOAT);
|
||||||
|
else if (curr == 'b') return lexer_switch(&LEXLET_BIN);
|
||||||
|
else if (curr == 'x') return lexer_switch(&LEXLET_HEX);
|
||||||
|
else if (is_digit(curr)) return lexer_repeat_switch(&LEXLET_OCT);
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dec_is_valid(char curr) {
|
||||||
|
return is_digit(curr);
|
||||||
|
}
|
||||||
|
static process_res_t dec_process(char curr) {
|
||||||
|
if (is_digit(curr)) return lexer_none();
|
||||||
|
else if (curr == '.') return lexer_switch(&LEXLET_FLOAT);
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool only_dot = false;
|
||||||
|
|
||||||
|
static bool float_is_valid(char curr) {
|
||||||
|
return only_dot = curr == '.';
|
||||||
|
}
|
||||||
|
static process_res_t float_process(char curr) {
|
||||||
|
if (is_digit(curr)) {
|
||||||
|
only_dot = false;
|
||||||
|
return lexer_none();
|
||||||
|
}
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static process_res_t hex_process(char curr) {
|
||||||
|
if (is_hex(curr)) return lexer_none();
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
static process_res_t bin_process(char curr) {
|
||||||
|
if (curr == '0' || curr == '1') return lexer_none();
|
||||||
|
else if (is_digit(curr))
|
||||||
|
throw messages::message_t { messages::message_t::ERROR, NO_LOCATION, "A binary literal may only contain zeroes and ones." };
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
static process_res_t oct_process(char curr) {
|
||||||
|
if (is_oct(curr)) return lexer_none();
|
||||||
|
else if (is_digit(curr))
|
||||||
|
throw messages::message_t { messages::message_t::ERROR, NO_LOCATION, "An octal literal may only contain octal digits." };
|
||||||
|
else return lexer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
static process_res_t comment_process(char curr) {
|
||||||
|
if (curr == '\n') return lexer_end();
|
||||||
|
else return (process_res_t) {
|
||||||
|
.ended = false,
|
||||||
|
.dont_add = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool last_star = false;
|
||||||
|
|
||||||
|
static process_res_t multicomment_process(char curr) {
|
||||||
|
if (curr == '/' && last_star) {
|
||||||
|
last_star = false;
|
||||||
|
return {
|
||||||
|
.ended = true,
|
||||||
|
.repeat = false,
|
||||||
|
.new_parselet = nullptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (curr == '*') last_star = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
.ended = false,
|
||||||
|
.dont_add = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const lexlet_t LEXLET_DEFAULT = (lexlet_t) {
|
||||||
|
.process = default_process,
|
||||||
|
.type = token_t::NONE,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_IDENTIFIER = (lexlet_t) {
|
||||||
|
.is_valid = identifier_is_valid,
|
||||||
|
.process = identifier_process,
|
||||||
|
.type = token_t::IDENTIFIER,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_ZERO = (lexlet_t) {
|
||||||
|
.is_valid = zero_is_valid,
|
||||||
|
.process = zero_process,
|
||||||
|
.type = token_t::DEC_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_DEC = (lexlet_t) {
|
||||||
|
.is_valid = dec_is_valid,
|
||||||
|
.process = dec_process,
|
||||||
|
.type = token_t::DEC_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_HEX = (lexlet_t) {
|
||||||
|
.process = hex_process,
|
||||||
|
.type = token_t::HEX_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_BIN = (lexlet_t) {
|
||||||
|
.process = bin_process,
|
||||||
|
.type = token_t::BIN_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_OCT = (lexlet_t) {
|
||||||
|
.process = oct_process,
|
||||||
|
.type = token_t::OCT_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_FLOAT = (lexlet_t) {
|
||||||
|
.is_valid = float_is_valid,
|
||||||
|
.process = float_process,
|
||||||
|
.type = token_t::FLOAT_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_OPERATOR = (lexlet_t) {
|
||||||
|
.is_valid = operator_is_valid,
|
||||||
|
.process = operator_process,
|
||||||
|
.type = token_t::OPERATOR,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_STRING_LITERAL = (lexlet_t) {
|
||||||
|
.is_valid = string_is_valid,
|
||||||
|
.process = string_process,
|
||||||
|
.type = token_t::STRING_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_CHAR_LITERAL = (lexlet_t) {
|
||||||
|
.is_valid = char_is_valid,
|
||||||
|
.process = char_process,
|
||||||
|
.type = token_t::CHAR_LITERAL,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_COMMENT = {
|
||||||
|
.is_valid = nullptr,
|
||||||
|
.process = comment_process,
|
||||||
|
.type = token_t::NONE,
|
||||||
|
};
|
||||||
|
const lexlet_t LEXLET_MULTICOMMENT = {
|
||||||
|
.is_valid = nullptr,
|
||||||
|
.process = multicomment_process,
|
||||||
|
.type = token_t::NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<token_t> token_t::parse_many(ppc::messages::msg_stack_t &msg_stack, std::string const &filename, std::string const &src) {
|
||||||
|
std::vector<token_t> tokens;
|
||||||
|
std::vector<char> curr_token;
|
||||||
|
lexlet_t curr = LEXLET_DEFAULT;
|
||||||
|
std::size_t start = 0, line = 0, curr_start = 0, curr_line = 0, length = 0, i = 0;
|
||||||
|
|
||||||
|
while (src[i]) {
|
||||||
|
char c = src[i];
|
||||||
|
try {
|
||||||
|
process_res_t res = curr.process(c);
|
||||||
|
if (i == 0) res.repeat = false;
|
||||||
|
if (res.has_message) throw res.msg;
|
||||||
|
|
||||||
|
if (res.ended) {
|
||||||
|
if (curr.type) {
|
||||||
|
location_t loc = { filename, line, start, i - length, length };
|
||||||
|
tokens.push_back({ curr.type, { curr_token.begin(), curr_token.end() }, loc });
|
||||||
|
curr_token.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
curr = LEXLET_DEFAULT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (res.new_parselet) {
|
||||||
|
if (!curr.type) {
|
||||||
|
start = curr_start;
|
||||||
|
line = curr_line;
|
||||||
|
}
|
||||||
|
curr = *res.new_parselet;
|
||||||
|
}
|
||||||
|
if (!res.dont_add) {
|
||||||
|
curr_token.push_back(c);
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.repeat) {
|
||||||
|
curr_start++;
|
||||||
|
if (c == '\n') {
|
||||||
|
curr_line++;
|
||||||
|
curr_start = 0;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (messages::message_t const &msg) {
|
||||||
|
throw messages::message_t { msg.level, { filename, line, start, i - length, length }, msg.content };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
location_t loc = { filename, line, start, i - length, length };
|
||||||
|
if (curr.type) {
|
||||||
|
tokens.push_back(token_t {
|
||||||
|
curr.type, std::string { curr_token.begin(), curr_token.end() },
|
||||||
|
{ filename, line, start, i - length, length }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
std::vector<token_t> token_t::parse_file(ppc::messages::msg_stack_t &msg_stack, std::string const &filename, std::istream &f) {
|
||||||
|
std::vector<char> contents;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = f.get()) != EOF) contents.push_back(c);
|
||||||
|
|
||||||
|
return parse_many(msg_stack, filename, std::string { contents.begin(), contents.end() });
|
||||||
|
}
|
32
src/compiler/treeifier/operators.cc
Normal file
32
src/compiler/treeifier/operators.cc
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include <string>
|
||||||
|
#include "compiler/treeifier/tokenizer.hh"
|
||||||
|
|
||||||
|
using namespace ppc::comp::tree;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> operators = {
|
||||||
|
"<", ">", "<=", ">=", "==", "!=", "&&", "||",
|
||||||
|
"<<", ">>", "^", "&", "|", "!", "~",
|
||||||
|
"++", "--",
|
||||||
|
"+", "-", "/", "*", "%",
|
||||||
|
"?", "??",
|
||||||
|
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", "^=", "&=", "|=", "&&=", "||=", "??=",
|
||||||
|
"->", ".", ",", ";", ":",
|
||||||
|
"=>",
|
||||||
|
"[", "]", "{", "}", "(", ")"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::string const &tok::operator_stringify(tok::operator_t kw) {
|
||||||
|
if (kw < 0 || kw >= operators.size()) throw "Invalid operator ID given."s;
|
||||||
|
return operators[kw];
|
||||||
|
}
|
||||||
|
tok::operator_t tok::operator_find(const std::string &raw) {
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (auto const &op : operators) {
|
||||||
|
if (op == raw) return (tok::operator_t)i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
throw "Invalid operator '"s + raw + "' given.";
|
||||||
|
}
|
187
src/compiler/treeifier/tokenizer.cc
Normal file
187
src/compiler/treeifier/tokenizer.cc
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#include <string>
|
||||||
|
#include "compiler/treeifier/tokenizer.hh"
|
||||||
|
#include "compiler/treeifier/lexer.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
using namespace messages;
|
||||||
|
using namespace comp::tree;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
static std::vector<char> parse_string(msg_stack_t &msg_stack, bool is_char, lex::token_t token) {
|
||||||
|
char literal_char = is_char ? '\'' : '"';
|
||||||
|
|
||||||
|
bool escaping = false;
|
||||||
|
|
||||||
|
std::vector<char> res { };
|
||||||
|
location_t curr_char_loc = token.location;
|
||||||
|
curr_char_loc.length = 1;
|
||||||
|
curr_char_loc.start++;
|
||||||
|
|
||||||
|
char c;
|
||||||
|
|
||||||
|
for (std::size_t i = 1; (c = token.data[i]); i++) {
|
||||||
|
if (escaping) {
|
||||||
|
char new_c = c;
|
||||||
|
|
||||||
|
if (c == 'b') new_c = '\b';
|
||||||
|
else if (c == 'a') new_c = '\a';
|
||||||
|
else if (c == 'e') new_c = '\e';
|
||||||
|
else if (c == 'f') new_c = '\f';
|
||||||
|
else if (c == 'n') new_c = '\n';
|
||||||
|
else if (c == 'r') new_c = '\r';
|
||||||
|
else if (c == 't') new_c = '\t';
|
||||||
|
else if (c == 'v') new_c = '\v';
|
||||||
|
// TODO: Add support for oct, hex and utf8 literals
|
||||||
|
else if (c == literal_char || c == '\\') new_c = c;
|
||||||
|
else {
|
||||||
|
throw message_t { message_t::ERROR, curr_char_loc, "Unescapable character." };
|
||||||
|
}
|
||||||
|
res.push_back(new_c);
|
||||||
|
escaping = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c == '\\') escaping = true;
|
||||||
|
else if (c == literal_char) return res;
|
||||||
|
else res.push_back(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
curr_char_loc.start++;
|
||||||
|
if (c == '\n') break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_char) throw message_t { message_t::ERROR, token.location, "Unterminated char literal." };
|
||||||
|
else throw message_t { message_t::ERROR, token.location, "Unterminated string literal." };
|
||||||
|
}
|
||||||
|
static tok::token_t parse_int(msg_stack_t &msg_stack, lex::token_t token) {
|
||||||
|
enum radix_t {
|
||||||
|
BINARY,
|
||||||
|
OCTAL,
|
||||||
|
DECIMAL,
|
||||||
|
HEXADECIMAL,
|
||||||
|
} radix;
|
||||||
|
|
||||||
|
std::size_t i = 0;
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case lex::token_t::BIN_LITERAL:
|
||||||
|
i += 2;
|
||||||
|
radix = BINARY;
|
||||||
|
break;
|
||||||
|
case lex::token_t::OCT_LITERAL:
|
||||||
|
i++;
|
||||||
|
radix = OCTAL;
|
||||||
|
break;
|
||||||
|
case lex::token_t::DEC_LITERAL:
|
||||||
|
radix = DECIMAL;
|
||||||
|
break;
|
||||||
|
case lex::token_t::HEX_LITERAL:
|
||||||
|
i += 2;
|
||||||
|
radix = HEXADECIMAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "WTF r u doing bro?"s;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t j = token.data.length() - 1;
|
||||||
|
|
||||||
|
uint64_t res = 0;
|
||||||
|
|
||||||
|
for (; i <= j; i++) {
|
||||||
|
char c = token.data[i];
|
||||||
|
int8_t digit;
|
||||||
|
switch (radix) {
|
||||||
|
case BINARY:
|
||||||
|
digit = c - '0';
|
||||||
|
res <<= 1;
|
||||||
|
res |= digit;
|
||||||
|
break;
|
||||||
|
case OCTAL:
|
||||||
|
digit = c - '0';
|
||||||
|
if (digit < 0 || digit > 7) {
|
||||||
|
throw message_t { message_t::ERROR, token.location, "Octal literals may contain numbers between 0 and 7." };
|
||||||
|
}
|
||||||
|
res <<= 3;
|
||||||
|
res |= digit;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
digit = c - '0';
|
||||||
|
res *= 10;
|
||||||
|
res += digit;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (c >= 'a' && c <= 'f') digit = c - 'a' + 9;
|
||||||
|
else if (c >= 'A' && c <= 'F') digit = c - 'A' + 9;
|
||||||
|
else if (c >= '0' && c <= '9') digit = c - '0';
|
||||||
|
else throw message_t { message_t::ERROR, token.location, "Invalid character '"s + c + "' in hex literal." };
|
||||||
|
res <<= 4;
|
||||||
|
res |= digit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok::token_t { res, token.location };
|
||||||
|
}
|
||||||
|
static tok::token_t parse_float(msg_stack_t &msg_stack, lex::token_t token) {
|
||||||
|
double whole = 0, fract = 0;
|
||||||
|
|
||||||
|
char c;
|
||||||
|
std::size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < token.data.length() && ((c = token.data[i]) > '0' && c < '9'); i++) {
|
||||||
|
if (c == '.') break;
|
||||||
|
int digit = c - '0';
|
||||||
|
whole *= 10;
|
||||||
|
whole += digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '.') {
|
||||||
|
i++;
|
||||||
|
for (; i < token.data.length() && ((c = token.data[i]) > '0' && c < '9'); i++) {
|
||||||
|
int digit = c - '0';
|
||||||
|
fract += digit;
|
||||||
|
fract /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok::token_t { whole + fract, token.location };
|
||||||
|
}
|
||||||
|
|
||||||
|
tok::token_t tok::token_t::parse(messages::msg_stack_t &msg_stack, lex::token_t in) {
|
||||||
|
switch (in.type) {
|
||||||
|
case lex::token_t::IDENTIFIER:
|
||||||
|
return tok::token_t { in.data, in.location };
|
||||||
|
case lex::token_t::OPERATOR:
|
||||||
|
try {
|
||||||
|
auto op = tok::operator_find(in.data);
|
||||||
|
return token_t { op, in.location };
|
||||||
|
}
|
||||||
|
catch (std::string &err) {
|
||||||
|
throw message_t { message_t::ERROR, in.location, "Operator not recognised."s };
|
||||||
|
}
|
||||||
|
case lex::token_t::BIN_LITERAL:
|
||||||
|
case lex::token_t::OCT_LITERAL:
|
||||||
|
case lex::token_t::DEC_LITERAL:
|
||||||
|
case lex::token_t::HEX_LITERAL:
|
||||||
|
return parse_int(msg_stack, in);
|
||||||
|
case lex::token_t::FLOAT_LITERAL:
|
||||||
|
return parse_float(msg_stack, in);
|
||||||
|
case lex::token_t::STRING_LITERAL:
|
||||||
|
return { parse_string(msg_stack, false, in) };
|
||||||
|
case lex::token_t::CHAR_LITERAL: {
|
||||||
|
auto str = parse_string(msg_stack, true, in);
|
||||||
|
if (str.size() != 1) throw message_t { message_t::ERROR, in.location, "Char literal must consist of just one character." };
|
||||||
|
return str.front();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw message_t { message_t::ERROR, in.location, "Token type not recognised." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<tok::token_t> tok::token_t::parse_many(messages::msg_stack_t &msg_stack, std::vector<lex::token_t> tokens) {
|
||||||
|
std::vector<tok::token_t> res;
|
||||||
|
|
||||||
|
for (auto &tok : tokens) {
|
||||||
|
res.push_back(tok::token_t::parse(msg_stack, tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
79
src/compiler/treeifier/treeifier.c
Normal file
79
src/compiler/treeifier/treeifier.c
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "compiler/treeifier.h"
|
||||||
|
#include "compiler/treeifier/lexer.h"
|
||||||
|
#include "compiler/treeifier/tokenizer.h"
|
||||||
|
#include "compiler/treeifier/ast.h"
|
||||||
|
|
||||||
|
inline static const char* read_file(const char *path) {
|
||||||
|
FILE *file = fopen(path, "rb");
|
||||||
|
|
||||||
|
if (!file) return NULL;
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
int len = ftell(file);
|
||||||
|
char *data = malloc(char, len + 1);
|
||||||
|
data[len] = 0;
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||||
|
fread(data, 1, len, file);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ppc_treeify(msg_stack_t *msg_stack, const char *filename, const char *source, ast_namespace_t *pout) {
|
||||||
|
lex_tokens_t lex_tokens = { 0 };
|
||||||
|
tokens_t tokens = { 0 };
|
||||||
|
ast_namespace_t ast = { 0 };
|
||||||
|
if (!ppc_lex(msg_stack, filename, source, &lex_tokens)) goto failed;
|
||||||
|
if (!ppc_tokeinze(msg_stack, lex_tokens, &tokens)) goto failed;
|
||||||
|
ppc_lex_free(lex_tokens);
|
||||||
|
if (!ppc_ast(msg_stack, tokens, &ast)) goto failed;
|
||||||
|
ppc_tokeinze_free(tokens);
|
||||||
|
|
||||||
|
if (pout) *pout = ast;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
ppc_lex_free(lex_tokens);
|
||||||
|
ppc_tokeinze_free(tokens);
|
||||||
|
ast_namespace_free(ast);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
bool ppc_treeify_file(const char *filename, msg_stack_t *pmsg_stack, ast_namespace_t *pout) {
|
||||||
|
const char *source = read_file(filename);
|
||||||
|
msg_stack_t msg_stack = { 0 };
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
if (pmsg_stack) {
|
||||||
|
const char errmsg[] = "The file '%s' was not found.";
|
||||||
|
char *errmsgMem = malloc(char, strlen(filename) + sizeof(errmsg) - 2);
|
||||||
|
sprintf(errmsgMem, errmsg, filename);
|
||||||
|
free(filename);
|
||||||
|
|
||||||
|
list_add(msg_stack, ((message_t) {
|
||||||
|
.content = errmsgMem,
|
||||||
|
.level = MSGLEVEL_ERROR,
|
||||||
|
.location = loc_from_file(filename),
|
||||||
|
.malloced = true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
*pmsg_stack = msg_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_namespace_t ast = { 0 };
|
||||||
|
if (!ppc_treeify(&msg_stack, filename, source, &ast)) goto failed;
|
||||||
|
|
||||||
|
free(source);
|
||||||
|
if (pout) *pout = ast;
|
||||||
|
if (pmsg_stack) *pmsg_stack = msg_stack;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
free(source);
|
||||||
|
if (pmsg_stack) *pmsg_stack = msg_stack;
|
||||||
|
return false;
|
||||||
|
}
|
2
src/lang/proj.txt
Normal file
2
src/lang/proj.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
lang
|
||||||
|
core
|
17
src/lang/version.cc
Normal file
17
src/lang/version.cc
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "lang/version.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
|
||||||
|
bool version_t::operator==(version_t other) const {
|
||||||
|
bool major_same = major == other.major;
|
||||||
|
bool minor_same = minor == -1 || other.minor == -1 || minor == other.minor;
|
||||||
|
bool revision_same = revision == -1 || other.revision == -1 || revision == other.revision;
|
||||||
|
|
||||||
|
return major_same && minor_same && revision_same;
|
||||||
|
}
|
||||||
|
bool version_t::is_compliant(version_t other) const {
|
||||||
|
bool major_compliant = major == other.major;
|
||||||
|
bool minor_compliant = minor == -1 || other.minor == -1 || minor <= other.minor;
|
||||||
|
|
||||||
|
return major_compliant && minor_compliant;
|
||||||
|
}
|
137
src/lsproj.cc
Normal file
137
src/lsproj.cc
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
struct project_t {
|
||||||
|
std::string output;
|
||||||
|
std::vector<std::string> deps;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string read_str(std::istream &f, std::string const &skip_chars, std::string const &end_chars, int &end_char) {
|
||||||
|
std::vector<char> res { };
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
c = f.get();
|
||||||
|
auto a = end_chars.find(c);
|
||||||
|
if (c == -1 || a != -1ull) {
|
||||||
|
end_char = c;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if ((a = skip_chars.find(c)) == -1ull) {
|
||||||
|
f.unget();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
c = f.get();
|
||||||
|
if (c == -1 || end_chars.find(c) != -1ull) {
|
||||||
|
end_char = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.push_back(c);
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
if (skip_chars.find(res.back()) != -1ull) res.pop_back();
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string { res.begin(), res.end() };
|
||||||
|
}
|
||||||
|
std::string read_str(std::istream &f, std::string const &skip_chars, std::string const &end_chars) {
|
||||||
|
int end_char;
|
||||||
|
return read_str(f, skip_chars, end_chars, end_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
project_t read_project(std::istream &f) {
|
||||||
|
int end_ch;
|
||||||
|
std::string name = read_str(f, " \v\t\r\n", "\n", end_ch);
|
||||||
|
std::size_t cap = 16, n = 0;
|
||||||
|
std::vector<std::string> deps { };
|
||||||
|
|
||||||
|
if (name.length() == 0) {
|
||||||
|
throw (std::string)"The name of a project may not be empty.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_ch == -1) {
|
||||||
|
return project_t {
|
||||||
|
.output = name,
|
||||||
|
.deps = deps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.find(',') != -1ull || name.find(' ') != -1ull) {
|
||||||
|
throw (std::string)"The name of a project may not contain spaces or commas.";
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
std::string dep = read_str(f, " \v\t\r\n", ",\n", end_ch);
|
||||||
|
|
||||||
|
if (dep.find(' ') != -1ull) {
|
||||||
|
throw (std::string)"The name of a dependency may not contain spaces.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dep.length()) {
|
||||||
|
deps.push_back(dep);
|
||||||
|
if (end_ch == '\n') break;
|
||||||
|
}
|
||||||
|
if (end_ch == -1) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return project_t {
|
||||||
|
.output = name,
|
||||||
|
.deps = deps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_err(std::string const &error, std::string const &context) {
|
||||||
|
std::cerr << context << ": " << error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char* argv[]) {
|
||||||
|
std::string proj_name = "";
|
||||||
|
try {
|
||||||
|
argc--;
|
||||||
|
argv++;
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
throw (std::string)"Incorrect usage. Syntax: [src-dir] [project-name] [output|deps].";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string proj_path = (std::string)argv[0] + "/" + argv[1] + "/proj.txt";
|
||||||
|
proj_name = argv[1];
|
||||||
|
|
||||||
|
std::ifstream f { proj_path, std::ios_base::in };
|
||||||
|
|
||||||
|
if (!f.is_open()) {
|
||||||
|
throw (std::string)"The project '" + argv[1] +"' doesn't exist.";
|
||||||
|
}
|
||||||
|
|
||||||
|
project_t project = read_project(f);
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
if ((std::string)argv[2] == "output") {
|
||||||
|
std::cout << project.output;
|
||||||
|
}
|
||||||
|
else if ((std::string)argv[2] == "deps") {
|
||||||
|
for (std::string dep : project.deps) {
|
||||||
|
std::cout << dep << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw (std::string)"Invalid command given. Available commands: output, deps.";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (std::string err) {
|
||||||
|
if (proj_name.length()) std::cerr << "Error: " << proj_name << ": " << err << std::endl;
|
||||||
|
else std::cerr << "Error: " << err << std::endl;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
172
src/main/main.cc
Normal file
172
src/main/main.cc
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#ifdef WINDOWS
|
||||||
|
|
||||||
|
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef DISABLE_NEWLINE_AUTO_RETURN
|
||||||
|
#define DISABLE_NEWLINE_AUTO_RETURN 0x8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <conio.h>
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include "utils/threading.hh"
|
||||||
|
#include "utils/strings.hh"
|
||||||
|
#include "compiler/treeifier/lexer.hh"
|
||||||
|
#include "compiler/treeifier/tokenizer.hh"
|
||||||
|
#include "./opions.hh"
|
||||||
|
|
||||||
|
using std::cout;
|
||||||
|
using std::size_t;
|
||||||
|
using namespace ppc;
|
||||||
|
using namespace ppc::comp::tree;
|
||||||
|
|
||||||
|
void add_flags(options::parser_t &parser) {
|
||||||
|
parser.add_flag({
|
||||||
|
.name = "version",
|
||||||
|
.shorthands = "v",
|
||||||
|
.description = "Displays version and license agreement of this binary",
|
||||||
|
.execute = [](options::parser_t &parser, std::string const &option, ppc::messages::msg_stack_t &global_stack) {
|
||||||
|
cout << "++C compiler\n"
|
||||||
|
<< " Version: v" << PPC_VERSION_MAJOR << '.' << PPC_VERSION_MINOR << '.' << PPC_VERSION_BUILD
|
||||||
|
#if WINDOWS
|
||||||
|
<< " (Windows)"
|
||||||
|
#elif LINUX
|
||||||
|
<< " (Linux)"
|
||||||
|
#endif
|
||||||
|
<< "\n"
|
||||||
|
<< " License: MIT Copyright (C) TopchetoEU\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
parser.add_flag({
|
||||||
|
.name = "help",
|
||||||
|
.shorthands = "h",
|
||||||
|
.description = "Displays a list of all flags and their meaning",
|
||||||
|
.execute = [](options::parser_t &parser, std::string const &option, ppc::messages::msg_stack_t &global_stack) {
|
||||||
|
cout << "Usage: ...flags ...files\n\n"
|
||||||
|
<< "Flags and file names can be interlaced\n"
|
||||||
|
<< "Flags will execute in the order they're written, then compilation begins\n\n"
|
||||||
|
<< "Flags:\n";
|
||||||
|
|
||||||
|
for (auto const &flag : parser) {
|
||||||
|
std::stringstream buff;
|
||||||
|
buff << " --" << flag.name;
|
||||||
|
|
||||||
|
if (flag.match_type) buff << "=...";
|
||||||
|
if (flag.shorthands.size()) {
|
||||||
|
buff << " (";
|
||||||
|
bool first = true;
|
||||||
|
for (char shorthand : flag.shorthands) {
|
||||||
|
if (!first) buff << ",";
|
||||||
|
else first = false;
|
||||||
|
|
||||||
|
buff << " -";
|
||||||
|
buff << std::string { shorthand };
|
||||||
|
}
|
||||||
|
buff << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
buff << " ";
|
||||||
|
|
||||||
|
cout << buff.str();
|
||||||
|
size_t n = buff.str().length();
|
||||||
|
|
||||||
|
if (flag.description.size()) {
|
||||||
|
const size_t padding = 24;
|
||||||
|
const size_t msg_width = 80 - padding;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < padding - n; i++) cout << ' ';
|
||||||
|
|
||||||
|
int len = flag.description.length();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len / msg_width; i++) {
|
||||||
|
for (size_t j = 0; j < msg_width; j++) cout << flag.description[i * msg_width + j];
|
||||||
|
cout << std::endl;
|
||||||
|
for (size_t j = 0; j < padding; j++) cout << ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = len % msg_width;
|
||||||
|
|
||||||
|
for (int i = 0; i < remainder; i++) cout << flag.description[len - remainder + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
parser.add_flag({
|
||||||
|
.name = "silent",
|
||||||
|
.shorthands = "qs",
|
||||||
|
.description = "Doesn't display any messages, regardless of their severity",
|
||||||
|
});
|
||||||
|
parser.add_flag({
|
||||||
|
.name = "msg-threshold",
|
||||||
|
.description = "Sets a lower limit of messages that will print. Accepted values: 'all', 'debug', 'suggestion', 'info', 'warning', 'error', 'none'",
|
||||||
|
.match_type = options::MATCH_PREFIX,
|
||||||
|
});
|
||||||
|
parser.add_flag({
|
||||||
|
.name = "print-what",
|
||||||
|
.description = "Prints a 'what?' type of message (you'll see)",
|
||||||
|
.match_type = options::MATCH_PREFIX,
|
||||||
|
.execute = [](options::parser_t &parser, std::string const &option, ppc::messages::msg_stack_t &global_stack) {
|
||||||
|
global_stack.push({ (messages::message_t::level_t)69, NO_LOCATION, "IDK LOL." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char *argv[]) {
|
||||||
|
#ifdef WINDOWS
|
||||||
|
HANDLE handleOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
DWORD consoleMode;
|
||||||
|
GetConsoleMode(handleOut, &consoleMode);
|
||||||
|
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
consoleMode |= DISABLE_NEWLINE_AUTO_RETURN;
|
||||||
|
SetConsoleMode(handleOut, consoleMode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<std::string> args{ argv + 1, argv + argc };
|
||||||
|
std::vector<std::string> files;
|
||||||
|
messages::msg_stack_t msg_stack;
|
||||||
|
|
||||||
|
options::parser_t parser;
|
||||||
|
data::map_t conf;
|
||||||
|
add_flags(parser);
|
||||||
|
|
||||||
|
for (auto const &arg : args) {
|
||||||
|
if (!parser.parse(arg, msg_stack, conf)) {
|
||||||
|
files.push_back(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const &file : files) {
|
||||||
|
std::ifstream f { file, std::ios_base::in };
|
||||||
|
try {
|
||||||
|
auto res = tok::token_t::parse_many(msg_stack, lex::token_t::parse_file(msg_stack, file, f));
|
||||||
|
|
||||||
|
for (auto tok : res) {
|
||||||
|
if (tok.is_identifier()) std::cout << "Identifier: " << tok.identifier();
|
||||||
|
if (tok.is_operator()) std::cout << "Operator: " << tok::operator_stringify(tok._operator());
|
||||||
|
if (tok.is_float_lit()) std::cout << "Float: " << tok.float_lit();
|
||||||
|
if (tok.is_int_lit()) std::cout << "Int: " << tok.int_lit();
|
||||||
|
if (tok.is_char_lit()) std::cout << "Char: " << tok.char_lit();
|
||||||
|
if (tok.is_string_lit()) std::cout << "String: " << std::string { tok.string_lit().begin(), tok.string_lit().end() };
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (messages::message_t const &msg) {
|
||||||
|
msg_stack.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_stack.print(std::cout, messages::message_t::DEBUG, true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
35
src/main/opions.hh
Normal file
35
src/main/opions.hh
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "utils/message.hh"
|
||||||
|
#include "data.hh"
|
||||||
|
|
||||||
|
namespace ppc::options {
|
||||||
|
enum flag_match_type_t {
|
||||||
|
MATCH_WHOLE,
|
||||||
|
MATCH_PREFIX,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct parser_t;
|
||||||
|
|
||||||
|
struct flag_t {
|
||||||
|
std::string name;
|
||||||
|
std::string shorthands;
|
||||||
|
std::string description;
|
||||||
|
flag_match_type_t match_type;
|
||||||
|
void (*execute)(parser_t &parser, std::string const &option, messages::msg_stack_t &global_stack);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct parser_t {
|
||||||
|
private:
|
||||||
|
std::vector<flag_t> flags;
|
||||||
|
public:
|
||||||
|
void add_flag(flag_t const &flag);
|
||||||
|
void clear_flags();
|
||||||
|
|
||||||
|
auto begin() { return flags.begin(); }
|
||||||
|
auto end() { return flags.end(); }
|
||||||
|
|
||||||
|
bool parse(std::string const &option, messages::msg_stack_t &msg_stack, data::map_t &conf);
|
||||||
|
};
|
||||||
|
}
|
63
src/main/options.cc
Normal file
63
src/main/options.cc
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include <set>
|
||||||
|
#include "./opions.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
|
||||||
|
bool check_shorthand(std::string &option, options::flag_t const &flag) {
|
||||||
|
if (option.size() < 2 || option[0] != '-') return false;
|
||||||
|
|
||||||
|
if (option.size() == 2 && std::string { flag.shorthands }.find(option[1]) != -1u) {
|
||||||
|
option = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (flag.match_type == options::MATCH_PREFIX) {
|
||||||
|
if (option.length() < 3 || option[2] != '=') return false;
|
||||||
|
option = option.substr(3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool check_name(std::string &option, options::flag_t const &flag) {
|
||||||
|
if (option.size() > 2 && option[0] == '-' && option[1] == '-') {
|
||||||
|
std::string candidate = option.substr(2);
|
||||||
|
if (flag.match_type == options::MATCH_WHOLE) {
|
||||||
|
if (candidate == flag.name) {
|
||||||
|
option = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (candidate.find(flag.name) == 0) {
|
||||||
|
candidate = candidate.substr(flag.name.length());
|
||||||
|
if (candidate.length() > 0) {
|
||||||
|
if (candidate[0] == '=') {
|
||||||
|
option = candidate.substr(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ppc::options::parser_t::add_flag(flag_t const &flag) {
|
||||||
|
flags.push_back(flag);
|
||||||
|
}
|
||||||
|
void ppc::options::parser_t::clear_flags() {
|
||||||
|
flags.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ppc::options::parser_t::parse(std::string const &option, messages::msg_stack_t &msg_stack, data::map_t &conf) {
|
||||||
|
if (option.empty()) return false;
|
||||||
|
|
||||||
|
std::string opt = option;
|
||||||
|
|
||||||
|
for (auto const &flag : flags) {
|
||||||
|
if (check_name(opt, flag) || check_shorthand(opt, flag)) {
|
||||||
|
flag.execute(*this, opt, msg_stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
2
src/main/proj.txt
Normal file
2
src/main/proj.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
main
|
||||||
|
utils, compiler
|
142
src/utils/data.cc
Normal file
142
src/utils/data.cc
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include "data.hh"
|
||||||
|
|
||||||
|
template<typename ... Ts>
|
||||||
|
struct overload : Ts ... { using Ts::operator() ...; };
|
||||||
|
template<class... Ts> overload(Ts...) -> overload<Ts...>;
|
||||||
|
|
||||||
|
bool ppc::data::value_t::is_null() const {
|
||||||
|
return type == type_t::Null;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::is_map() const {
|
||||||
|
return type == type_t::Map;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::is_array() const {
|
||||||
|
return type == type_t::Arr;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::is_number() const {
|
||||||
|
return type == type_t::Num;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::is_string() const {
|
||||||
|
return type == type_t::Str;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::is_bool() const {
|
||||||
|
return type == type_t::Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ppc::data::value_t::array(ppc::data::array_t &out) const {
|
||||||
|
if (is_array()) {
|
||||||
|
out = *val.arr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::map(ppc::data::map_t &out) const {
|
||||||
|
if (is_map()) {
|
||||||
|
out = *val.map;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::number(ppc::data::number_t &out) const {
|
||||||
|
if (is_number()) {
|
||||||
|
out = val.num;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::string(ppc::data::string_t &out) const {
|
||||||
|
if (is_string()) {
|
||||||
|
out = *val.str;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ppc::data::value_t::boolean(ppc::data::bool_t &out) const {
|
||||||
|
if (is_bool()) {
|
||||||
|
out = val.bl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ppc::data::array_t const &ppc::data::value_t::array() const {
|
||||||
|
if (is_array()) return *val.arr;
|
||||||
|
else throw (std::string)"The value isn't an array.";
|
||||||
|
}
|
||||||
|
ppc::data::map_t const &ppc::data::value_t::map() const {
|
||||||
|
if (is_map()) return *val.map;
|
||||||
|
else throw (std::string)"The value isn't a map.";
|
||||||
|
}
|
||||||
|
ppc::data::number_t ppc::data::value_t::number() const {
|
||||||
|
if (is_number()) return val.num;
|
||||||
|
else throw (std::string)"The value isn't a number.";
|
||||||
|
}
|
||||||
|
ppc::data::string_t const &ppc::data::value_t::string() const {
|
||||||
|
if (is_string()) return *val.str;
|
||||||
|
else throw (std::string)"The value isn't a string.";
|
||||||
|
}
|
||||||
|
ppc::data::bool_t ppc::data::value_t::boolean() const {
|
||||||
|
if (is_bool()) return val.bl;
|
||||||
|
else throw (std::string)"The value isn't a bool.";
|
||||||
|
}
|
||||||
|
|
||||||
|
ppc::data::value_t::value_t() {
|
||||||
|
this->type = type_t::Null;
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::array_t const &val) {
|
||||||
|
this->type = type_t::Arr;
|
||||||
|
this->val.arr = new array_t { val };
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::map_t const &val) {
|
||||||
|
this->type = type_t::Map;
|
||||||
|
this->val.map = new map_t { val };
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::string_t const &val) {
|
||||||
|
this->type = type_t::Str;
|
||||||
|
this->val.str = new string_t { val };
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::bool_t val) {
|
||||||
|
this->type = type_t::Bool;
|
||||||
|
this->val.bl = val;
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::number_t val) {
|
||||||
|
this->type = type_t::Num;
|
||||||
|
this->val.num = val;
|
||||||
|
}
|
||||||
|
ppc::data::value_t::value_t(ppc::data::value_t const &other) {
|
||||||
|
type = other.type;
|
||||||
|
switch (other.type) {
|
||||||
|
case type_t::Map:
|
||||||
|
val.map = new map_t { *other.val.map };
|
||||||
|
break;
|
||||||
|
case type_t::Arr:
|
||||||
|
val.arr = new array_t { *other.val.arr };
|
||||||
|
break;
|
||||||
|
case type_t::Str:
|
||||||
|
val.str = new string_t { *other.val.str };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
val = other.val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ppc::data::value_t::~value_t() {
|
||||||
|
switch (type) {
|
||||||
|
case type_t::Map:
|
||||||
|
delete val.map;
|
||||||
|
break;
|
||||||
|
case type_t::Arr:
|
||||||
|
delete val.arr;
|
||||||
|
break;
|
||||||
|
case type_t::Str:
|
||||||
|
delete val.str;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ppc::data::value_t &ppc::data::value_t::operator=(ppc::data::value_t const &other) {
|
||||||
|
// return *this;
|
||||||
|
// }
|
117
src/utils/location.cc
Normal file
117
src/utils/location.cc
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#include "utils/location.hh"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
|
||||||
|
std::string location_t::to_string() const {
|
||||||
|
std::stringstream res;
|
||||||
|
bool written_anything = false;
|
||||||
|
|
||||||
|
if (filename.length()) {
|
||||||
|
res << filename;
|
||||||
|
written_anything = true;
|
||||||
|
}
|
||||||
|
if (line != -1u) {
|
||||||
|
if (written_anything) res << ':';
|
||||||
|
res << line + 1;
|
||||||
|
written_anything = true;
|
||||||
|
}
|
||||||
|
if (start != -1u) {
|
||||||
|
if (written_anything) res << ':';
|
||||||
|
res << start + 1;
|
||||||
|
written_anything = true;
|
||||||
|
}
|
||||||
|
if (length != -1u) {
|
||||||
|
if (written_anything) res << '(' << length + 1 << ')';
|
||||||
|
written_anything = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.str();
|
||||||
|
}
|
||||||
|
static void fix_location(location_t &loc) {
|
||||||
|
if (loc.line == -1u) loc.line = 0;
|
||||||
|
if (loc.start == -1u) loc.start = 0;
|
||||||
|
if (loc.length == -1u) loc.length = 0;
|
||||||
|
if (loc.code_start == -1u) loc.code_start = 0;
|
||||||
|
}
|
||||||
|
location_t location_t::intersect(location_t other) const {
|
||||||
|
location_t a = *this;
|
||||||
|
location_t b = other;
|
||||||
|
|
||||||
|
if (a.start == -1u || b.start == -1u) return { };
|
||||||
|
|
||||||
|
if (a.start > b.start) {
|
||||||
|
location_t c = a;
|
||||||
|
a = b;
|
||||||
|
b = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_location(a);
|
||||||
|
fix_location(b);
|
||||||
|
|
||||||
|
int a_end = a.code_start + a.length;
|
||||||
|
int b_end = b.code_start + b.length;
|
||||||
|
|
||||||
|
if (a_end < b_end) {
|
||||||
|
a.length += b_end - a_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
location_t::location_t() {
|
||||||
|
this->line = -1;
|
||||||
|
this->start = -1;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = -1;
|
||||||
|
this->filename = "";
|
||||||
|
}
|
||||||
|
location_t::location_t(std::string filename) {
|
||||||
|
this->line = -1;
|
||||||
|
this->start = -1;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = -1;
|
||||||
|
this->filename = filename;
|
||||||
|
}
|
||||||
|
location_t::location_t(std::size_t line, std::size_t start) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = -1;
|
||||||
|
this->filename = "";
|
||||||
|
}
|
||||||
|
location_t::location_t(std::string filename, std::size_t line, std::size_t start) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = -1;
|
||||||
|
this->filename = filename;
|
||||||
|
}
|
||||||
|
location_t::location_t(std::size_t line, std::size_t start, std::size_t code_start) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = code_start;
|
||||||
|
this->filename = "";
|
||||||
|
}
|
||||||
|
location_t::location_t(std::string filename, std::size_t line, std::size_t start, std::size_t code_start) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = -1;
|
||||||
|
this->code_start = code_start;
|
||||||
|
this->filename = filename;
|
||||||
|
}
|
||||||
|
location_t::location_t(std::size_t line, std::size_t start, std::size_t code_start, std::size_t length) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = line;
|
||||||
|
this->code_start = code_start;
|
||||||
|
this->filename = "";
|
||||||
|
}
|
||||||
|
location_t::location_t(std::string filename, std::size_t line, std::size_t start, std::size_t code_start, std::size_t length) {
|
||||||
|
this->line = line;
|
||||||
|
this->start = start;
|
||||||
|
this->length = line;
|
||||||
|
this->code_start = code_start;
|
||||||
|
this->filename = filename;
|
||||||
|
}
|
71
src/utils/message.cc
Normal file
71
src/utils/message.cc
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include "utils/message.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
|
||||||
|
std::string messages::message_t::to_string() const {
|
||||||
|
std::string loc_readable = location.to_string();
|
||||||
|
std::string level_readable;
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case messages::message_t::DEBUG: level_readable = "debug"; break;
|
||||||
|
case messages::message_t::SUGGESTION: level_readable = "suggestion"; break;
|
||||||
|
case messages::message_t::INFO: level_readable = "info"; break;
|
||||||
|
case messages::message_t::WARNING: level_readable = "warning"; break;
|
||||||
|
case messages::message_t::ERROR: level_readable = "error"; break;
|
||||||
|
default: level_readable = "what?"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream res { };
|
||||||
|
|
||||||
|
if (loc_readable.length()) res << loc_readable << ": ";
|
||||||
|
res << level_readable << ": " << content;
|
||||||
|
|
||||||
|
return res.str();
|
||||||
|
}
|
||||||
|
bool messages::message_t::is_severe() const {
|
||||||
|
return level > messages::message_t::WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool messages::msg_stack_t::is_failed() const {
|
||||||
|
for (auto const &msg : messages) {
|
||||||
|
if (msg.is_severe()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void messages::msg_stack_t::print(std::ostream &output, messages::message_t::level_t threshold, bool color_output) const {
|
||||||
|
if (!messages.size()) return;
|
||||||
|
|
||||||
|
for (auto const &msg : messages) {
|
||||||
|
if (msg.level < threshold) continue;
|
||||||
|
|
||||||
|
std::string loc_readable = msg.location.to_string();
|
||||||
|
|
||||||
|
switch (msg.level) {
|
||||||
|
case messages::message_t::DEBUG:
|
||||||
|
output << (color_output ? "\e[38;5;8mdebug: " : "debug: ");
|
||||||
|
break;
|
||||||
|
case messages::message_t::SUGGESTION:
|
||||||
|
output << (color_output ? "\e[38;5;45msuggestion: " : "suggestion: ");
|
||||||
|
break;
|
||||||
|
case messages::message_t::INFO:
|
||||||
|
output << (color_output ? "\e[38;5;33minfo: ": "info: ");
|
||||||
|
break;
|
||||||
|
case messages::message_t::WARNING:
|
||||||
|
output << (color_output ? "\e[38;5;214mwarning: " : "warning: ");
|
||||||
|
break;
|
||||||
|
case messages::message_t::ERROR:
|
||||||
|
output << (color_output ? "\e[38;5;196merror: " : "error: ");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
output << (color_output ? "\e[38;5;196mw\e[38;5;226mh\e[38;5;118ma\e[38;5;162mt\e[38;5;129m?\e[0m: " : "what?: ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loc_readable.length()) output << loc_readable << ": ";
|
||||||
|
output << msg.content;
|
||||||
|
if (color_output) output << "\e[0m";
|
||||||
|
output << std::endl;
|
||||||
|
}
|
||||||
|
}
|
1
src/utils/proj.txt
Normal file
1
src/utils/proj.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
utils
|
40
src/utils/strings.cc
Normal file
40
src/utils/strings.cc
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include <sstream>
|
||||||
|
#include "utils/strings.hh"
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
|
||||||
|
std::vector<std::string> str::split(std::string const &splittable, std::initializer_list<char> splitters, bool remove_empty_entries) {
|
||||||
|
std::stringstream buff;
|
||||||
|
std::vector<std::string> res;
|
||||||
|
|
||||||
|
for (char c : splittable) {
|
||||||
|
if (std::string { splitters }.find(c) == -1u) {
|
||||||
|
buff << c;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!buff.str().empty() || !remove_empty_entries) {
|
||||||
|
res.push_back(buff.str());
|
||||||
|
buff = { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buff.str().empty() || !remove_empty_entries) {
|
||||||
|
res.push_back(buff.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str::trim(std::string splittable, std::initializer_list<char> splitters) {
|
||||||
|
auto split = std::string { splitters };
|
||||||
|
|
||||||
|
while (!splittable.empty() && split.find(splittable[0]) != -1u) {
|
||||||
|
splittable = splittable.substr(1);
|
||||||
|
}
|
||||||
|
while (!splittable.empty() && split.find(splittable[splittable.length() - 1]) != -1u) {
|
||||||
|
splittable = splittable.substr(0, splittable.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return splittable;
|
||||||
|
}
|
44
src/utils/threading.cc
Normal file
44
src/utils/threading.cc
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "utils/threading.hh"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace ppc;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
#ifdef WINDOWS
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
threading::thread_t threading::thread_t::start_impl(void *func, void *args) {
|
||||||
|
DWORD id;
|
||||||
|
HANDLE hnd = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, args, 0, &id);
|
||||||
|
|
||||||
|
if (!hnd) throw "Couldn't create thread."s;
|
||||||
|
return { hnd };
|
||||||
|
}
|
||||||
|
int threading::thread_t::join() const {
|
||||||
|
if (WaitForSingleObject(handle, INFINITE) == WAIT_FAILED) {
|
||||||
|
return GetLastError();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
threading::thread_t::~thread_t() {
|
||||||
|
CloseHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif LINUX
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
threading::thread_t threading::thread_t::start_impl(void *func, void *args) {
|
||||||
|
pthread_t *handle = new pthread_t;
|
||||||
|
pthread_create(handle, nullptr, (void* (*)(void *))func, args);
|
||||||
|
return { handle };
|
||||||
|
}
|
||||||
|
int threading::thread_t::join() const {
|
||||||
|
return pthread_join(*(pthread_t*)handle, nullptr);
|
||||||
|
}
|
||||||
|
threading::thread_t::~thread_t() {
|
||||||
|
delete (pthread_t*)handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user