Code Monkey home page Code Monkey logo

charly's Introduction

Build Status Version License

Charly


Warning: This project is no longer being actively maintained. You can find the new project at KCreate/charly-vm


The Charly programming language

Charly is a dynamic, weakly-typed multi-paradigm programming language. This repository contains a Crystal implementation of the language.

Motivation and Goals

I've always been interested in the development and design of new programming languages. This is my first try at writing an actual language. My primary goal was to learn how to teach a machine what my language looks like and what it should do. Based on the experiences I've made while working on Charly, I now feel comfortable to experiment with lower-level technology such as virtual machines or compilers.

This implementation uses a tree-based execution model, which is (as opposed to a bytecode interpreter) rather trivial to implement. Given that I never took any classes or read any books about this topic, it was the easiest way for me to put up a working language. Charly might well switch to a bytecode-interpreter in the future, but only once I've gathered enough experience to feel comfortable with writing a virtual-machine for it.

If you're interested in writing your own programming language, I've compiled a list of resources you may find useful.

Syntax and language guide

Visit the website for an introduction to the language.

Installation

  1. Install Crystal
  2. Clone this repo with git clone https://github.com/charly-lang/charly
  3. Run install.sh

You will be prompted for your admin password (used to copy to /usr/local/bin).

After that you need to set the CHARLYDIR environment variable. Just add the following line to your .bashrc, .zshrc, etc. Replace the path with the path to the Charly source code (e.g The path to the git repo).

export CHARLYDIR=~/GitHub/charly-lang/charly

You can also build the interpreter via the following command:

$ mkdir bin
$ crystal build src/charly.cr --release -o bin/charly

This will place the executable in the bin folder.

OS Support

I'm developing on macOS 10.12 so it should work without any problems on that. The CI Build runs on Ubuntu 12.04.5 LTS.

CLI options

$ charly -v
Charly 0.3.0 [bb5b857] (15. March 2017)
$ charly -h
Usage: charly [filename] [flags] [arguments]
    -f FLAG, --flag FLAG             Set a flag
    -h, --help                       Print this help message
    -v, --version                    Prints the version number
    --license                        Prints the license

Flags:
    ast                              Display the AST of the userfile
    dotdump                          Dump dot language displaying the AST
    tokens                           Display tokens of the userfile
    lint                             Don't execute after parsing (linting)

53 internal methods are loaded

Using the built-in REPL

$ charly repl
> 2 + 2
4
> "test"
test
> $ * 4
testtesttesttest
> func increment(a) { a + 1 }
Function
> increment(25)
26
> print("hello world")
hello world
null
> .exit

If you need to pass arguments or flags to a REPL session you can do so via the repl command

$ charly repl these are all arguments
> ARGV
[these, are, all, arguments]

charly's People

Contributors

hugoabonizio avatar kcreate 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

charly's Issues

Make the primitive types classes

When doing a member expression or index expression on a non-object or non-array, the interpreter should look for a function on classes defined in primitives.charly.

This could work like this:


class Numeric {
  func times(callback) {
    let i = 0;

    # self automatically set to the number
    # can't be overwritten (see issue #31)
    while (i < self) {
        callback(i);
        i = i + 1;
    };
  };
};

"test".reverse(); # "tset"
25.times(func(i) {}); # Just like in ruby

Every class function or object function will get a pointer called self that will always point to the current object the function runs in. In this case it will be pointed to a primitive value. The interpreter redirects these calls at runtime.

More Array & String methods

  • split(string, haystack) - Split a string into different pieces
    • If the argument is an empty string, return an array containing each char
  • delete(array, arg) - Returns an array where all values equal to arg are removed
  • clone(array) - Returns a new array containing copies of all values inside array
  • flatten(array) - Recursively flatten an array
  • index_of(array, value) - Returns the index of the first occurrence of value. Returns -1 if not found
  • join(separator) - Joins the elements of an array together

Eval function scope

Should the eval function be run inside the same scope as the calling stack?

Scoping bug when binding redirected primitive methods

Reduced:

let Box = {
  let children = ["test", "what"];
  let iterate = children.each;

  func length() {
    2;
  };

  func __member(index) {
    children[index];
  };
};

Box.children.push(1);
Box.children.push(2);
Box.children.push(3);

Box.iterate(func(e) {
  print(e);
});

Output:

โฏ charly test/debug.charly
test
what

Bitwise operators

Operator Usage
Bitwise AND a & b
Bitwise OR a
Bitwise XOR a ^ b
Bitwise NOT ~ a
Left shift a << b
Sign-propagating right shift a >> b
Zero-fill right shift a >>> b

Bracket notation for member expressions

Currently, array index member expressions are handled via a CallExpression node. This makes for weird syntax that looks like this:

