Ash


Un langage de script simple et concis avec support pour l'orienté objet.

Sommaire

Introduction

Ce document décrit le langage de script Ash. Il se veut simple et concis.

Vous trouverez dans ce document une présentation de ses fonctionnalités ainsi que des encarts expliquants les choix de design qui ont été fait. En effet, je pense que pour mieux comprendre un artefact généré par l'homme, qui résulte d'un ensemble de décisions, il vaut mieux savoir les raisons derrière celles-ci. On évoquera ainsi ce qui a été écarté et ce qui est envisagé comme évolution possible.

Ash se veut toutefois un langage assez stable, en réaction à à la complexification et à l'accélération des différents langages de programmation qui se mettent à changer tous les ans, même les plus anciens, en ajoutant à chaque fois plus de fonctionnalités, de syntaxes, de façons différentes de faire la même chose. Toutefois, étant l'œuvre d'une seule personne, il se peut que je n'implémente pas tout ce que je voulais, d'où les évolutions possibles mentionnées.

1. Noyau du langage

1.1 Installation

Pour lancer coder avec Ash, il existe trois façons :

ash.exe

ash.exe pipo.ash
ash.ex -i pipo.ash

 ash.exe -transpy pipo.ash
 python pipo.py

Les arguments sont stockés dans une liste. 0 est le nom du script. En nég, les options préalables. En pos, les options du script.

Note de design

Ces encadrés exposent des décisions de design.

La syntaxe de Ash s'inspire des langages Lua, Ruby et Pascal.
Les langages JavaScript et Python sont également des inspirations pour leur fonctionnement et leur bibliothèque de base.

1.2 Identifiants, valeurs et littéraux

Comme premier programme lorsque l'on apprend un langage de programmation, la tradition veut qu'on affiche "Bonjour le monde !" sur la sortie standard. Pour cela, il faut lancer l'interpréteur interactif ash.exe puis taper :

 writeln("Hello World!")

Vous venez d'appeler une fonction, nommée writeln, avec un paramètre, la chaîne de caractère "Hello World!". En exécutant ce code, la chaîne sera affichée sur la sortie standard.

Ash est un langage de script qui fonctionne avec des identifiants et des valeurs. Un identifiant référence une valeur. Une valeur est exprimée directement dans un code source par un littéral. Pour déclarer un nouvel identifiant et la valeur qu'il référence, on utilise l'opérateur = en mettant à gauche l'identifiant et à droite la valeur :

a = 5

Ici a est l'identifiant et 5 le littéral qui désigne l'entier 5. De même, la chaîne "Hello World!" est un littéral de type chaîne de caractères. Un littéral est une constante, il n'est pas modifiable, qui possède un type. Un littéral ne peut jamais apparaître à gauche de l'opérateur =.

Plusieurs identifiants peuvent référencer la même valeur, dans le code ci-dessous les identifiants a, b et c référencent la même valeur :

a = 5
b = 5
c = b -- équivalent à c = a

Un identifiant peut, au cours d'un programme, référencer différentes valeurs mais à un instant donné il n'en référencera toujours qu'une :

a = 5
a = "Hello World"

Ash fait la différence entre les minuscules et les majuscules, il est sensible à la casse, ainsi a et A ne sont pas les mêmes identifiants.

Un identifiant peut être associé à un type lors de sa déclaration (= la première qu'on l'utilise). Dans ce cas, il ne pourra référencer que des valeurs de ce type. Pour cela on utilise l'opérateur : suivi du type :

a : int = 5
a = 8 -- ok 8 est de type int
a = "Hello World" -- déclenchera une erreur

On peut obliger un identfiant à ne référencer qu'une seule valeur et ne pas pouvoir en changer lors de sa déclaration. Pour cela, on utilise le mot-clé const.

const a : int = 5 -- on peut mettre le type
const b = 6 -- mais sinon il sera déduit du type de l'expression à droite du =
a = 22 -- erreur
b = "abc" -- erreur

Enfin, on peut déclarer un identifiant qui ne référence aucune valeur avec le mot-clé nil :

i = nil

On ne peut jamais utiliser un identifiant avant qu'il ne soit défini.

1.3 Instructions et expressions

Un programme Ash est composé d'instructions et d'expressions.

