Language Reference

This page explain the concepts used by the Jome programming language.

This documentation assumes the reader is familiar with javascript.

Jome is similar to JavaScript, but there are a few distinctions that you must be aware of.

  • Execution is different: You execute .jome files using the jome CLI.
  • The syntax is more permissive. You can have a syntax like javascript or something similar to the ruby programming language.
  • You can use a default library. Functions and constants become available using the hashtag. TODO
  • There are heredocs to include code from other languages (html, css, ...) written as xml tags.

Schemas

You can define custom schemas for xml tags.

You define it using the built-in schema, schema.

schema:

  • model: The name of what this schema is describing. This is case insensitive. This way you can write < recipe > if you prefer instead of < Recipe >, but still do new Recipe.

attr:

  • name: Name of the attribute
  • type: Type of the attribute. TODO: Link to a list where it is described all possible types.
  • optional: Whether the attribute is optional

List allows to have children of different model nested. list:

  • name: Name of the list
  • optional: Whether the list is optional

many: Can contain one or many children of the type specified by id.

  • model: The model of the schema of the children allowed

variants: Can be one of any of the nested types. The same as type union.

<?schema model="fruit"
  <variants>
    <include model="apple"> // from a schema defined elsewhere
    <include model="orange"> // from a schema defined elsewhere
  </variants>
?>
<list name="steps" optional>
  <many model="step" />
</list>

has_many: A shorthand for list when all the children are of the same model.

  • name

one:

has_one:

belongs_to:

content:

  • name: The name of the variable to access the content.

You can also use components. They are the same as schema but only exists within it's scope.

<?schema model="Recipe"
  <attr name="name" type="string" />
  <attr name="servings" type="string" optional />
  <has_many name="ingredients" model="ing" optional>
  <has_many name="steps" model="step" optional>
  <component model="ing">
    <attr name="qty" type="string" />
    <attr name="name" type="string" />
  </component>
  <component model="step">
    <content name="text" type="string" />
  </component>
?>

You can then extend the schema by using the keyword schema and adding methods like you would to a class.

The previous xml schema defines the constructor already.

schema Recipe {
  def toHTML
  end
}

Then you can either create an instance with xml or programmatycally.

let recipe = <recipe
  name="Biscuits aux brisures de chocolat"
>
  <ingredients>
    <ing qty="1 t" name="farine" />
    <ing qty="1 t" name="beurre" />
  </ingredients>
  <steps>
    <step>Mélanger @1 avec @2</step>
  </steps>
</recipe>
let recipe = new Recipe({
  name: "Biscuits aux brisures de chocolat"
  ingredients: [
    new Ingredient({qty: "1 t", name: "farine"}),
    new Ingredient({qty: "1 t", name: "beurre"})
  ],
  steps: [
    new Step({text: "Mélanger @1 avec @2"})
  ]
})

The advantage of the xml version is that the editor should allow you to edit it in a beautiful way. It is nicer when just entering data.

The advantage of the programmatycally version is if you need to refer to other code a lot, it's simpler this way.

What happens when you define a schema? How is that compiled to javascript?

A class is created. A constructor is created. The class extends some base class?

If you have used the schema keyword, then it is merged with the schema xml into the class.

A schema is used for validation.

A serializer and deserializer can be specified. By default JSON.

Example testing using xml schema:

<test>
  <msg>Trim should remove spaces before and after.</msg>
  <code>(" foo bar ").trim()</code> // code is of type code
  <expect>foo bar</expect> // expect can be any value
  // or <expect type="string">foo bar</expect>
</test>

Syntax - WIP

The idea is to support a lot of syntaxes so you can choose the one you prefer. Ideally, the editor would show you the code as you like it and save it in the format used by the organisation.

Currently, the only supported syntax is using the end keyword. Later, a colon at the end of a line with indentation would be supported. Curly braces would be supported too.

One thing which can be nice is to alternate between two syntax at every nesting level so it's easier to know where you are.

class Person
  sayHello() {
    console.log 'Hello!'
  }
end

Would be the same as:

class Person {
  def sayHello
    console.log 'Hello!'
  end
}

Function calls

Parentheses are optional for function calls.

let add = (x,y) => x + y
add 10, 5

You can pass arguments for a single object without using curly braces.

let add = ({x, y}) => x + y
add x: 10, y: 5

You can use a colon prefix to insert an entry in the object with the name of the variable as the key.

let name = "Luc"
sayHello :name // same as sayHello({name: name}) or sayHello({name})

You can use a colon prefix with an exclamation mark at the end to make a boolean entry equal true.

doWrite "Some text", to: "./someFile.txt", :force! // same as force: true

