TypeScript with a Borrow Checker. Multi-threaded, Tiny binaries. No GC. Easy to write.

Overview


 

Rust inspired Borrow Checker, TypeScript inspired Syntax

Statically Compiled Binary

Multi Threaded & Concurrent

Memory Safe Without Garbage Collection


Please contribute your thoughts to the design of this language specification!

Hello World - Skip to Language Design

import console from '@std/console'

function main() {
  const text = 'Hello World' // Dynamic string
  console.log(read text) // Handing out a "read" borrow
}

CLI Usage (expected)

The CLI will include a compiler, test runner, formater, linter, documentation generation and module management.

bsc --os linux --arch arm64 -o main main.bs

Table of Contents

Introduction

This repository describes the specification for a language based on TypeScript that implements a Rust-like borrow checker.

By writing our code in a way that gives the compiler hints as to how we are using our variables; we can have reliable, crazy fast multi-threaded applications.

The objective of this is to have a language that:

  • Is easy to understand
  • Has a quick time-to-productive, competitive with languages like Go
  • Encourages good software design with a focus on maintainability
  • Produces the smallest binaries possible
  • Supports and encourages multi-threading and concurrency

Why not Rust?

The immediate question is "why not just use Rust?".

Rust is a fantastic language but often you will hear engineers say that it's "too difficult", "I am not smart enough to learn Rust", or "the language takes too long to become productive in".

It's my opinion that this perceived difficulty comes from the granularity of the data types, symbol overload, syntax format and not because the borrow checker is hard to understand.

While the borrow checker certainly has a learning curve, the use of simplified descriptive operators combined with opinionated built-in data types allows the concept to be more readably described.

It's the goal of this project is to provide a language that, at the cost of a little performance, is quick to learn and become productive in while also empowering engineers to fearlessly write crazy fast software.

Borrow Checker tl:dr

Feel free to skip this if you already understand the borrow checker.

Rust uses a compiler driven feature, kind of like a linter, that tracks the "ownership" of variables in a program.

A variable is owned by the scope it was declared in. The owner can then "move" the value to another scope or lend the value out for "reading" or "mutation".

A variable can only have one owner and the owner can only lend out either one "mutable" borrow, or many "readable" borrows.

This allows the compiler to know when a variable is at risk of a data race (e.g. multiple scopes writing to it). It also allows the compiler to know when to make memory allocations and a memory deallocations at compile time.

This results in producing a binary that does not need a garbage collector and a ships a negligible runtime.

For example; we have three functions declared:

// Asks for read only access to a string
const readLog = (read value: string) => console.log(read value)

// Asks for write access to a string
const writeLog = (write value: string) => log.push('bar')

// Asks for ownership transferal of a string
const moveLog = (move value: string) => console.log(read value)

If we declare a variable we can then give it to one of these functions to complete an operation.

In the following example we create a variable and hand out one read borrow to the readLog function.

let foo = 'foo'
readLog(read foo)

The read borrow given to readLog is temporary and is only present when that function is executing.

It's helpful to imagine a sort of counter state tracking the borrows for foo.

Owner top level
reads 0
writes 0

When given to readLog, the number of reads ticks up by one while the function executes, then ticks down when the function completes.

If we then hand a write borrow to writeLog, we will have one write borrow. Remember, only 1 write borrow or infinite read borrows.

let foo = 'foo'

readLog(read foo)
writeLog(write foo)

In the above we tick up one read, tick down one read, tick up one write, tick down one write.

We can also change the owner of foo to another scope by using move.

let foo = 'foo'
moveLog(move foo)

This now means that anything on the top level scope can no longer use foo as its owner is moveLog. This also means that the foo variable will be dropped from memory when the moveLog function completes.

So you can imagine the following code would fail

let foo = 'foo'

moveLog(move foo)
readLog(read foo) // Error "tried to use 'foo' when you don't own 'foo'"

Tracking a variable's lifecycle like this means no need for a garbage collector. Rust's idea is pretty clever, nice work Mozilla!

Great short and simple video by YouCodeThings explaining the concept

Language Design

BorrowScript is a derivative subset of the existing TypeScript language with the addition of keywords and concepts to enable the use of borrow checking.