Seules les expressions peuvent être évalués : 2 + 3 est une expression qui peut être évaluée à l'entier 5.

Un bloc est un ensemble composée d'instructions et/ou d'expressions. Un bloc n'est pas évaluable.

Exemple de l'instruction while :

 while expression do
     bloc
 loop

Cette instruction exécutera le bloc tant que l'expression entre le while et le do sera vraie.

Une instruction utilise des mots-clés : ici, while, do et loop.

1.4 Mots-clés

Ash utilise 27 mots-clés.

var const
if then elsif else end
while do loop for in
break next
fun pro return
and or not
class static is
import
true false nil

1.5 Constantes et variables

Pour déclarer une variable, il faut utiliser cette syntaxe :

[var] ID [: TYPE] = EXPR

Il est recommandé de commencer une variable par une minuscule et d'utiliser un "_" pour séparer les mots.

var age_personne : nat = 25

Pour déclarer une constante de référence, il faut utiliser cette syntaxe :

const ID [: TYPE]  = EXPR

Cela indique que l'identifiant ID référencera toujours la valeur donnée par EXPR. Toutefois, cette valeur peut-être elle-même constante (comme le littéral 5) ou modifiable.

Il est recommandé d'écrire les constantes en majuscule.

const PI : num = 3.14

nom de variable : (_A-Za-z)(0-9A-Za-z)

_ is for dummy variable or the last expression. utf-8 variable are supported.

following word are reserved because they are keyword.

defined?(tst) --> nil
tst = nil --> variable defined with value
if tst is nil then
    writeln('tst is nil')
end
tst = 22
writeln(tst)  --> 2

A variable is automatically cleaned after its containing scope is destroy (function or prog body).

1.6 Types et opérateurs

Une expression est forcément évaluable à une valeur avec un type :

5 + 2

Cette expression donne 7, de type nat pour les entiers naturels.

Une expression est composée d'opérateur et d'opérande.

Certains opérateurs sont binaires (= 2 opérandes) ou unaire (= 1 opérante).

Certains opérateurs s'appliquent à certains types de valeurs.

Il n'y a pas d'opérateur postfixe en Ash.

Une expression peut être composée de sous-expression :

exp op exp
op exp

Séparateurs d'expression : \n, ;

Liste des opérateurs :

Priorité Opérateurs Description
1 > extraction d'import, type de retour des fonctions
1 < héritage
1 = affectation
7 +=, -=, *=, **=, /=, //=, %= affectation combinée
7 +, -, *, **, /, //, % mathématiques
1 . call
6 ⩵, ≠, >, ⩾, ⩽, < comparaison
3 and or not booléen
2 << >> binaire
# longueur d'une séquence
$ conversion en chaînes de caractères
${x} interpolation dans les chaînes de caractères

1.7 Un programme plus complexe

Une autre tradition est le calcul d'une factorielle. Ouvrez un fichier texte, enregistrez-le sous le nom de fact.ash puis tapez dedans :

-- fonction factorielle récursive
fun fact(nb : int)
    if nb == 0 then
        return 1
    elsif nb > 0 then
        return nb * fact(nb - 1)
    else
        return Exception("Invalid number")
    end
end
nb = input('enter a number:')
writeln(fact(nb))

1.8 Commentaires

Les commentaires monolignes commençent par -- (comme Lua et SQL) et s'étendent jusqu'à la fin de la ligne.

Les commentaires multilignes commençent par --[[ et finissent par --]] (comme Lua)

Note de design

Cela permet de conserver // (C, JavaScript) et # (Ruby, Python) comme opérateurs de division entière et longueur respectivement.

-- comment
--[[ start of multi line
]] end of multi line

2. Types de base et leurs opérateurs

Ash utilise 9 types de données de base :

Nom Type Littéral
Booléen Boolean, boolean, bool true false
Entier Integer, integer, int 25 -25 0b010 0xAB
Naturel Natural, natural, nat 25
Nombre Number, number, num 2.5 -2.5
Chaîne String, string, str "bonjour"
Tableau Array, array {val1, val2, val3}
Liste List, list [val1, val2, val3]
Dictionnaire Dict, dict {key: value}
Valeur nulle Nil, nil nil

2.1 Booléen

2.2 Entier et réel

2.3 Chaîne

2.4 Liste

