Coder Social home page Coder Social logo

queso-lang / queso Goto Github PK

View Code? Open in Web Editor NEW
37.0 5.0 1.0 234 KB

๐Ÿง€ A delicious programming language

License: Apache License 2.0

TypeScript 100.00%
programming-language language programming vm functional-programming dynamically-typed strongly-typed rust multi-paradigm-programming interpreted-language

queso's Introduction

๐Ÿง€ queso

license size helpwanted

Checkout develop and feature branches for latest commits!

See the SPECIFICATION for a more concrete definition of the language.

What is queso?

queso is a general-purpose, dynamically-typed, safe, and immutable scripting language with a focus on functional programming. queso builds on the foundation of existing languages with convenience and quality of life features and tweaks.

queso promotes the everything is an expression notion, where constructs such as if, while, as well as blocks, have a value.

queso will be compiled to WebAssembly, and is supposed to be run in any WASM runtime, be it the browser, or a native environment.

Enough with that. Let's see it in practice!

let filterSpicySalsas = salsas -> salsas.>filter(salsa -> salsa.isSpicy);

let salsas = [
  {name: `fresca`, isSpicy: false},
  {name: `roja`, isSpicy: true},
  {name: `habanero`, isSpicy: true},
];

let spicySalsas = salsas |> filterSpicySalsas;

log(
  spicySalsas
    .>map(_.name)
    .>sort()
    .>join(`, `)
)
// prints habanero, roja

First, we define the function filterSpicySalsas. All functions in queso are lambdas.

Inside that function, we see the dot-pipe operator .>. This is because filter is not actually a method, but rather just a function.

Traditionally, we could represent that same operation with: filter(salsas, salsa -> salsa.isSpicy) or with the pipe operator salsas |> x -> filter(x, salsa -> salsa.isSpicy). Thus, the dot-pipe operator .> pipes the left operand into the right operand's first argument.

Then, familiarly, we define a list of objects. The value of the name key is a string (all strings in queso are multiline and interpolated), while isSpicy contains a bool.

We could then do filterSpicySalsas(salsas) to retrieve just the salsas with isSpicy == true, or simply use the cheese pipe operator, like we did above.

Lastly, from the spicy salsas, we want to print out a sorted, comma-separated string of the salsas' names. And so, we map the list of salsas to their names. This could be done like so: spicySalsas.>map(salsa -> salsa.name).

In this case however, we can use special semantics, which come from the fact that operators in queso are functions themselves. Moreover, we can use the placeholder _ keyword to easily create curried functions. For instance, let sum = (a, b) -> a + b can be curried like so: let sumWithFive = sum(5, _). This is equivalent to writing let sumWithFive = b -> sum(5, b).

Thus, .>map(_.name) is equivalent to .>map(salsa -> salsa.name). Notice that this creates a unary function, but we can just pass the operator itself without placeholders if we are epxected to provide a binary function. Take this example of a function which reduces a list:

let reduce = (list, reducer, initial) -> (
  mut accumulator = initial;
  for el in list => (
    accumulator = reducer(accumulator, el)
  );
  accumulator // last value in a block is returned
);

let foo = [1, 2, 3];
// same as foo.>reduce((a, b) -> a + b, 0)
log( foo.>reduce(+, 0) ); // 6

// a more complicated example to show that even the dot-access operator can be used this way:
let traverseKeys = [`buzz`, `yeet`];
let bar = {buzz: {yeet: 123}};
log( traverseKeys.>reduce(., bar) ) // 123
// here, bar is being accessed with the keys specified in traverseKeys
// this is just like writing bar.buzz.yeet

Notice how our reduce function uses parentheses () to denote a block. This is because while other languages use () for grouping expressions to alter the precedence of operations, such as in a - (b + c), queso extends this notion to grouping expressions themselves into lists, just like normal blocks. The last expression in the block will be "returned" as the block's value. The blocks are also full-fledged scopes with the possibility to define and shadow variables.

Coming back to the original example, we use dot-piping to 1. map the salsa objects to just their names, 2. sort the values lexicographically, 3. join them with a comma. We end up with habanero, roja.

More about queso

Let's jump in to a real-world example of a web server in queso:

// userService.queso
import orm => repos;
export let getUserById = id ...-> (
  let users = ...repos.users.getOne({where: {id}});
  {++user, -password}
)

// middleware.queso
export let adminGuard = (ctx, next) ...-> ctx.state.user.role == 'admin' ? ...next() : throw {type: 401}; 

// userRouter.queso
import ./userService => getUserById;
import ./middleware => adminGuard;
import web => createRouter;

export let router = createRouter();

router.GET(`/user/:id`, adminGuard, ctx ...-> (
  [ctx.request.body.id, ctx.state.user] |> [id, user]
    -> id == user.id ? user : ...getUserById(id)
));

So, right off the bat, we get a look at the module system. We define three modules (a file is a module) with their respective exports and imports. All exports are named.

In the first file, we import some theoretical ORM library. Then we define a function to be used later on in our web server to fetch a user by their id. The function is asynchronous, which is indicated by the async ...-> operator. This means you can use the await ... operator inside of the function. Here, we're calling an import from the ORM library, and then awaiting the returned Promise.