BorrowScript is not compatible with existing TypeScript code or libraries. BorrowScript aims to co-exist as an independent language that inherits the fantastic type system from TypeScript and applies the borrow checker concept from Rust.

For the most part, you can look at the existing TypeScript language specification and apply the borrow checker additions to it.

Builtin Types

BorrowScript contains opinionated builtin types. Where Rust would use something like:

let myString: String = String::from("Hello World");

BorrowScript uses:

const myString: string = "Hello World"

All types are references to objects and can be mutated or reassigned if permitted. The types are as follows:

= [] const m: Map = new Map() ">
const s: string = ""
const n: number = 0
const b: boolean = true
const z: null = null
const a: Array<string> = [] 
const m: Map<string, string> = new Map()

Nullable types are described as. Remember that null is an object and not a null pointer:

let foo: string | null = 'foo'
let bar: string | null = null

Ownership Operators

Following after Rust, variables are owned by the scope they are declared in. Ownership can be loaned out to another scope as read or write. There can either be be one scope with write access or unlimited scopes with read access.

It's practical to visualize each variable with it's own ownership state table.

Variable foo
Owner main()
reads 0
writes 0
function main() {
  const foo = "Hello World"

  readFoo(read foo) // reads++
  // reads--

  writeFoo(write foo) // writes++
  // writes--
}

Ownership privilege is retroactive where an owner has read/write. A scope with write has read/write. A scope with read has read only. A scope can only lend out to another scope a permission equal or lower than the current held permission.

Declaration location

Ownership permissions are defined in function or method parameters.

function addWorld(write hello: string): void {
  hello.push(' World')
}

From there the caller can supply a variable to this function, providing the scope has the correct ownership permissions.

function main() {
  let hello = "Hello" // main() is owner with read/write
  const world = "World" // main() is owner with read

  addWorld(write hello) // main() gives a write borrow to addWorld() 
  addWorld(hello) // "write" can be inferred therefore omitted 
}

read

The read operator prevents mutation deeply for all types, including arrays and maps. The compiler will fail to compile the application if a mutation is attempted when read access is provided.

function readFoo(read foo: string) {
  console.log(foo) // "foo"
}

Rust equivalent

fn read_foo(foo: &String) {
  print!("{}", foo);
}

write

The write operator allows mutation deeply for all types, including arrays and maps. Only one write can be given out at any time. There can be no read borrows while there is a write.

function writeFoo(read foo: string) {
  foo.push('bar')
  console.log(foo) // "foobar"
}

Rust equivalent

fn write_foo(foo: &mut String) {
  foo.push_str("bar");  // Static string declaration
  print!("{}", foo);
}

move

The move operator allows for transferal of a variable's ownership from one scope to another. This literally removes a variable from the current scope.

Rust has two variations of moving a value into a function. Moving a value as mutable or immutable.

It's practical to think of the parameter as a variable declaration let or let mut (let or const)

fn move_foo(foo: String) { // let foo
  print!("{}", foo);
}

fn move_foo(mut foo: String) { // let mut foo
  foo.push(String::from("bar")); // Dynamic string declaration
  print!("{}", foo);
}

BorrowScript expresses this using the following syntax (still in discussion):

function moveFoo(foo: string) { // if omitted, the compiler will assume an immutable move
  console.log(foo)
}

function moveFoo(move<const> foo: string) { // default if omitted 
  console.log(foo)
}

function moveFoo(move<let> foo: string) {
  foo.push('bar')
  console.log(foo)
}

copy

This is syntax sugar specifically for BorrowScript. It invokes the .copy() method on an object. The use case for this is to simplify the transferal of types through "ownership gates" which we will discuss further below

const foo = "foo"
let bar = copy foo // same as foo.copy()
bar.push('bar')

Ownership Gates

In Rust, callback functions do not automatically have access to the variables in their outer scope. In order to gain access to a variable from within a nested scope (callback function), you must explicitly import variables from the parent scope.

Here is a simple example of this in TypeScript

const message = 'Hello World'

setTimeout(() => {
  console.log(message)
})

In Rust, you have to move a value into the callback scope before using it.

let message = String::from("Hello World");

set_timeout(&(move || {
  print!("{}", message);
}));

In BorrowScript we describe imports from the parent scope of a callback using ownership gates which are declared as square brackets after the function parameters:

{ // "move" can be omitted console.log(message) }) ">
const message = "Hello World"

setTimeout(()[move message] => { // "move" can be omitted
  console.log(message)
})

The complete syntax looks like:

function name<T>(params)[gate]: void { /* code */ }

You can also copy a value into a child scope using ownership gates. Using copy will create a shadow variable with the same name within the child scope.

setTimeout(()[copy message] => { /* code */ })

Which is equivalent to:

{ const message = messageCopy console.log(message) }) ">
const message = "Hello World"

const messageCopy = copy message
setTimeout(()[move messageCopy] => {
  const message = messageCopy
  console.log(message)
})

Lifetimes

Lifetimes are parameters described using the lifeof type operator within a generic definition.

This syntax needs a bit of work probably

function longestNumber<lifeof A>(A<read> x: number, A<read> y: number): A<number> {
  // return the longer number
}

What this essentially tells the compiler is to only work when both the x and y variables share the same lifespan. If one drops out of scope before the other (clearing it from memory) then the life times of the variables are not the same.

Threads

Threads will be available from the standard library via:

import { Thread } from '@std/threads'

function main() {
  const thread1 = new Thread()

  thread1.exec(() => {
    console.log('Some work')
  })

  thread1.waitForIdle()
}

Concurrency, async & await

