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
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
requireand 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.