PasCry
PasCry
PasCry (Pascal in Crystal) is a small prototype Pascal-like derivative written in Crystal. The point of this project was to learn more about LLVM and building an end-to-end compiler from source text to native binary.
Example
The following is an example program of the array programming operations in PasCry.
program arr is
variable tmp : integer;
variable arr : integer[3];
procedure stuff : integer()
variable x : integer[3];
begin
tmp := getinteger();
x[0] := tmp;
arr[0] := tmp + 1;
x[1] := tmp - 1;
arr[1] := tmp + 10;
x[2] := tmp * 10;
arr[2] := tmp * 2;
tmp := putinteger(x[0]);
tmp := putinteger(x[1]);
tmp := putinteger(x[2]);
tmp := putinteger(arr[0]);
tmp := putinteger(arr[1]);
tmp := putinteger(arr[2]);
return 0;
end procedure;
begin
tmp := stuff();
end program.
More example programs can be found here
Features
| Area | Supported |
|---|---|
| Builtin Types | integer, float, string, bool, and fixed-sizes arrays of builtin types |
| Declarations | global and local typed procedure and typed variable declarations |
| Control Flow | if statements (if (cond) then if_body; else else_body; end if), loops (for (init; cond) body; end for), recursion |
| Operators | scalar and array arithmetic, scalar and array comparison, scalar and array logical |
| Builtin functions | getInteger/putInteger, getFloat/putFloat, getBool/putBool, getString/putString, sqrt |
Grammar for PasCry
program = program_header program_body "." ;
program_header = "program" identifier "is" ;
program_body = ( declaration ";" )* "begin" ( statement ";" )* "end program" ;
declaration = ( "global" )? ( procedure_declaration | variable_declaration ) ;
procedure_declaration = procedure_header procedure_body ;
procedure_header = "procedure" identifier ":" type_mark "(" parameter_list? ")" ;
parameter_list = parameter ( "," parameter )* | parameter ;
parameter = variable_declaration;
procedure_body = ( declaration ";" )* "begin" ( statement ";" )* "end procedure" ;
variable_declaration = "variable" identifier ":" type_mark ( "[" bound "]" )? ;
type_mark = "integer" | "float" | "string" | "bool" ;
bound = number ;
statement = assignment_statement | if_statement | loop_statement | return_statement ;
procedure_call = identifier "(" argument_list? ")" ;
assignment_statement = destination ":=" expression ;
destination = identifier ( "[" expression "]" )? ;
if_statement = "if" "(" expression ")" "then" ( statement ";" )* ( "else" ( statement ";" )* )? "end if" ;
loop_statement = "for" "(" assignment_statement ";" expression ")" ( statement ";" )* "end for" ;
return_statement = "return" expression ;
identifier = [a-zA-Z] [a-zA-Z0-9_]* ;
expression = ( "not" )? arithOp ( "&" | "|" expression )* ;
arithOp = relation ( "+" | "-" arithOp )* ;
relation = term ( "<" | ">=" | "<=" | ">" | "==" | "!=" term )* ;
term = factor ( "*" | "/" factor )* ;
factor = "(" expression ")" | procedure_call | ( "-" )? ( name | number | string | "true" | "false" ) ;
name = identifier ( "[" expression "]" )? ;
argument_list = expression ( "," expression )* ;
number = [0-9] [0-9_]* [.[0-9_]*] ;
string = "\"" [^"]* "\"" ;
Installation
Requirements
- The Crystal language
- Version >= 1.11.0. See getting the right crystal section for more details
- The compiler is built entirely in Crystal with zero dependencies
- LLVM 17.0.6
- Crystal comes with pre-built bindings to LLVM. See getting the right LLVM section for more details.
- Clang 17.0.6
clangis used as the linker and final compilation step to go from LLVM IR.llfiles to a native executable.clangmust be discoverable on the system path.
How to Install
- To install, run
shards build compilerand the compiler executable will be found in thebindirectory.- Similarly,
shards run compiler -- [arguments to executable]can be used to directly build and run the executable at the same time.
- Similarly,
Usage
The usage of the executable is as follows:
Welcome to the compiler for EECE 5183
Usage: compiler [subcommand] [options] [program-file]
compile subcommand: Compile a file
run subcommand: Compile and Execute a file using LLVM JIT
-emit-ir, --emit-llvm-ir Emits generated LLVM IR
-dump-ir, --dump-llvm-ir Dumps generated LLVM IR
-v, --version Show Version
-h, --help Show Help
Getting the right LLVM
As of writing (04/26/2024), most major package managers (apt, pacman, snap) have crystal being pre-compiled with LLVM 15.0.7. For this project, LLVM 17.0.6 was the primary LLVM version tested. Mileage may vary depending on the compiled LLVM version.
The one package manager that is currently up-to-date on the LLVM version being built with Crystal is Homebrew/Linuxbrew. However, recently the Homebrew/Linuxbrew bottle for Crystal has been updated to use LLVM 18.1.4 which Crystal current fails to build correctly with.
The reason for this is because LLVM 18 no longer requires
llvm_ext.oto be linked and therefore will no longer build the object file during installation. However, Crystal and shards have not been updated to fix the utilized linking path, therefore they fail to build on the latest bottle from Homebrew/Linuxbrew.
There are currently two options to get the correct LLVM version:
- Compile Crystal from source at the GitHub using the correct LLVM version.
- Pour from an old Homebrew/Linuxbrew formula to recieve the correct built binary.
Getting the right Crystal
Requirements
- To install Crystal using the second method detailed above, Homebrew/Linuxbrew will be required.
Installing Crystal using brew
- Retrieve the correct brew formula for Crystal 1.11.2. This can be done using
curl -o crystal.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/337abdc50d8299795a13edf78b5eb8f340985579/Formula/c/crystal.rb - Install using
brew install --formula crystal.rb - Verify that the correct version of Crystal has been installed with
crystal --version. The output should show Crystal 1.11.2 and LLVM 17.0.6. - Export
$(brew --prefix)/libas the environment variablesCRYSTAL_LIBRARY_PATHandCRYSTAL_LIBRARY_RPATH. This will allow for Crystal to correctly link against LLVM and its other dependencies (such as libevent). - Verify that crystal is picking up on the correct environment variables with
crystal env.
These installation steps were tested on an Ubuntu WSL2 instance, mileage may vary.
Using the Docker Image
This section assumes that docker is already installed and properly setup. To do so, please see getting docker.
- Run
docker build -t <container_name> .- This will set up Crystal 1.11.2 and all of its dependencies such that Crystal will be built with LLVM 17.0.6
- Run
docker run --rm --it --entrypoint /bin/bash <container_name> - The compiler binary will be located at
bin/compiler. All compiled binaries will be outputted to theoutputdirectory. If no output name is defined with-o, it will becompiler_tmp.out.
Development
All development occurred on Crystal version 1.11.2 on a 13 inch M1 Macbook Pro 2020 edition. Crystal was installed using homebrew with LLVM version 17.0.6 and clang version 17.0.6. A docker file is provided to be used for all future development.
Remaining Tasks
- Verify remaining semantic details
- Include source information inside error messages
- Advanced resynchronization capabilities
- Update strings to not memory leak
- Create docker file
- Create better, more advanced, cli options
Contributing
- Fork it (https://github.com/JarnaChao09/PasCry)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- JarnaChao09 - creator and maintainer
PasCry
- 1
- 0
- 0
- 0
- 0
- about 4 hours ago
- January 15, 2024
MIT License
Fri, 17 Jul 2026 23:39:59 GMT