Concurrency is managed by Promises (which are basically analogous to Rust's Futures). Control flow is managed using async await. Like JavaScript, functions are executed in an event loop. Unlike JavaScript, these functions may execute on any one of the allocated threads (like Go).

This is still in design

I am experimenting with the idea of having the queue its own object for cases where you want the main thread dedicated to something like UI or IO and the rest of the threads dedicated to work.

import { Thread } from '@std/thread'
import { Queue } from '@std/queue'

async function main() {
  const thread1 = new Thread()
  const thread2 = new Thread()

  const queue = new Queue([thread1, thread2])

  queue.task(() => console.log('Hello'))
  queue.task(() => console.log('World'))

  await queue.allSettled()
}

Where potentially we use a decorator to use an all-thread concurrency setup

import { DefaultQueue, queue } from '@std/queue'

@DefaultQueue()
async function main() {
  queue.task(() => console.log('Hello'))
  queue.task(() => console.log('World'))

  await queue.allSettled()
}

Mutex

To manage variables that need to be written to from multiple threads we use a Mutex which holds a state and allows us to lock/unlock access to it, ensuring no one can get the value when it's being used.

import { Mutex } from '@std/sync'

async function main() {
  const counterRef = new Mutex(0)

  setInterval(()[copy counterRef] => {
    let counter = counterRef.unlock()
    counter.increment()
  }, 1000)
}

The Rust equivalent same thing in Rust would look like:

fn main() {
  let counterRef = Arc::new(Mutex::new(0));
  let counterRef1 = counterRef.clone();

  set_timeout(&(move || {
    let mut counter = counterRef1.lock().unwrap();
    counter* = counter* + 1;
  }), 1000);
}

Error handling

At this stage, errors will be return values from tuples

const [ value, error ] = parseInt("Not a number")
if (error != null) {
  // handle
}

Audiences

BorrowScript targets the engineering of high level application development, namely:

  • Graphical applications
    • Web applications via web assembly
    • Native mobile applications
    • Native desktop applications
  • Web servers
  • CLI tools
  • Embedded applications

Justification

GUI Applications

Graphical applications written targeting web, desktop and mobile have seen a lot of innovation and attention. From technologies like React/React Native, Electron/Cordova to Flutter, there is an interest in creating client applications that are portable between platforms, are performant and memory efficient.

While BorrowScript is simply a language and does not describe a framework that manages bindings to the various platform UI APIs - it does allow for a simple, familiar language which addresses the concerns that graphical applications have, making it a great candidate for such a use case.

While its primary focus is targeting the web platform via web assembly, there is no reason we would not be able to see React-Native like platform producing native desktop and native mobile platforms.

Multi threading

Effective use of multi-threading is critical for making graphical applications feel responsive.

As an example, web applications are often criticized for their lack of performance when compared to their native counterparts. Native applications however make maximal use of threads and as a result have interfaces that seldom block.

Including a borrow checker into a familiar language used in web development means that concurrency across threads can be used without fear of data races. This will also allow graphical applications to be used on lower end devices as their resources can be utilized more effectively.

Small Binary

As web assembly requires languages to ship their runtime, it's important for languages to ship as little runtime code as possible.

A borrow checker allows a compiler to compile the code without the need for a garbage collection implementation or a substantial runtime.

This is valuable for web applications however, while not strictly required, efficient small binaries are also appreciated when distributing native applications and web server applications.

Web Servers:

Web servers require high and consistent IO performance, maintainable code bases and simple distribution.

There are many languages to choose from in this space making the argument for BorrowScript less compelling on the server side.

There are a few ways that BorrowScript can be competitive in this context, however.

Performance (speculation)

Rust performance is often compared to C++ and out performs languages like Go and Java.

While it's too early to guarantee performance, we know that TSCB is a slightly higher level Rust, where the types are replaced with built-ins.

I would like to see its performance sit between Rust and Go.

Go-like concurrency with Rust-like safety

Having an application server written in a high level, familiar and maintainable language that promises the concurrently of a language like Go while also ensuring you cannot write data races might be compelling for back-end application developers.

Small, statically linked binaries are good for containers

While this is an implementation detail of the compiler, entirely self contained binaries that embed their dependencies (unless explicitly specified) are fantastic from a distribution and security perspective.

It allows engineers to distribute containerized applications based on scratch images.

No GC is good for performance consistency

Languages without garbage collection have consistent performance as they avoid locks originating from garbage collection sweeps.

This is especially noticeable in applications with lots of activity - such as chat servers.

Good candidate for PaaS, FaaS

Lastly; small, self contained, memory and performance optimized binaries make for great candidates in PaaS or FaaS contexts (Lambda, App Engine, Heroku, etc).

Language Design

BorrowScript is a derivative subset of the existing TypeScript language with the addition of keywords and concepts to enable the use of borrow checking.

BorrowScript is not compatible with existing TypeScript code or libraries. BorrowScript aims to co-exist as an independent language that inherits the fantastic type system from TypeScript and applies the borrow checker concept from Rust.

For the most part, you can look at the existing TypeScript language specification and apply the borrow checker additions to it.

Hello World

import console from '@std/console'

function main() {
  const text: string = 'Hello World'
  console.log(read text)
}

We create an immutable reference to a string object. We then give read-only access to the console.log method, where the value is consumed.

Notes

  • An application begins execution at the main function.
  • When main exits, it returns a status code 0 as default
  • Imports starting with @std/* target the standard library
  • Using the read operator, a variable is lent for reading

Lambdas, Type Inference and Shorthand

In the example code I am using the long hand version of everything. I am including type signatures as well as function definitions.

The language will support lambda functions and TypeScript type inference so it won't be necessary to write the complete type signatures for everything.

Experimental Syntax

Shorthand ownership operators

I am exploring the idea of using shorthand ownership operators and sensible defaults namely;

move is the default if omitted. write will also accept w, read or r, copy or c.

Both would work:

function foo(read bar: number) { }
function foo(r bar: number) { }

Omitting matching ownership operators

I am exploring the idea of omitting ownership operators on method parameters when called if the incoming value has matching ownership parameters.

For example the signature for console.log describes that it accepts values provided with read access that contain the toString() method:

interface Stringable {
  read toString(): string
}

console.log(read ...any Stringable[])

All built-in types have a toString method that only requires read access to use.

const foo = 1337

console.log(read foo)
console.log(foo) // perhaps this will be fine

Consuming external libraries (Rust, C++)

I would like to introduce a means to consume external Rust and C++ libraries. The how of this is still in discussion.

TODO

More Details

Below are pages with additional information on the language specification.

Success Challenges / Quest Log

We know this is successful when these example programs are completed and their objectives are met.

Note that these examples may change as the specification and standard library specification evolves

Examples

Simple HTTP Server

HTTP server that is multi-threaded and the handler function is scheduled on one of the available threads.

The API for the http library has not been finalized, this is an aproximation

import { Server } from '@std/http'

async function main() {
  const server = new Server()

  server.get('/', (req, res) => {
    res.setBodyString('Hello World')
    res.send()
  })

  await server.listen([127, 0, 0, 1], 3000)
}

HTTP Server with State

HTTP server that increments a counter stored in a mutex. On each request the counter value will be incremented and the value sent back in the response. The HTTP server is multi-threaded and the handler function is scheduled on one of the available threads.

This is dependant on the design decision describing how ownership of values is passed into nested closures and not final

import { Server } from '@std/http'
import { Mutex } from '@std/sync'

async function main() {
  const server = new Server()
  const counterRef = new Mutex(0)

  server.get('/', (req, res)[copy counterRef] => {
    let value = counterRef.lock()
    value.increment()
    res.send()
  })

  await server.listen([127, 0, 0, 1], 3000)
}
Comments
  • Semi colons;

    Semi colons;

    Three Two One ... Fight? Does BorrowScript use trailing semicolons? I'm in camp yes but don't particularly care either way as long as the weird current edge cases in js are avoided.

    opened by Isaac-Leonard 12
  • Why is `move` the default?

    Why is `move` the default?

    The document says:

    move is the default if omitted

    I am wondering why?

    I would have assumed read would be the default, as this would encourage functional programming - if somebody wants to create code with mutations, they would use copy, write or move too specify "how".

    That is, read appears to be the one that stands out from the other three, all of which are about how to deal with changing values - which, in my opinion, would seem to make it the logical candidate for a default.

    I'm totally open to being wrong on this. ๐Ÿ˜„

    I approach this from the perspective of a Typescript developer with no Rust experience.

    Coming from Typescript, I don't normally have to think of memory management, and honestly never expected I would care or even like the Rust memory model, so let me explain:

    I view these annotations as more than just memory management - in fact, it's nice that they work for compile-time memory management, but what really interests me is the idea of making it explicit what a function can be expected to do with your variable.

    It surprises me how much I like this idea, and how easy it is to understand - it never exactly clicked for me in Rust, where for some reason this seemed burdensome to me. I'm not sure why. I might not have been receptive at the time and maybe I owe Rust a second look. But this looks great in the context of Typescript. ๐Ÿ‘

    I would probably even have found this meaningful in a language that only enforces these rules at compile-time and doesn't need them for memory management - such as Typescript.

    Anyhow, if there's a good reason why move is the default, maybe this should be explained in that section of the spec?

    opened by mindplay-dk 10
  • Syntax compatibility

    Syntax compatibility

    As I mentioned in #13, it might be beneficial in gaining users by having tooling that already works.

    Related to that, what about using compatible syntax?

    F.e. instead if

    function readText(read value: string): void {
      console.log(value)
    }
    

    it could be

    // Using currently-invalid syntax that TypeScript knows how to parse, and might be compatible with upcoming EcmaScript specs
    // @ts-ignore function decorator
    @read('value')
    function readText(value: string): void {
      console.log(value)
    }
    
    //
    
    // Using TypeScript comment directive syntax:
    // @read value
    function readText(value: string): void {
      console.log(value
    }
    
    // Or maybe even
    function readText(/*@read*/ value: string): void {
      console.log(value)
    }
    

    This will help to

    offer the ergonomics of the TypeScript language.

    because if someone can open a BS project in VS Codes and it just works out of the box today. F.e. hover and see type tooltip, or run Go To Definition shortcut to navigate code, etc, that'll be really helpful for developer ergonomics.

    Those benefits can outweigh the slightly more verbosity. Tapping into existing tooling support would make this more highly adoptable out of the box right now.

    opened by trusktr 6
  • How to handle borrow checking in data races caused by callbacks?

    How to handle borrow checking in data races caused by callbacks?

    In the example below we have an http server that runs a callback function when any request is received. The callback function will increment a counter. The callback function can be scheduled on multiple threads and will therefore put the counter at a risk of data races.

    It's important to prevent compilation in this scenario - however this scenario does not violate the borrow checker's rules.

    Assuming the syntax described in this issue to pass variables from an outer scope in then we would pass write access into the handler like:

    let counter: number = 0
    
    server.handle(function(move c: http.Context)[write counter]: void {
      counter.increment()
    
      c.response.setStatus(200)
      c.response.send()
    })
    

    Obviously we would want to use a Mutex to wrap counter, but the compiler needs to catch this and prevent compilation. What should happen here?

    todo opinions wanted 
    opened by alshdavid 6
  • Does this project need a better name?

    Does this project need a better name?

    TypeScriptBC stands for TypeScript (with) Borrow Checker. The name is descriptive, but also misleading as it's not really TypeScript and not compatible with anything in the TypeScript/JavaScript world.

    While the language takes the type system, majority of the language and module syntax from TypeScript - it's quite different when you apply a borrow checker to it.

    I was trying to think of Rust puns. I had the idea of "Oxide", "Oxie" or "OxieLang" and having a cute cartoon Ox as a mascot.

    image Not this image specifically, but similar

    But then I thought that people might call newcomers Oxen and that's kinda weird.

    Honestly, if I were good at naming things, I wouldn't have created a repo called TypeScriptBC ๐Ÿ˜„

    opinions wanted 
    opened by alshdavid 5
  • Prefer message passing techniques  for inter thread communication

    Prefer message passing techniques for inter thread communication

    It might be a good idea to limit. the power of threads to prefer message passing over modifying variables and directly controlling mutexes and semaphores. This would mean the borrow checker would be simpler and likely improve the clarity of code. Currently typescript allows this with web workers and nodes worker threads however these require a lot of boilerplate to make useful. Instead of this would it be possible to instantiate a thread in the currently outlined method but have a way to pass variables in and out of the thread. This could be done with some sort of thread.pass(move var:something):void; method. and some sort of promise based response method that the passing thread can wait on to regain control of the var variable.

    It might also be useful to implement some sort of stream interface on the Thread class so that they can emit values that get acted upon by a callback listener function in another thread.

    opened by Isaac-Leonard 4
  • Assemblyscript

    Assemblyscript

    What would be the key difference between AssemblyScript and this?

    Is it mainly the borrow checker?

    I wonder if AS would make a good jumping-off point for this language? They've already put some years of hard work into the compiler itself, which might could shave considerable time off to get this language off the ground?

    (Or if the AS maintainers might even be open to taking their language in that direction?)

    opened by mindplay-dk 4
  • Null, undefined and nan

    Null, undefined and nan

    How does this project plan to handle and / or implement null, undefined and nan values? Will they remain as they are in currently in typescript or will null and undefined be merged into one value? Will the equality stuff with nan values also be changed or will anywhere a nan may normally be produced simply throw an error?

    todo 
    opened by Isaac-Leonard 4
  • Just sharing a thought

    Just sharing a thought

    Utilizing vscode intellisense and the TypeScript language server is very useful, and here, I propose different ways to explicitly denote mutable borrows and move semantics as an example of what might be more ergonomic for TypeScript developers using a hypothetical TypeScriptBC compiler.

    I don't mind the read or write directives, as they are clear and understandable, but it might be easier to swim downstream with the typescript language instead of against it.

    Any thoughts?

    opened by jtenner 3
  • [proposal] How to deal with passing parameters into child functions from lexical scopes?

    [proposal] How to deal with passing parameters into child functions from lexical scopes?

    Given this example in vanilla TypeScript

    const foo = 'foo'
    
    function bar() {
      console.log(foo)
    }
    

    The bar function has untethered access to its outer scope and thus can access foo. This conflicts with the notion of a borrow checker where foo must be explicitly passed into the bar scope and its permissions described.

    Perhaps adding a seperate set of parameters to denote scope transfer would suffice

    function<T>(...args)[...args]: U { /* code */ }
    

    example:

    const foo = 'foo'
    
    function bar()[read foo]: void {
      console.log(foo)
    }
    
    todo opinions wanted 
    opened by alshdavid 3
  • A better way to handle Errors

    A better way to handle Errors

    I believe is good idea keep try catch block handler and keep both ways to handle errors (inline/try catch).

    Sometime interrupt the code execution and handle in central place is a good idea Old style try catch

    try {
    
      //Error here
      parseInt( "Not a number" )
    
    }
    catch ( error ) {
    
       // handle error
    
    }
    

    New style 1

    Could be a good idea use try before call function. In order not propagate the error to superior scope and not break the code execution.

    const [ value, error ] = try parseInt( "Not a number" )
    
    if ( error != null ) {
    
      // handle error
    
    }
    

    New style 2

    const [ value, error ] = try {  
    
     let x = parseInt( "Not a number" )
    
     return 10 / x //This return is to exit from try block scope, and assign the value variable in the superior scope.
    
    }
    
    if ( error != null ) {
    
      // handle error
    
    }
    
    opened by sirlordt 1
  • Enhanced tab support

    Enhanced tab support

    \t is very easy and quick way of formatting outputs in console. But t is interpreted by console and coder cannot have a control over it.

    A new tab style would be adopted eg \sN or \eN where N a natural number. Any length() function should consider the new tab style and not just 3.

    opened by GHNewbiee 0
  • Parser skip capability during run time us per user request/instruction

    Parser skip capability during run time us per user request/instruction

    Parser skips comments as a build-in feature. Comment style is hardly declared.

    Due to language mixing, there are cases which one language interferes with the features of another language, for example js variables within HTML comment eg <!-- ${var} -->. As js parses from the very beginning it may throw error if var is not declared, etc. Coder should have an opportunity to instruct parser when it meets <!-- --> to skip them like they were js comments. In that case HTML comment style is not violated.

    This would be done by two techniques:

    • Either coder dynamically declares new styles of comments eg ('<!--', '-->').newComment(), ('<!--', '-->').removeComment, or
    • Parser provides general switch functions eg parsenOff(), parserOnso that a coder can write within a bs file parserOff() <!-- ${var} --> parserOn
    opened by GHNewbiee 0
  • Array with free lower index bound

    Array with free lower index bound

    Array should not be 0-indexed by design. Lower bound would be set by user. It will help people who wants to write simple programs for numerical calculations.

    opened by GHNewbiee 0
  • Unsafe code

    Unsafe code

    Something the spec hasn't touched is the notion of unsafe code with manual memory management.

    I think it would also offer a great starting point for the compiler as an unsafe mode would essentially be a statically compiled subset of TypeScript with manual memory management. It would let us try out the viability of the type system where the concepts of the borrow checker could be applied on top of that.

    function main() {
      unsafe {
        const foo: string = "" // allocate a dynamic string on the heap
    
        delete foo // delete allocation
      }
    }
    

    So as an example, the standard library might include this implementation for an Array

    struct ArrayItem<T> {
      value: T
      next: ArrayItem<T | null>
    }
    
    class Array<T> {
      private _items: ArrayItem<T> | null
     
      constructor() {
        this._items = null
      }
    
      destructor() { 
        // traverse linked list and delete links
      }
    
      push(item: T) {
        // create or add to linked list
      }
      
      pop() {}
      shift() {}
      unshift() {}
    }
    
    const list = new Array<string>() // ref to an array
    
    const a = "a" // ref to a dynamic string
    const b = "b" // ref to a dynamic string
    
    list.push(a) // add ref to array
    list.push(b) // add ref to array
    
    delete list
    delete a 
    delete b
    

    Deleting the Array doesn't delete the items contained in the linked list when unsafe.

    When using a borrow checker, the items pushed into the array would have ownership moved to the class. This means the array will be able to clean up all the references it owns - deleting the items and values in the array.

    With unsafe, the array cannot know to delete the values - only the links.

    opened by alshdavid 0
  • Compiler

    Compiler

    Have you decided how you want to build the compiler for BorrowScript? I think at this point, we have a basic specification for the language so some of us interested in the project can start coming up with a test suite of valid and invalid source files for the language, along with discussing and setting up the basic infrastructure for the compiler ( parsing and code generation).

    I think it would be a great idea to have a Discord/Slack for this project where people can join, discuss and work together on making this project a reality.

    opened by argoopjmc 6