You can use do ... end syntax at the end of a function call to pass a function as a parameter. The syntax is similar to ruby. You pass arguments between vertical bars.

import * from 'jome-lib'
[1,2,3,4,5].#each do |i|
  #log i
end

Piping

You can write the input to a function call to the left by using the operators :., |> or .#

.# refer to built-in functions which are always available. The other two will use local functions or global ones defined in the file config.jome.

The difference between :. and |> is the precedence. :. has the highest precedence same as .

import * from 'jome-lib'
obj.#keys.#filter(k => k[0] === 'p').#each do |k|
  // ...
end

"john":.upcase:.reverse // upcase and reverse here are local or global functions

["1","2","3"]:.reverse.join(" ") |> console.log

"john" |> String.upcase |> String.reverse

Piping allows to write in a functional way more easily.

It's up to the user to choose between using .# or :.. By default in Jome it is using .# because it does not collide names.

A good rule of thumb would be to use .# for utils you don't use often and use :. for utils you use often and that you don't mind the name being global.

Implicit return for tags

If the last expression of a function or a file is a tag, it is implicitely returned.

def render
  let name = "Marc"
  <html>
    <div><p>Hello {name}!</p></div>
  </html>
end
// Last expression of a file
<css>
  .someClass {
    color: red;
  }
</css>

TODO: Move this somewhere else with tags

There is no default for tags. You must specify how to handle them.

You can treat them simply as a string. You can allow interpolation using curly braces {variable} or using <% = variable %>

You can parse tags as xml or not.

config.jome

You can use config.jome to pretty much define your own programming language.

You can choose what are the global variables and functions available in this file (sin, cos, PI, or map, filter, ...)

You can also choose how to handle strings and tags.

Jome imports inside config.jome should be jome files inside a /utils folder or similar. It should be separated from the rest of the code, because it is different in the way that it does not have access to config.jome globals because it is being compiled.

Parameters allowed:

  • main: The main file to be executed. index.jome by default.
  • globals: A list of global variables or functions available everywhere.
  • formats: Define how to handle strings and tags
  • units: WIP

Functions

You can use do ... end to create functions. You pass arguments between vertical bars.

import * from 'jome-lib'
[1,2,3,4,5].#each do |i|
  #log i
end

You can create an instance of a class without using the new keyword, simply call it as a function.

class SomeClass end

let someInstance = SomeClass()
let otherInstance = new SomeClass() // You can also still use the new keyword

For this to work with imported classes, you add an ampersand before the identifier.

import &SomeClass from './some_lib.js'

let someInstance = SomeClass()
let otherInstance = new SomeClass() // You can also still use the new keyword

The advantage of this will be more apparent in latter Jome versions.

Keywords

Here are the Jome keywords that are different than javascript:

  • with: An alternative way to describe parameters
  • end: Replaces curly braces for statement blocks
  • elif: Same as else if
  • elsif: Same as else if
  • chain: Allows to call multiple methods on the same instance and return the last.
  • private: Comming soon...
  • along: Comming soon...
  • code: Comming soon...
  • unit: Comming soon...
  • type: Comming soon...
  • interface: Comming soon...

Comments

You can use regular js comments with // and /* */.

You can also use # for documentation comments. Note: The hashtag must be followed by a space.

// This is a regular comment

# This is a documentation comment that describes the function below.
# Second line of same documentation comment.
# @returns Some value
with
  string str # Documentation comment that describes what this argument is
  number nb # A multiline description
  # can continue under

  # A multiline description
  # of the arg total
  float total
def someFunc
  /* TODO: implement this function */
end

By default, regular comments are discarded when compiled and documentation comments are kept.

Assignment - TODO WIP

let x = 10 // yes int x = 10 // yes, string, float, ... works too x := 10 // maybe, same as let x ::= 10 // maybe, same as const? var x = 10 // probably const x = 10 // probably Foo foo = Foo("arg") // no, use instead: let foo = Foo("arg")

Types

TODO: Supporter les 2 syntaxes, c'est pas ben ben plus compliqué et ça laisse le choix à l'utilisateur.

def sum(x: int, y: int) -> int def sum(int x, int y) -> int

The types are specified before the variable name like in C++ or Java, not after like in typescript.

