Skip to content

scottshotgg/express

Repository files navigation

express

Express is an extremely flexible language allowing both static and dynamic types (var; think JavaScript or Python — except better) with an extremely lite, dynamically embedded runtime within the executable allowing for typing to be as weak or strong (or 🦆) as required. By allowing even the typing to be defined within the language, many different programming paradigms, architectures, and methods of implementation can be readily expressed.

The runtime currently does dynamic typing and RAII lifetime management, however, in the future it will include a greenthread scheduler a la Go, atomization of cross-thread operations, garbage collection, RTTI and (maybe) reflection, SQLite3/GraphQL embedded databases, DOM instantiation and manipulation, and a few other ideas that are currently only conceptual. Most features will be optional with the ability to easily enable and disable them at compile time and possibly runtime (if a JIT/AOT is supported — maybe using GraalVM).
The main influences in the languages design are: C++, JavaScript, Go, and (atleast conceptually) Rust.

For binary production, programs are currently transpiled to C++ and then clang is subsequently invoked (along with clang-format) to produce the corresponding binary. There will also be a C++ program produced at compile time, which can be included in the output via the --emit-cpp flag.
At this time, transpiling is, time-wise, sufficiently more efficient than outputting LLVM tokens or building an intermediary using SSA/3AC. Later on, this will most likely be changed in favor of direct LLVM token production when features either become too much of a burden to implement and maintain in C++ or the transpiler development lags too much to adequently support forwarding the development of the language.

Each stage of the compiler (lexer, syntax parser, semantics parser, and C++ transpiler) are currently all implemented in Go and may be converted to C++ or Rust later on (when I feel like operating LLVM directly), but a JavaScript implementation in Node is also being developed simultaneously and will later on be consolidated with this repo after a reorganization of the file structure.
It is currently located at https://github.com/Swivelgames/Express/tree/alt/node


State of the Compiler

Stages Implemented:

  • Lexer
  • Syntax
  • Semantics
  • C++ Transpiler with clang-format
  • Binary

Features Implemented:

[~] means the feature is still being decided on
[=] means I am currently working on implementing that feature

  • Blocks
  • Basic types (int, bool, float, string)
  • object type
    • statements within objects (making objects and blocks identical structures)
  • struct type
    • Tags
  • function type
  • [-] array type
    • int[]
    • float[]
    • bool[]
    • string[]
    • var[]
    • object[]
    • <struct>[]
    • array[]
    • function[]
    • Type inference
  • [-] var type
    • Basic type encapsulation (int, bool, float, string)
    • object type encapsulation
    • <struct> type encapsulation
      • This is doing the same thing as casting your struct to an object
    • [~] array type encapsulation
      • Leaning against: not sure if I want to allow a single var to be able to contain multiple values
    • [~] function type encapsulation
      • A var holding a function doesn't make a lot of sense right now
  • Type modifiers - keywords
    • array postfix
    • unsigned prefix
    • constant prefix
  • Type modifiers - shorts
    • s postfix
    • u prefix
    • c prefix
  • [=] Function usage
    • [=] Function declaration
      • [=] no args and no returns
      • [=] args without returns
      • [=] returns without args
      • args and returns
    • Function call
  • [-] Access modifiers
    • private
    • public
    • fileprivate


How To Contribute

Proposals

Proposal submission is the method to contribute your ideas and work into Express. The first step to submitting a proposal is a usually feature request through a PR. Below contains the list of steps and the corresponding criteria for submission and approval.

  • Features:
    Features should be submitted in the form of a markdown file (title_of_your_feature.md) describing the proposed abilities into a folder within proposals/features/<appropriately_labeled> folder. Proposals need to use fenced code blocks with a combination of cs, vb and js highlighting tags and contain an Express program example of your feature.


  • Ideas:
    Ideas about the languages direction, internals related to the architecture, or general core-langaage level implementations requests should go into the proposals/ideas folder and should be formatted as a whitepaper with detail containing a beginning narative that will explain where and how the idea came from, atleast two supporting arguments that should answer the question of why your proposal should be considered, and one informal/abstract (i.e, pseudocode) use-case on how it would be used after implementation.


  • Implementations and Experiments:
    Implementations and Experiments should be done in a separate branch off of the repo after approval of a submitted feature request. When you are ready to submit your implementation, you should fetch the latest upstream master and merge your code in locally, confirm that tests still work, and then submit a PR into master.
    Your PR should also contain an Express test program in the test/output/programs folder, along with the respective lex, syn, sem, cpp, and bin files that will be used to verify the tests.


Note: If your PR does not meet the criteria when it is submitted, cannot be merged into the parent branch, or standard tests do not pass, it will be automatically denied by the CI/CD pipeline with comments about failure. You will need to submit another PR after fixing the mentioned issues.



Example Program

Apologies before we get started, but this does need to be updated when I have time as there are quite a few features missing in contrast to what is supported. Please take a look at the programs located in test/programs/ for more examples.

In the program below, you will find a few examples of the allowed flexibilities and optional verbosity that allow the language to be so Expressive.
You can find the full uncommented version located in test/programs/advanced.expr.



Let's Begin:



Start off by declaring some variables:

int powerlevel = 0x2327;
bool over9k = false;
float pi = 3.14159265359;
string arizona = "iced out boys";

Note: If you're experienced in programming, you might have noticed that we did not begin by declaring a main function or designated execution entry point; this is definitely not by design and will be enforced at a later date.


In addition to the basic static types, Express also supports using dynamically typed variables as well.
It is important to note that these variables are dynamically typed at run time and thus will incur a performance penalty in constrast to static variables correlating to the same shadow type.

// start 'hi_my_type_is' off as a dynamically typed string variable
var hi_my_type_is = "what"

// now i'm an integer
hi_my_type_is = 666

// here's a bool
hi_my_type_is = false

// and finally, a float
hi_my_type_is = 2.71828

For a closer look at how the runtime manages this in C++, see the source code in lib/var.cpp.
More documentation and better comments [ as if there is any, lol - really though :^) ] will be added later - i promise


Comments also take on a familiar syntax:

// Inline comment

/*
  Multiline
  comment
*/

Another way to declare variables in Express is through type inference. Both of the variables below are of type int, however the latter type was inferred based on evaluation of the rvalue:

int zero = 0
    one := 1    // tabbed for visibility

Note: Type inference will never produce a var or a struct type.
Fundamentally, the var type could be considered the ground state for any variable type and thus would always resolve as a possible type. Furthermore, if you are specifying to infer a variables type, it doesn't make much sense, functionally, to respond with a generic container.
On the other hand, struct is a different issue. Structs and objects are very similar ideas, however, one is dynamic - object, and the other is not - struct. Thus, when assigning a BLOCK to a variable in Express, it will assume you do not want this to be a static struct type or else you would have declared it yourself at compile-time. However, this does not prevent you from declaring an object at compile-time or a struct at run-time using a type specification at declaration.


Optional Verbosity:
Before moving on, let's explain a primary motivator in the Express language development. Above, you might have observed that the usage of commas and semicolons as statement delimiters seems to be optional, and indeed they are! Statement delimiters are not required, but are acceptable (; or ,) if you'd prefer to be more verbose or are accustomed to C-style programming.
In the underlying parser architecture, they (; or ,) serve a semantic purpose by marking the end of a statement parse which will manually command the compiler to dump the current parse. By default, the ending of the statement will be semantically inferred, however, there are compiler flags and ECMA-335 attributes to modify the default action and enforce strict punctuation as granularly (or entirely) as you prefer.
In this regard, having the flexibility to allow the compiler to semantically infer the end of the statement, while also retaining the ability to manually signal when a statement should end, can be very satisfying and relaxing when programming.
This allowed flexibility is known as optional verbosity in Express and is one of the key motivators in it's development.


Now, I wouldn't do this, but as a testament towards the semantic reasoning within the parser; you can even write statements on the same line as each other:

string ayy = "ayy" string waddup = "waddup" int timestamp = 1527799745

Usage of the set operator is permitted even outside of object declarations.

anotherOne: "anotherOne"

Below shows a type inferred object where most of its properties are also type inferred. The following statements until the ending brace are all conatined within the testerino variable.

testerino := {
    id: "ba3d4793-cfae-48d1-ad51-47cbfd70f98a"

Reference one of the above variables in a new property declaration

    time: timestamp

You can also use the assignment operator along with a type to crimp the type of the variable instead of leaving it up for interpretation by the compiler

    float price = 55.3592,

The inference operator can also be used within objects. Although currently it doesn't do anything different than the set operator within an object, it may have a more impactful use later

    dank_meme := true

Any unicode character is supported

    🔥420🔥    : "🅱️laze it"
    ⒹⒿKhalid : anotherOne

An array composition using the above definitions of zero and one to derive a type and length inference:

    ten: [ one, zero, one, zero ]

A few nested objects

    throw_more_dots: {
        throw_more_dots: {
            more_dots: {
                more_dots: {
                    ok: "stop dots",
                },
            },
        },
    }

Ending of the testerino object

}

Arrays can be declared as well; below is a static string type array using composition to infer an array length:

string[] stringArray = [ "hi", "my", "name", "is", "scott" ]

Expanding on the above example, delineatiation of elements from one another using commas follows the same logic as the aforementioned statement delimiters. It isn't required but should be used at your own descretion of verbosity.
The spacing also doesn't matter, but readable code does.
Again - its all semantics ¯\_(ツ)_/¯

string[] here_comes =
[
  ayy,
      waddup
             "its",
                    "dat"
                          "boi"
                                ]

Quick power level check before blasting off...

if powerlevel < 9001 {
   powerlevel = 9001
   over9k = true
}

A simple for loop:

percent: 0
for progress := 0, progress < 100, progress++ {
   percent = progress
}

A key-based iterator for loop (for..in):

int i = 0;
for index in [ 1, 2, 4 ] {
    i = index;
}

A value-based iterator for loop (for..of):

houstonWeHaveLiftOff := false
countdown := [ 9, 7, 6, 5, 4, 3, 2, 1 ];
for step of countdown {
  // Get ready for take off
  houstonWeHaveLiftOff = false
}
houstonWeHaveLiftOff = true

A simple function:

func lookAtme() {
    // function contents...

    something := "else"
}

About

Express language development and utilities

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published