Owner
David Alsh
It's in my code
David Alsh
A very lightweight and chatbot with multi language support

A very lightweight and chatbot with multi language support

Artificial Intelligence 6 Jul 25, 2022
๐ŸŒณ Tiny & elegant JavaScript HTTP client based on the browser Fetch API

Huge thanks to for sponsoring me! Ky is a tiny and elegant HTTP client based on the browser Fetch API Ky targets modern browsers and Deno. For older b

Sindre Sorhus 8.5k Jan 2, 2023
A tiny FPS for js13k

A tiny FPS for js13k

Dominic Szablewski 1.4k Dec 26, 2022
Pretty UI to make getting details about PS1 stations easy.

ps1data Pretty interface to navigate through PS1 stations at BITS Pilani Disclaimer Given that I had to do this whole thing in about 18 hours, this ha

BITS-ACM 39 Jun 25, 2022
An easy to use, yet advanced and fully customizable javascript/jQuery paginator plugin

anyPaginator An easy to use, yet advanced and fully customizable Javascript/jQuery paginator plugin. anyPaginator is a spinoff of the anyList project

Arne Morken 2 Feb 17, 2022
BotsApp is an optimized and easy-to-use WhatsApp UserBot written in Node.js

?? BotsApp ?? Your Personal Assisstant, on WhatsApp! BotsApp is an optimized and easy-to-use WhatsApp UserBot written in Node.js. Utilize your persona