You can declare primitive types directly, but you still use the keyword let for class instances. (You can't do it like c++ because it would be confused with function calls)

int i = 0
string str = "some string"
let any = "some string"
let obj = SomeClass("param")

When the type is ambiguous, or you prefer to be explicit, you can cast it like so:

let name = (Name) getName(10)
let someName = (Array<SomeClass>) [SomeClass(10)]

Inside function arguments, it's the same thing. The type comes before the variable name.

def someFunction(int count, string message)
  // ...
end

with
  int count # the count
  string message # a message
def someFunction
  // ...
end

I want to do the same for function types too. They replace the keyword def. This syntax uses curly braces instead of end keyword. A def with no args does not require parentheses, but this syntax does.

int sum(int x, int y) {
  // ...
}
int add(int x, int y) {return x + y}

class SomeClass
  int someMethod(int x, int y) {
    // ...
  }
  void doSomething() {
    // ...
  }
end

If I am to allow this syntax, then might as well allow no type in front

class SomeClass
  doSomething() {
    // ...
  }
end
class SomeClass {
  def doSomething
    // ...
  end
}
def soSomething : int

end

How does it work for instance variables initialization? Works well

class SomeClass

  int @count = 0
  string @message = "Hello world!"

end

TODO: Type of function signature for callbacks and al

def doSomething((void => int) callback)

You cannot use curly braces with the with keyword. You use def. The return type is given after the return keyword.

with
  int x # Number 1
  int y # Number 2
  return int
def add

end

Or maybe the return type is given using a fat arrow?

with
  int x # Number 1
  int y # Number 2
  => int # The sum of number 1 and 2
def add

end

Or maybe the return type is given using a colon?

with
  int x # Number 1
  int y # Number 2
def add : int # The sum of number 1 and 2

end

TODO: tuple, templates

string[] names = ["John", "Mary"]
Array<string> names = ["John", "Mary"]
SomeContainer<string> container()

class SomeContainer<T>
  T[] @list;
end

I want Jome by default to be very permissive. You can then add rules to restrain for being more standard or more secure.

So if you are working on a personal project, you can use whatever style you prefer.

If you are working on a shared project or in a company, you should use a linter so the code is more standard.

I like to have both curly braces and end keyword. Personally, I think I prefer a mixture of both. At each indentation, change. This way it is easier to know where you are.

Strings

They are many kinds of quotes in Jome that should handle every use case. There is:

  • Single quotes: Rendered as is. let code = 'if (cond) {return 0;}'
  • Triple single quotes: Rendered as is. Allows to insert single quotes inside the string.
  • Double quotes: Allows template literals. Insert content using curly braces. "Hello {name}!"
  • Triple double quotes: Allows template literals. Insert content using double curly braces. """Hello {{name}}!"""
  • Verbatim strings (@): Render the string as is without escaping. let windowsPath = @"C:UsersPascalDocumentsFile.txt". Verbatim strings can be used with any of the kinds above. Interpolation will work for double quoted strings.

Backticks are not supported for now. It's not yet decided how to handle them.

TODO: Support them, just not interpolation yet because I don't know which interpolation I want to be able to use.

Note: All strings are allowed to be multiline.

In order to have consistency, strings shall always be compiled into strings. For example, if you compile to ruby, than backtick strings shall be converted to double quote strings because executing shell commands this way is not allowd in Jome. Use < sh > tags for that instead.

Similarly, single quote strings compiled to c++ will be using double quote strings so that it is a valid string.

Formatting v5

TODO: The first part of the tag is what is used for syntax highlighting. So for example, html-js, it should highlight like html. -js is just a postfix that means compile a certain way

You can format all kinds of strings and tags by using the syntax forall <tag_name> chain func1[, func2] wrap func3[, func 4] end.

The functions given to wrap are added arround the final string and executed at runtime.

The functions given to chain modify the raw string itself at compile time. They can even convert the strings into raw javascript code.

The input and the output of chain functions are of the same type. They take an array of lines. And each like is an array of strings or TemplateLiteral.

For example, the js tag takes a string in input and simply output it all into javascript code.

FIXME: FIX THE CODE BELOW BECAUSE ONLY JS IMPORTED FUNCTIONS ARE ALLOWED. DO ... END is not allowed. This is because what if there are strings inside the jome function of other things it could become a loop nightmare.

forall js
  chain do |lines|
    // First validate that every line contains only a string. Template literals are not allowed in a js tag.
    if !lines.every(line => line.length === 1 && line.type === 'string')
      throw new Error("Template literals are not allowed in a js tag.")
    end
    // Convert all the string into a single TemplateLiteral
    return TemplateLiteral(lines.map(l => l[0]).join('
'))
  end
end

The chain functions must be imported javascript functions.

Chain function are given the parameter lines, and another parameter ctx or something, that allows to including other files at the top of the file.

Or it returns either lines, an array of array, or an instance of TagWithContext that returns lines and the files required to include.

Wait what about <% %> is this allowed. A heredoc is a string literal so it does not make sense here? I don't really think it makes any sense really...

Content tag percent syntax

You can define heredoc with xml tags, but you can also simply prepend a percent followed by the tag name.

let isTrue = <txt>Hello</txt> === "Hello"%txt

The difference is that you won't get syntax highlighting for things like html, css, etc. But you will at least get syntax highlighting for template literals properly.

TODO: You can use percent formating after variable name too:

content%txt === <txt><%= content %></txt>

Possiblité d'avoir des arguments aux formats? Par exemple, %indent(2spaces) or %indent(2tabs)

Config save

Jome is by default opiniated and provide a lot of default of a lot of formats. They will change over time as librairies or opinions change.

To make sure it works properly and it is not confusing what it does, every format used will be stored inside config.jome.

When a new format is encountered in the source code, it is added to config.jome automatically, and librairies that need to be installed will be installed automatically.

The goal is that it is really easy to do advanced stuff. For example, no need to use create-react-app or anything complicated. If you want a single page of your big application to use react, that use a react format and it will magically work.

It keeps a snapshot of the formats used and allows to easily know what a format does, just look it up inside config.jome.

For more info on tags/formats

See the formats page.

Formatting v3

You can specify for:

  • single_quote
  • double_quote
  • triple_single_quote
  • triple_double_quote

Calling function left operand

In the same way that you can do operand.#function, I want to be able to do it with local function as well.

This would be usefull for formatting strings.

"some string":.someTransformFunction

:.

Shorthand syntax for function calls

I want a short syntax for function calls like in ruby.

I am thinking of using the same.

let names = people.#each(&:name)

Contrary to ruby, &: does not have any other meaning like defining a block to a symbol. It's just a syntaxic sugar.

Or

:. could work for this too? When it has no operand, then it returns a functions that calls it on the object. I don't know... I don't like it too much because it's very different. :. get a function by this name, here it is an object property...

let names = people.#each(:.name)

Verbatim string literals

Verbatim string literals are strings that do not interpolate. The idea is taken from C#. But in C# it also includes backslashes without escaping them I don't know if I want to do that. Or I want to offer multiple possibilities.

str = @"This is a string that does ${not} interpolate"

Heredocs

In jome, the idea is that you could include most other programming languages directly using xml tags.

// Execute a shell command in a script
<sh>ls -A</sh>

// Ruby is a pretty nice language for scripts too 
<rb>puts (1..10).select(&:even?).sum</rb>

Scripts for data

TODO:

En jome, c'est 01010101 pour faire du binaire et FF00AA pour faire de l'hexadécimal. 12345678 compile en 0o

Ne pas supporter 0xFF00AA et ne pas supporter non plus 0b01010101, ceci est confondant avec les unités.

bin et hex compile en utilisant 0x et 0b

TODO: Rajouter du syntax highligh au hex pour changer de couleur à toute les 2 charactères? Afficher comme des string et à tous les 2 charactères mettre comme si escaped string pour que la couleur change un peu.

TODO: Supporter la syntaxe 123e4 par contre let exponentialNumber = 123e4; console.log(exponentialNumber); // Outputs: 1230000

Scripts interpolation

You can add data inside the scripts using the < % = % > syntax.

Contrary to other template languages like ejs, you must finish the expression given inside the interpolation tag.

If you want to do a condition for example, you use a nesting tag < > ... < / >

< % = if true < >
  <div class="content">
    < % = content % >
  </div>
else < >
  <div class="content">
    < % = content % >
  </div>
< / > % >

Substitutions

You can insert stuff after the transformers using <%s varName %>. Under the hood, a hash is created for varName, it is inserted into the string and it is substituted after the transformers are called.

TODO: Find a syntax to allow the same thing, but for strings. "Hello {%s name}"?

Paths

The issue with relative paths is that you don't know what they are relative to. In js, in include files, the relative path is relative to the current file. If you try to open and write a file, than it is relative to the current working directory.

In Jome, this is more explicit using paths. '#./' is used for paths relative to current file, '#cwd/' is used for paths relative to the current working directory. They are compiled to become absolute, so you can pass it to a function in another file and you are sure it will reference the proper file.

You can use # to define paths. They must start by '/', '.' or 'cwd/'. (Maybe '~' too)

let path0 = #. // same as __dirname
let path1 = #/some/absolute/path.txt
let path2 = #./someFile.jome
let path3 = #../otherDir/"some file with spaces.txt"
let path4 = #cwd/someFile.txt // Allow paths after #cwd to get files inside current working directory.
let path5 = #~/Downloads/someFile.txt // Maybe

Relative paths are converted to absolute paths. It's the same as joining __dirname with the relative path.

Spaces are not allowed, but you can escape them. Any other character is allowed?

You can use quotes inside paths, just not at the beginning. You can use like like so:

let test = #./'some/file with spaces.txt'
let test = #/'some/file with spaces.txt'
let test = #./"some/file with spaces.txt"
let test = #/"some path/file with spaces.txt"
let test = #/some/path/to/some/"file with spaces.txt"

If you want quotes inside your filename, than you need to escape them.

TODO: Interpolation inside path

let filename = 'foo.txt'
let filepath = #./some/dir/{filename}

Paths always use the /, but maybe they are converted when compiling for Windows?

Require file handlers

Version 2:

For jome file, executes the file and return the data.

For any other file, simply return the data as a string. Very basic stuff. For streaming or images, use functions directly. Ah well, it would be nice if for .json it would load the JSON directly? Maybe do more fancy stuff. IDK

<?file-loader ext="*.json" ?>

Work in progress

You can process files using the syntax: #(./some_file.txt)

The default file handlers are:

  • *.js, js: Same as the function require
  • *.jome, jome: Compiles the file, use require and run jome file
let data = #("./some_data.json")
let lib = #("./some_lib.js") // same as require('./some_lib.js')
let result = #("./some_command.jome")

You can use the keyword as to using another string as a matching string instead of the filename

let data = #(./some_data as "json")

How about let's say an image?

let img = #(./some_img.jpg)

Well in that case instead of using require, you could define a custom file handler.

let img = #(./some_image.png)
// Here it could be compiled for example to:
const PNG = require('png-js');
let img = new PNG('some_image.png');

You can pass arguments to the file handlers. For jome files, they will be given to the file for example.

let result = #(./some_command.jome, "someArg")

Include file handlers

Read the give file and insert the content as a string to the js file.

let data = #...("./some_data")

< % = % > vs { }

The syntax allowed will be < % = % > because it is pretty much universal already.

Curly braces do not work for CSS for example.

Jome global object

Create a global object jome, much like window, to contain jome specific data.

Maybe jome object would be the way to be language agnostic.

Create an API that people can use. So for example, you do jome.log instead of console.log, this way it works when compiling to python it would use print.

jome.evt => refers to this inside an event jome.window => refers to the window jome.params; Deprecated? jome.env // to set environment variables (process.env in js) jome.global => to set global variables (globalThis in js)

You can also use the configuration file config.jome to define global variables or functions. See config.jome.

Instance properties (@)

One of the objective of Jome is to remove the weird thing that is this.

I want in Jome this to refer to the current instance when inside a method and that's it.

In order to do this, I should bind automatically all the functions inside the class to the proper this. Inside the constructor: everyMethod1.bind(this) everyMethod2.bind(this) Then inside the everyMethod1: __this = this everyMethod2: __this = this

At is usually the same as the keyword this.

let _this = @ // same as this
let name = @name // same as this.name

Idea: But it can also be used to refer to children.

let firstChild = @1 // Child index starts at 1
let secondChild = @2

TODO: Use the keyword self instead of this, this makes it clear that it refers to self inside a class, and that it is a little different than this in javascript in that it always refer to the class instance.

Use jome.evt to get current event Use window or jome.window to get the window

FIXME: This clashes with decorators. But it can still work too I believe. Decorators are before class and method definitions. Attribute accessors are inside.

See https://www.sitepoint.com/javascript-decorators-what-they-are/ to learn decorators.

Exclamation mark in objects

Inside an object, you can use an exclamation mark after a variable name so it sets it value to true.

let obj = {someCond!} // same as {someCond: true}

I am not yet sure about that one. Maybe symbols with an exclamation mark at the end means true? This makes it less confusing and this could allow us to create methods using an exclamation mark at the end.

let obj = {:someCond!} // same as {someCond: true}

Loops

Loops are used exactly like in javascript, but with the end keyword.

Work in progress

I think I want to support while, and for like in c++. I don't want to support for of and for in because they are confusing I believe. Use obj.#keys.#each or arr.#each instead.

Note: There is no do ... while, because the do keyword is used for functions. Maybe exec ... while ???

for (let el in els)
  // ...
end

for (let i = 0; i < list.length; i++)
  // ...
end

while (someCondition)
  // ...
end

For of is usefull to loop through a string and handle unicode surrogate pairs

Conditions

You can use elif, elsif or else if. They are all the same.

if someCond
  doSomething
elsif someOtherCond
  doSomething
else
  doSomething
end

You can also add an if after a statement to make it conditional. The statement will only be executed if the condition is true.

return "some val" if someCond

J'aimerais pouvoir utiliser un if modifier dans un string literal.

someStr = Hello{' '+name if name}

Et j'aimerais que dans ce cas, ça retourne automatiquement '' au lieu de undefined ou null

Switch / case

Mon idée, tu fais: case someVal Ça assigne la valeur someVal comme valeur temporaire qui va être utilisée. Ensuite, quand tu call when, ça compare avec la valeur de someVal

ex: let cmd = "yell" case cmd // Any code can be here it does not matter return "HELLO WORLD" when "yell" let someVar = "hello" when "talk" else "hola" when "spanish" else "sup"

Par défault, when fais simplement une comparaison. Mais tu peux utiliser d'autre opérations avec en suivant when d'un opérateur

case number return "very big number" when > 10000 return "big number" when > 1000 return "number"

Le case doit être dans le scope des when. Tu ne peux pas par exemple le mettre caché dans le if. Les when sont simplement remplacé par "if (expression du case)"

Classes

Normal arguments are only available inside the constructor. The constructor is everything inside the class before the first def ... end. If you use @someArgument, then it will automatically set it for you. You pass arguments to the constructor after the class name or before with the with keyword.

class Person(favoriteColor)
  @firstName: string = "John"
  @lastName: string = "Doe"
  @age: number
  @favoriteColor = favoriteColor || 'brown'
end

with
  @favoriteColor = 'brown'
class Person
  @firstName: string = "John"
  @lastName: string = "Doe"
  @age: number
end

class Person
  @username: string = "Bigdaddy007" // same as doing @username = "Bigdaddy007" inside the constructor
  @username?: string = "Bigdaddy007" // same as doing @username = jome.params.username || "Bigdaddy007" inside the constructor
  @usersame // does nothing
  @username // same as doing @username = null inside the constructor
  
  @@className = "Person"
end

All attributes are public by default. Use the private keyword to make them private. (Not yet implemented)

Methods

class SomeClass
  // Multiple lines methods
  def someMethod
    // ...
  end

  // Inline methods
  def add10 = x => x + 10

  // Alias
  def addTen = add10

  // What about constants? Is this allowed?
  constant = 125 // What does this meannnnnnn??
  @@constant = 125 // Double ampersand! What does this meannnnnnn?!?!

  @token = ... // attached to instance (set in constructor)
  token = ... // attached to prototype
  @@token = ... // static, attached to class

  // SomeClass.constant

  def @@staticMethod
  end

  // Noon je ne trouve pas ça tant beau
  // Je préfère utiliser le static keyword

  def static staticMethod
  end
end

Idée: Quand une classe inhérite d'un interface, mettre les valeurs par défaut dans le prototype.

Inline methods - TODO WIP

Pouvoir définir une méthode sur une seule ligne avec def < funcName > =

class SomeClass
  def inlineMethod = "someText" // Careful here it is a function, not only a string

  def inlineMethod2 = {
    key: 'value'
  }
end

Deconstructings - TODO WIP

I want to be able to name deconstructed arguments in a method. Maybe with keyword as? aka? alias?

def example(props as {arg1, arg2})
end

Inheritence

WIP

Modules and exports - TODO WIP

You can write modules by writing files with the .jomm extension.

You can export things with the export keyword.

export def someFunc
end
export let someVar = 10

// usage:
// import {someFunc, someVar} from './some_file.jome'

Use export default works too.

FIXMEEEEEEEEEEEEeee: Remove everything I created about the main keyword. Let's use export default instead.

I want to be more explicit.

import foo from 'bar'

Does it mean import the default, or it means import all of it? Logically it's the default only.

But let's say I export using cjs. I export only a function of a class. I don't want to have to do import * as foo from 'bar'

Alternatives?

import as foo from 'bar' const foo = require('bar') require foo from 'bar' import 'bar' in foo import 'bar' as foo

I like:

import 'bar' as foo
import 'index.jome' as foo

I don't know. Maybe I actually prefer simply require like before...

const execute = require('execute.jome')
const &Webpage = require('webpage.jome')

I think I got it! Using a colon, this means import like using require. I don't like the colon. Use the keyword of instead.

import &Webpage : './webpage.jomm'
import build : 'build.jome'
import {trim, flat} : '@jome/core'

Functions - TODO WIP

// Utilise la syntaxe comme ruby
def sayHello
  console.log('Hello')
end

def sayHelloTo(name)
  console.log("Hello! {name}")
end

The idea in Jome is that you don't have to make the distinction between a function and an arrow function. We'll see if this works.

With keyword

The with keyword is used to list function arguments.

It can be standalone in a .jome file which means they are arguments that can be given to the file function.

import * from 'jome-lib'
with src, dest end
#cp src, dest

It can be used along the def keyword to specify the function arguments. This allows easy documentation of the code without having to repeat yourself in the comments.

with
  string name # The name
  int count # The count
def doSomething : int
  // ....
end

It can also be used to specify the arguments the constructor of a class takes.

with
  name: String # Full name of the personnellement
class Person
end

along keyword - TODO WIP

When you want to also have the unit or the code for a variable, you can use the along keyword.

along is useful to be used alongside:

  • unit: The unit of the expression given, as a string
  • type: The variable type of the expression given (like typescript), as a string
  • source: The source code literaly given of the expression, as a string
  • code: The js compiled code of the expression, as a string

The along keyword is useful, because you could also want the use of the modifiers above alone.

def debug = |msg along code along type along unit| => ( def debug = |msg along code as customCodeName along type as customTypeName along unit as customUnitName| => (

You can't only get the modifier value, but then again you can simply assign an unused variable name

def printType = |unused along type|

Base library

Every project can choose a base library. This library should contain common utility functions and constants.

For example:

// Using lodash as a base library
import * from 'lodash'

// Then you get access to all the named exports by prepending an hashtag symbol before.
#partition([1, 2, 3, 4], n => n % 2)
// → [[1, 3], [2, 4]]

// You can also use the hashtag operator (.#) to put the first operand before instead of after.
{ 'a': 1 }.#defaults({ 'a': 3, 'b': 2 })
// → { 'a': 1, 'b': 2 }

Using multiple import like this is not allowed because it would be annoying to know where the function is coming from and this avoids name conflicts.

If you want multiple import, then create a file or library and join the import and export them.

export * from 'ThingA';
export * from 'ThingB';
export * from 'ThingC';

Ouin, finalement ce n'est pas super, parce que ce n'est pas plus clair ainsi, c'est juste un truc de plus... mais bon cette syntaxe est déjà accepté donc c'est OK

Capture de code - TODO WIP

TODO

Je pense que la syntaxe que je veux est d'utiliser le type 'code'.

def debug = |msg along code| => (
  console.log(code+':', msg)
)
debug(nomDeVariable) // => console.log("nomDeVariable:", nomDeVariable)

// When calling in javascript, you need to supply both arguments
debug(nomDeVariable, "nomDeVariable")

aka or alias - TODO WIP

I want to be able to give many possible names to a parameter.

For example, the port, it can be 'port' or 'p'

In js, you have to do:

let run = ({port, p}) => {
  port = port || p
}

In Jome, you can simply do:

let run = ({port aka p}) => {
  // ...
}

It will automatically be compiled to the code above.

Threads - TODO WIP

Exactly the same as JavaScript? await, async, ...

async def someMethod
end

let someFunc = async () => {x: 1, y: 2}

Private - TODO WIP

For private fields inside classes, you can use a private block. You can also use the private before a method.

class SomeClass
  private
    def somePrivateMethod
    end
  end

  private def someOtherPrivateMethod
  end
end

It compiles in javascript using a # before the names of fields.

Ternary - TODO WIP

Idée: pas de ternary operator comme d'habitude, parce que je veux pouvoir faire (x ? y) seulement sans le :. Le : est très utilisé je trouve. Je n'ai pas tant envie de l'utiliser comme opérateur simple. L'idée est donc d'utiliser les opérateurs qui existe déjà, ? et ??. x ? y ?? z Si x, alors y, sinon z D'un côté je n'aime pas dévié d'un standard. ? : est pas mal dans tous les languages, mais d'un autre côté je trouve ça vraiment gossant de ne pas pouvoir faire simplement x ? y. Le résultat serait null. let r = x ? y // si x, alors y, sinon null let r = x if y // si x, alors y, sinon undefined let r = {x ? y} // si x, alors {y}, sinon null let r = {x if y} // si x, alors {y}, sinon `` J'aimerais vraiment ça!!!!!!!!!!!!!!

Chaining methods

You can execute multiple methods on the same object using the chain keyword.

// Create a server, add a route and start it
ExpressServer port: 3000 chain
  get '/' do |req, res|
    res.send(homePage)
  end
  start
end

Chain returns the value of the last command.

Notebook like jupyter

Jome code can be used to create notebooks like jupyter.

You can use the .jomn extension to denote that the file is expected to be opened as a notebook.

Markdown cells

You can create markdown cells by surrounding the content with an hashtag and a star #* This is a *markdown* cell *#.

Note: ### is deprecated. Starting and ending with three hastags alone on a line is deprecated because it's confusing when you don't know if it's a start or an end.

#*
This is a markdown cell
### This level 3 title
Below is the ending
*#

Code cells

Not all code cells are executed.

TODO: What keyword to use to mean that it should be executed???

cell
  // ...
end

script
  // ...
end

code
  // ...
end

Data cells

Jome introducte the idea of data cells in a notebook.

The idea is that you should be able to enter data in a spreadsheet like format in the notebook.

TODO: Explain that tags are data cells, and that right now I am simply abusing the markdown cells

Collapsed code cells

There should also be collapsed by default code cells. For example for imports. If you click on it, you should see it. But you could also that it is collapsed by default.

But I want to be able to search for it though... CTRL+F should find text hidden inside the collapsed cell. Not sure if that is possible in the browser...

Ah yes, maybe simply make is small and scrollable, but when it gains focus, it becomes bigger! This should work with CTRL+F!

with blocks

Idea: A with block should be showned in a notebook like documentation on the web. It should be pretty, and the script code below would be the content of the function of class.

Bugs

TODO: Link to BUGS.md

Contributing

I recommend using visual studio code for now because it is super usefull for debugging tokenization. You hit Ctrl+Shift+P, "inspect editor token and scope", and you see if it is correct. Also you see using syntax highlighting.

You can make pull requests on github.

You can simply make constructive comments on github.

Keep in mind I am working only 10 hours a week on this project for now.

Acknowledgements

  • CoffeeScript: I was kinda lost at some point. I did not have a clear direction for my language. Until I thought, hey, coffeescript did something similar! So it gave me a lot of guidance.
  • underscore.js: A great library full of goodies.
  • vscode: Escpecially thank you for creating custom grammar. It is really nice to create a grammar and see live the tokenization.
  • ChatGPT: I probably never would have had to courage to go through with writing a programming language if I did not have the help from ChatGPT.

Thank you to everyone who contributed to any open-sourced library. Escpecially under a license like MIT license. You are awesome!

Librairies used:

TODO

TODO: Check for grammar and vocabulary mistakes! aspell? TODO: config.jome > md > spellcheck: true, I want to see mistakes within vscode.

TODO: args are passed space separated instead of comma separated --args with dashes near each other are grouped together

TODO: Error handly. Try catch...

units

Units are simply an extension of type but for numbers.

They are used for validation at compile time.

They can also be used for automatic conversion. Let's say a function sleeps a given amount of time, you can pass 100 ms, 0.1 s, ... and it will all be the same.

For this to work, you must specify an exact unit.

def sleep(time = ? s)
  // ...
end
sleep(0.1 s)
sleep(100 ms) // will be compiled into 0.1
let ref = sleep
ref(100 ms) // the behavior here will depend on the unit checker / type checker

A quantity with a unit is just a number. It is not evaluated at runtime.

The concept of a unit does not exist at runtime.

You can also specify that a function expects a unit of a type or similar. You add an asterisk after the type.

def applyForce(force = ? N*)
  // ...
end
applyForce(2) // ok, defaults to N
applyForce(2 N) // ok
applyForce(10 lbs) // ok
applyForce(1 m) // will throw an error at compile time, a meter is not a valid force

FIXME: An asterisk is not the best because it is confusing with multiply which is pretty common in this context. let someVal = 10N*1m

But I don't have a better idea that an asterisk, and maybe they are never used in the same context?

The asterisk part is a work in progress.

Maybe this means pass two parameters. def applyForce(force = ? N*) /* same as */ def applyForce(force, unit)

But this does not work...

The issue is that when I do: applyForce(2 N), I am only passing a single value to the function.

Well it's the same thing of along code and along unit idea. Maybe this works, but it becomes a special kind of function that must always be referenced directly?

This would be illegal: let ref = applyForce

Whenever you use a function that needs meta data, it must be called directly.

I need a name for that.

A macro.

Macros

Macros are functions that are extended at compile time with the context. Additional parameters are added to the function.

You can for example, add the text that was given to the parameter be also passed as a string.

# Here 
def debug(value along code c)
  console.log(c, ': ', value)
end
debug(1 + 1)
// => 1 + 1: 2

You can also get the unit from a parameter this way.

def printUnit(someVal along unit u)
  console.log(u)
end
printUnit(10 N)
// => N

Maybe a keyword macro to show that it is explicitely a macro?

The difference between a function and a macro in Jome is that a macro has extra hidden parameters.

Passing data to middleware

The compiler should be smart enough to know that callFunction needs the unit, callMiddleware can have the unit since it is a macro too. callMiddleware would be flagged by the compiler has requiring unit. Therefore, it's signature would be: callMiddleware(val, unit)

callMiddleware(1 m)

macro callMiddleware(val)
  // How to specify that val has the unit here???
  callFunction(val)
end

macro callFunction(val)
  let unit = unitof(val)
end

To macro or not to macro

The compiler could do this all on it's own. It does not have to be explicit with the macro keyword.

Is it useful that it is explicit?

Or we don't care and we don't want to have to deal with this???

I think we don't care and unitof and sourceof could simply behave like magic.

Other

Shorthand key syntax (deprecated, only like js is supported {foo} === {foo: foo})