Signature
pro flatten()
pro pop, shift() : any
pro insert(index : int, value : any)
pro add, append, push(value : any)
get first : any
get last : any
fun find, index(value : any) : nat
fun find_all(value : any) : [int]
fun include?, has? contains?(value : any) : bool
get min : any
get max : any
get length, count, size : nat
pro sort()
pro reverse()
pro uniq()

2.5 Dictionnaire

3. Instructions / Contrôle du flux

3.1 Séquence

Un bloc est un ensemble d'instructions ou d'expressions. Les instructions et expressions sont séparées par des nouvelles lignes ou des points virgules.

a = 1 + 4
2              -- valide
a = 1 + 4 ; 2  -- valide (équivalent aux 2 lignes du dessus)
a = 1 + 4 2    -- ceci n'est pas valide en ash

3.2 Sélection

if CONDITION then
    ACTION
[elsif CONDITION then
    ACTION]*
[else
    ACTION]
end

3.3 Boucle

while CONDITION do
    ACTION
loop

for ID1[, ID2] in ITERABLE [if CONDITION] do
    ACTION
loop

break
next

4. Sous-programmes

4.1 Fonctions

fun[ction] ID (P1 [: T1], P2 [: T2]) [: TYPE]
    ACTION
    return EXPR
end

4.2 Procédures

pro[cedure] ID (P1 [: T1], P2 [: T2])
    ACTION
    return
end

4.3 Getters

Un getter est une fonction qui ne prend pas de paramètres. Elle sert à retourner des valeurs qui sont calculées. On évitera de mettre des calculs trop importants dans un getter.

get ID : TYPE
    ACTION
    return EXPR
end

5. Classes et objets

class ID [< SUPER]
    [static const, static var, [var]] ID = EXPR
    [static] fun or pro
    pro init(@a int, b int)
        @b = b
    end
end

Un attribut est une variable attachée à une instance particulière, on le préfixe par @ à l'intérieur des méthodes d'une classe. Lors de sa déclaration en dehors d'une fonction, on ne le préfixe pas :

class personne
    age : int
    name : string
    fun hello() : void
        writeln("Bonjour, je suis ${@name} et j'ai ${@age} ans")
    end
end
self
super

a = ID.new(param)

class Point          object (fixed fields)/obj
   x int
   y int
end
@attr -- instance attribute
$attr -- class attribute (en Ruby pour global et @@ pour class)

if o is Class c then
   -- vous pouvez utiliser c dans ce bloc
end

static class Order < String -- no instance <=> enum
    static Move = "Move"
    static Wait = "Wait"
    static Attack = "Attack"
    static Follow = "Follow"
end

6. Gestion des erreurs

return EXCEPTION

fun div(a : num, b : num) : num
    if b == 0 then
        return DivByZeroException
    else
        return a / b
    end
end

div(5, 0)
if DivByZeroException then
    writeln("An exception has been catched")
end

7. Modules

 import SYMBOL[.SYMBOL] [include ELEM1[, ELEMi]]
 import FILEPATH as SYMBOL [include ELEM1[, ELEMi]]

Accès via symbol.membre

Un fichier = un module

Pour l'importer : require str ex :

  1. require "flat.ash"
  2. require "../flat.ash"
module ID < SUPER
    -- pas d'instanciation !
    -- permet de faire :
    -- des classes statiques
    -- des interfaces
end

8. Bibliothèque standard

ID [module] = import (FILEPATH | ID) [ > ID1, ID2 ]

module ID -- (en tête de fichier, pas de fin) (plutôt que package ou namespace)

8.1 Noyau

Object
o.clone
o.methods
o.respond_to?
o.dump
o.load
Hash
d.delete
d.find
d.keys
d.values

8.2 Console

writeln
write

8.3 Fichiers

8.4 Système d'exploitation

9. Implémentations et interfaces

9.1 Ash/Python

9.2 Ash/JavaScript

9.3 Ash/C

9.4 Ash/PHP

9.5 Ash/Lua

10. Liste des éléments du langage

Type Eléments
Séparateurs ( ) [ ] { } , ;

11. Eléments de style

  1. snake_case pour les méthodes, les attributs et les variables,
  2. PascalCase pour les classes,
  3. MAJUSCULES pour les constantes.