BotsApp 5.5k Jan 1, 2023
DisOwen project - Makes it easy and fun to use Discord

DisOwen project - Makes it easy and fun to use Discord. Also first userbot for Discord

Ber4tbey 7 Aug 4, 2022
Online Golang Struct to TypeScript Interface Converter

Golang Struct to TypeScript Interface Use this tool live! https://stirlingmarketinggroup.github.io/go2ts/ This tool converts Go structs to TypeScript

Stirling Marketing Group 42 Dec 17, 2022
Type-Safe Errors for JS & TypeScript

Type-Safe Errors for JS & TypeScript

Gio 1.4k Jan 6, 2023
Shikimori.ts - JavaScript & TypeScript wrapper for shikimori.one

shikimori.ts shikimori.ts - JavaScript & TypeScript wrapper for shikimori.one Features Full TypeScript support Support all platforms Easy to use Table

null 5 Sep 15, 2022
A simple bot for Lingos exercises automation written entirely in Typescript.

Linger The spiritual successor to lingus Stack used Typescript SWC Yarn Nodejs Puppeteer Dotenv UUID Nodemon Instalation Prerequisites: Nodejs: ^16.13

TlenDev 2 May 9, 2022
An implementation of cellular automata in TypeScript

?? cellular-automata An implementation of cellular automata in TypeScript. ?? Usage First of all, clone the repository: $ git clone [email protected]:aro