# Old
let myArray = [1, 2, 3];
myArray(1); # 2

# Proposal
let myArray = [1, 2, 3];
myArray[1]; # 2

Under the hood this would generate a MemberExpression node. An object can specifiy a method called __member__ that will receive all calls via this member expressions. The same goes for the assignment operator combined with a MemberExpression

class List {
    func __member(index) {
        # do stuff
    };

    func __member__save(index, value) {}
};

let myList = new(List);
myList[1]; # Executes the `__member` method
myList[1] = 2; # Executes the `__member__save` method
myList[1, 2] = 3; # Execute the `__member_save` method and passed the `2` as the second argument

This should kinda mirror the behavior of ruby.

This Issue depends on #41

Class Inheritance, Composition and Extension

This builds on top of this issue.
Syntax for composing different classes together to form a new one is as follows:

class Person {};
class SuperPowers {};
class TheFlash(Person, SuperPowers) {};

This just executes Person, SuperPower and TheFlash (in this order) in a shared stack.

To extend a class you write

class Person {};
class Person(Person) {};

Note that you have to be in the same scope as where the class was defined, as class definitions are block-scoped.

Rewrite some Lexing rules

Current:
2-2 - :NUM :NUM

Should be:
2-2 - :NUM :MINUS :NUM

Options:

  • Maybe parse the minus operator separately
  • Also parse the :NOTEQ operator as a bool-inverter

const keyword

Initialized variables via const can't be changed.

const a = 25;
a = 30; # This doesn't work

const b; # This doesn't work, missing initializer

const c = 25;
func asdf() {
    const c = 30; # This does work
};

Classes & Objects

  • Objects are just stacks hidden inside a variable
  • Implement a kind of locking feature in stack.rb that prevents new variables from being initialized inside the object's stack
  • new(Class) just creates a stack, executes the corresponding initialize function and locks the stack
  • Outside objects can then access these properties via a member expression
    • object.property
    • Member expression should be chainable

Proposed Syntax:

class Math {
    let PI;

    func initialize() {
        PI = 3.14;
    };

    func abs(value) {
        # Return the abs of value
    };
};

let Math = new Math;
myVar.PI # => 3.14
myVar.abs(-5); # => 5

Todo List

Optimize performance

  • Parser
    • AST construction can take 40 - 300 miliseconds
  • Lexer
    • Performance is even worse than the parser
    • May take more than a second
    • Don't depend on Regular Expressions

Add += etc. operators

This could always be rewritten like the following:
target op= value becomes target = target op value

instanceof function

Returns the name of the class as a string

class Person {};

let leonard = new(Person);
print(instanceof(leonard)); # "Person"

Require a folder

If the path passed to require points to a directory, the file called main.charly inside that directory should be included.

Access all arguments via __arguments__

Variable automatically inserted into the function stack just before it is executed.
This would allow for optional parameters.
The function print could also print multiple things instead of just one.

ASTNode print method

Should return a pretty printed version of the source-code representing the node.

Postfix operators

Expressions like this should be possible

1++; # 2

# a == 2
a++; # 3
(a)++; # 4
# a == 4

A postfix operator can be placed after every Term. If the left side as an identifier the result of the postfix expression will be assigned to the identifier in the current scope.

Remove unneeded libraries from prelude.charly

The prelude should only require the absolute minimum of libraries someone could ever need.
If a developer now wants to work with methods from math.charly he should include it himself.

Event bubbling system

This could be used for a couple of things

  • return explicitly return a value
  • break break out of a while loop
  • redo stop execution of the current block and restart at the beginning of the same block
  • throw, try/catch catch exceptions thrown by the programmer
  • exit exit from the current program (should bubble all the way up to the cli)

Different call expression syntax

Proposed new syntax to call functions

func increment(number) {
  number + 1;
};

# Current syntax:
increment(25); # => 26

# Proposed syntax:
(increment 25)   # => 26
(increment)      # => fail (missing argument)
increment        # => FunctionLiteral, won't be executed

Unit-testing library

Implement some functions to make testing specific parts of a program easier.

How the API should look:

let UnitTest = require("unit-test");
let describe = UnitTest("Math", "test");

# Testing
describe("Arithmetic operations", func(it) {
    it("Should add two numbers", func(assert) {
        assert(2 + 2, 4);
    });
});

describe("Comparison operations", func(it) {
    it("Should compare two numbers", func(assert) {
        assert(2 == 2, true);
    });
});

describe("Failing tests", func(it) {
    it("fails", func(assert) {
        assert(2 == 10, true);
    });
});

# Showing the result
print(UnitTest("Math", "result"));

This should print something like the following:

Math Suite
  Arithmetic operations          1 of 1 tests passed!
  Comparison operations          1 of 1 tests passed!
  Failing tests                  0 of 1 tests passed!

