PasCry

PasCry: a compiler for a Pascal-like derivative written from scratch in Crystal, leveraging LLVM to generate native binaries.

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
  • Clang 17.0.6
    • clang is used as the linker and final compilation step to go from LLVM IR .ll files to a native executable.
    • clang must be discoverable on the system path.

How to Install

  • To install, run shards build compiler and the compiler executable will be found in the bin directory.
    • Similarly, shards run compiler -- [arguments to executable] can be used to directly build and run the executable at the same time.

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.o to 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:

  1. Compile Crystal from source at the GitHub using the correct LLVM version.
  2. 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

  1. 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
  2. Install using brew install --formula crystal.rb
  3. 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.
  4. Export $(brew --prefix)/lib as the environment variables CRYSTAL_LIBRARY_PATH and CRYSTAL_LIBRARY_RPATH. This will allow for Crystal to correctly link against LLVM and its other dependencies (such as libevent).
  5. 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.

  1. 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
  2. Run docker run --rm --it --entrypoint /bin/bash <container_name>
  3. The compiler binary will be located at bin/compiler. All compiled binaries will be outputted to the output directory. If no output name is defined with -o, it will be compiler_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

  1. Fork it (https://github.com/JarnaChao09/PasCry)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

Repository

PasCry

Owner
Statistic
  • 1
  • 0
  • 0
  • 0
  • 0
  • about 4 hours ago
  • January 15, 2024
License

MIT License

Links
Synced at

Fri, 17 Jul 2026 23:39:59 GMT

Languages