Once we have that user, we want to return it, but remove the password property, for security reasons. We create a new object, then spread that original user object (spreading means copying all key:value pairs) with the concatenation ++ operator, and lastly remove the password key using the - operator. Recall that the last expression in a block will be returned, so we don't need to use the return keyword explicitly.

In the second file, we define a small utility function for checking whether the user is authorized to access our endpoint.

Lastly, in our main file, we import the functions from the two other files, as well as a function for creating a router object from some theoretical web server library. We create the router (almosts like instantiation), then define one route with the middleware and the route handler. If the requested user is the current user, we just return the user object which already sits in our ctx. Otherwise, we use our getUserById() function by awaiting it.

Standard Library

Queso provides a flexible system for basic behavior and the ability to swap the standard library with your own implementation that tailors best to your needs. This is because queso does not implement any methods on the built-in primitives, rather it provides a core module for basic functions. For instance, let's say the native function for finding an element in a list was not flexible enough for you:

// this is imported implicitly, but can be disabled entirely
import core => find;
log( [1, 2, 3].>find(_ > 1) ) // prints the element 2, but what if you wanted the index too?

let find = list, predicate ->
  for i in range(list) =>
    list[i] |> el -> predicate(el) ? return [el, i] : continue
  else => [null, -1]

log ( [1, 2, 3].>find(_ > 1) ) // prints [2, 1]

Roadmap

  • Lexer
    • Basic lexer functionality
    • File position tracking
    • Per-file position tracking
    • Lex all tokens (๐Ÿšง)
    • Token stream abstraction (๐Ÿšง)
  • Parser
    • Pratt parsing for expressions
    • Determine operator precedence
    • Research CST, LR parsing
  • Resolver
    • Resolve standard variable declarations
    • Determine the best way to resolve more complex declarations, such as if let
  • Additional passes (TBD)
  • Generator
    • Binaryen for WASM
  • Runtime (TBD)
  • Standard Library (TBD)

License

Apache 2.0

queso's People

Contributors

judehunter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

73nko

queso's Issues

Wrong resolving when dealing with blocks as expressions

Describe the bug

mut a = 5; trace a + {mut b = 3; b}

This should print 8, and yet it prints 10.
It has to do with the resolver assigning the first slot of the stack to a, and the second to b, but then the VM puts a on the first slot, then copies it to the second slot (because it's accessed after trace) and then b on the third slot instead of the second, so the last b actually copies a again.

Expected behavior
Prints 8.

AST visualisation

The problem
Currently, the AST visualisation is string based which is not the best.

The solution
Hook up graphviz or some other graph visualisation library.

Keyword expressions

Add support for the:

  • trace expression
  • return expression
  • throw expression
  • catch binary expression
  • async/await expressions

Fix how positions are stored inside a chunk

Currently, only the lines of the tokens are stored within the chunk (alongside instructions). Some are not even the correct lines.

The chunk should simply store the whole TokenPos.

Fix the infinite `expected an expression` loop

Describe the bug
When the parser gets ahold of a certain unexpected token (an identifier, for example) it start an infinite loop of

[1:0-1] Expected a SEMI after expression      
[1:0-1] Expected an expression

To Reproduce

  1. cargo run
  2. enter $ or thingy or . etc.

Expected behavior
The parser handles the error gracefully

Read from file

  • Implement queso <file> in the CLI and in the interpreter
  • Add filename for trace and error reporting

The idea of the null/nulling operator

The problem
Considering that the language uses the everything is an expression notion, it is sometimes hard not to return anything.
Example of how it can be done currently (in spec)

fn foo(x): {
  trace x; // trace x normally returns x which is not what we want here (for some reason)
  null; // we don't want to return anything (return null)
}

The solution

The ~ null-value may be a prefix operator which simply evaluates the following expression but returns null instead.

  • previous example done with the null operator:
fn foo(x): ~ trace x;
// the return value of trace x (which is x) is discarded

  • inside blocks:
fn foo(x, y): {
  bar(x);
  ~ trace y;
}

which might also be done this way (since a block is an expression):

fn foo(x, y): ~ {
  bar(x);
  trace y;
}

in my opinion this is extremely useful and makes a lot of sense.


  • control flow

for loops - normally they return an array made out of the return values of every iteration

fn foo(x, y): ~ for n in 0..5 -> trace n;
let bar = foo();

This is a useful tool implementation-wise. Here, with proper optimization, the array doesn't have to be created at all. That would be easy to implement even for examples such as:

fn foo(x, y): for n in 0..5 -> trace n;
let bar = ~ foo();

if..else if..else

fn foo(x, y):
  ~ if x -> trace x
  else if y -> trace y;

This simply returns nothing, no need for this:

fn foo(x, y):
  if x -> ~ trace x
  else if y -> ~ trace y;

Alternatives

  • Rust's semi or no-semi syntax
  • Use the return keyword always instead of returning the last thing in a block. This however is not really in the spirit of the language.

Add a --verbose flag

The problem
Currently, the interpreter spits out the bytecode instructions, which is needed for debugging, but is not disableable at this time, which creates a mess, sometimes.

The solution
Parse cli arguments and turn on or off debugging/verbose printing for certain parts of the interpreter.
Something like:

queso --verbose
queso --verbose=[toks,ast,instrs]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.