Faye's Games 2 Feb 7, 2022
Typescript template for a discord bot using discord.js

Discord-Typescript-Template Typescript template for a discord bot using discord.js. Installation Clone the repository git clone https://github.com/Ami

null 2 Oct 17, 2022
ThinkMore Forum frontend build with Next.js, Typescript, Redux, Jest, Sass, MUI.

ThinkMoreForum-Frontend Website Desktop Mobile Front-end technology stack Next.js Redux Typescript MUI Axios Husky React testing-library Jest Eslint,

Alan 151 Dec 10, 2022
Max is a Typescript-based Discord bot with many functionalities

Max is a Typescript-based Discord bot with many functionalities. He is both my learning curve for understanding Discord's API as well as my current passion project.

Jack Levreau 4 May 24, 2022
proxy ๐Ÿฆ„ yxorp is your Web Proxy as a Service (SAAS) Multi-tenant, Multi-Threaded, with Cache & Article Spinner

proxy ?? yxorp is your Web Proxy as a Service (SAAS) Multi-tenant, Multi-Threaded, with Cache & Article Spinner. Batteries are included, Content Spinning and Caching Engine, all housed within a stunning web GUI. A unique high-performance, plug-and-play, multi-threaded website mirror and article spinner

4D/าต.com Dashboards 13 Dec 30, 2022
A tiny cross-platform client for SQLite3, with precompiled binaries as the only third-party dependencies.

Tiny SQLite3 A tiny cross-platform client for SQLite3, with precompiled binaries as the only third-party dependencies. A nice feature about this modul

Fabio Spampinato 19 Oct 27, 2022
A simple lock-and-release eventually-consistent DB to be consumed by multi-threaded applications in node.

worsen - a persistence store for quick prototyping A simple lock-and-release eventually-consistent DB to be consumed by multi-threaded applications in

Aniket Biprojit Chowdhury 2 Oct 1, 2022
Borrow & Return bowl for the restaurants

App Center Sample App for React Native The React Native application in this repository will help you quickly and easily onboard to Visual Studio App C

Frozen Heart 2 Jan 24, 2022
๐Ÿ”ฑ Javascript's God Mode. No VM. No Bytecode. No GC. Just native binaries.

Javascript's God Mode: one language to rule them all. Code everything, everywhere, for everything, in JavaScript. No VM. No Bytecode. No packaging. No

null 3.4k Dec 31, 2022