The lines 1 of 1 tests passed! are replaced by the current name of the test that is being run.
We use the carriage return char to implement the rollback and redraw on each finished test.

The results should always line up
Passed suites should be colored green.
Failed suites should be colored red.

Parsing bug with nested call expressions

The following code:

(print)("test");

Will produce this AST:

#: Program - BT:  ms
โ””โ•ด#: Block - BT: 0 ms
โ”‚ โ””โ•ด#: Statement - BT: 0 ms
โ”‚ โ”‚ โ”œโ•ด#: Expression - BT: 0 ms
โ”‚ โ”‚ โ”‚ โ”œโ•ด#: CallExpressionNode - BT: 0 ms
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ•ด#: LeftParenLiteral - (
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ•ด#: LeftParenLiteral - (
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ•ด#: ExpressionList - BT: 0 ms
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ•ด#: StringLiteral - test
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ•ด#: RightParenLiteral - )
โ”‚ โ”‚ โ”‚ โ”œโ•ด#: IdentifierLiteral - print
โ”‚ โ”‚ โ”‚ โ”œโ•ด#: RightParenLiteral - )
------

Programmatic Interface to the Interpreter

Allow passing a string to the interpreter-fascade instead of a filename. The CLI should handle reading the file, not the interpreter.

This could be useful for the following kind of things

  • REPL
  • Evaluation of Charly code by non-charly code
  • Executing charly code inside Charly itself.

Math functions

Implement the following functions inside math.charly

  • cos
  • cosh
  • acos
  • acosh
  • sin
  • sinh
  • asin
  • asinh
  • tan
  • tanh
  • atan
  • atanh
  • cbrt (cubic root)
  • sqrt
  • PI
  • abs
  • ceil
  • floor

Container

Implement container syntax

container MyNewContainer {
    let myName = "Leonard";
};

print(MyNewContainer.myName); # "Leonard"

Container just act like immediately initialized class expressions. There will be no constructor inside container.

Invalid self pointer in redirected methods

let string = "123";
let real = "";

string.each(func(char1) {
  real = real + char1;
  string.each(func(char2) {
    real = real + char2;
  });
});

print("Expected: 112321233123");
print("Got: " + real);
Expected: 112321233123
Got: 112323

Global value called ARGV

Should be the same in every file.
All arguments passed to the CLI after -- should be passed to the program.
ARGV will be an Array containing Strings.

Function to print AST at run-time

The function should receive only a callback.
The AST of the block is then printed to the console via a puts call.
The depth property limits the depth of the printed AST.

Usage:

dump_ast(func() {
    # Do stuff here
});

Array & String methods should be the same

Functions like length should work for both Arrays and Strings. These functions should also work with Strings.

  • length
  • each - Callback receives a char and an index
  • map - Callback receives a char and an index
  • filter - Callback receives a char and an index
  • first - Returns the first char in the string
  • last - Returns the last char in the string
  • reverse - Return a string in reversed order
  • empty - Returns true if the string is empty

Real modules

Modules should, instead of sharing a common global scope, be able to define what values they export via the injected variable export. The contents of this variable will be the result of the load or require call.

Example:

awesome-library/main.charly

class MyAwesomeClass {
    # code
};

export = MyAwesomeClass;

# More code follows

main.charly

let myClass = require("awesome-library");
print(myClass); # MyAwesomeClass

IO Functions

Abstract

The io module should contain methods to interact with the file system at a pretty low level.
You should be able to open streams to files, sockets etc.

The fs module will contain high-level bindings to read and write to files, run and to receive network requests.

Each file will be a charly object containing an integer and possibly a filename. The interpreter will internally keep a mapping table between these numbers and the actual resources on the system. That way we don't have to create new primitive types just to represent system resources.

File access should be async by default (just wrap native crystal methods). (is this even possible?)

Code

const fs = require("fs")
const net = require("net")

# Open and write to a file, closing after the block has run
fs.open("./myfile.txt", ->(file) {
  file.write("hello world")
  file.write("\n")
  file.write("what are you doing")
})

# Read a file
fs.read("./myfile.txt", ->(content) {
  print(content)
})

# Read a file without callback
fs.read("./myfile.txt") # => hello world\nwhat are you doing

# Open a HTTP server on localhost at port 3000
net.create_server("localhost", 3000, ->(req, res) {
  print("Request for: " + req.path)
  res.status(200).send("You are talking to charly!")
})

net module

The actual definition of the net module should be included in another PR. The above example is just a basic example of how something like this could work. It should be a node.js-like wrapper around the native crystal methods.

Show parsing error in CLI

When the Parser is inside the last known production of a node, the first failing term-assumption should throw an error. Parsing should be immediately stopped and a nice little error message should show up.

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.