Helium, écriture d'un compilateur en python

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjours, Depuis maintenant quelques annèes je m'intéresse beaucoup aux langages de programation en générale. J'ai appris la programation avec python puis j'ai essayé plusieurs langage (C/C++, OCaml, Perl, Bash, JavaScript…) pour voir un peu les différente possibilité. Malgré tout, Python reste de loin mon préféré pour la facilité de dévellopement rapide qu'il offre et pour sa multitude de module. À force de m'intéresser aux langages j'en suis venu à m'intéresser à la compilation et à l'interprétation de langage. J'ai suivie plusieurs tuto pour compilé ou interprété des langages simple en particulier avec OCaml. Je me suis donc mis en tête de créer un compilateur fonctionnel pour un langage simple et léger mais qui à terme pourrait peut être permettre un bootstrap (dans très longtemps). Je précise que le but de ce projet et autant de créer un langage réellement utilisable que de comprendre les mécanisme fondamentaux du compilateur ce qui justifie certain choix. En effet j'ai fait deux choix assez originaux dans ce genre de projet : D'une part je ne veux pas utiliser d'outils dédié du type Yacc/Lex, je préfère tout coder en clair même si le résultat pourras difficilement s'adapter à un autre langage que le mien. D'autre part je n'utilise pas non plus d'expression régulière pour faire le lexing de ma source.

Pour répondre aux contraintes que je me suis définit j'ai donc choisie d'utiliser le langage que je maitrise le mieux : Python. Pour moi les deux principaux avantages de ce langage sont la facilité de prototypage et la syntaxe claire et simple qui permet de transposer plus simplement le code dans un autre langage.

En ce qui concerne le langage que je met au point il répond à deux critères :

  • La syntaxe est claire et légère

  • Au finale il doit pouvoir s'interfacer simplement avec les bibliothèque du C L'intéret serait de pouvoir dévelloper des projet assez complexe en profitant de l'immense panel de bibliothèques du C.

Pour faciliter l'interfaçage avec le C, mon langage est en fait compilé en C, je compile ensuite avec GCC pour avoir un programme fonctionnel.

Le projet est donc assez important et n'atteindra peut être jamais l'ensemble de mes objectifs c'est pourquoi j'ai adopté une logique incrémentale que j'ai décomposé en étapes :

[FAIT]

  • Implémenter un système de variables flottantes

  • Implémenter les opérations basique sur les flottants (+, -, *, /, ** (puissance), <, <=, >, >=, =, !=, not, and, or)

  • Implémenter les structures de contrôles de base (if, else, while)

  • Implémenter la déclaration de fonction

  • Implémenter un système de type de base (int et float avec un système d'inférence de type qui pourras s'adapter à n'imorte quel autre type)

  • Implémenter un système de module (pour l'instant ne déclarant que des fonctions et des variables)

[À FAIRE]

  • Rajouter les types composés (string, array)

  • Créer un système d'interface pour importer des bibliothèques C et utiliser les fonctions qui y sont (d'abord celle utilisent les types déjà définies)

  • Rendre possible la création de nouveaux types ou implémenter un concept d'"objet" simple (le but et de pouvoir ensuite enrichir le langage en revenant moins souvent au code C)

  • Si j'arrive jusque là j'aurais déjà fait un sacré boulot, la suite consisteras à améliorer l'interfaçage avec le C pour pouvoir profiter de sa pleine puissance et enfin envisager le bootstrap

Si je parviens au bootstrap (dans quelques années peut être) alors je considérerais que mon langage est assez mature pour une utilisation réel mais d'ici là j'ai énormément de travaille.

Je vous présente donc le résultat actuel de mon travail sur le langage que j'ai baptisé Helium:

  • La syntaxe LALR de Helium

  • Un petit algo simple pour le test

  • Le TODO à cours terme

  • L'ensemble des classes et fonctions constituant le compilateur

  • A la fin un bout de code qui compile la chaine d'exemple

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
# coding: utf-8

# In[DEBUT]:
'''
code    : declare code
        | expr code
        | import code
import  : methyl ident
        | helium ident
binding : carbon ident str
declare : dec ident = expr
funct   : def type ident ( argset block end
argset  : ident : type argset
        | )
type    : i
        | int
        | r
        | real
expr    : number
        | expr op expr
        | ident
        | alter
        | call
        | ifblock
        | whblock
        | { exprlist }
alter   : alt ident = expr
call    : ident ( exprset
        | expr : ident ( exprset
ifblock : if expr endif
endif   : expr else expr
        | expr
exprlist: expr exprlist
        | expr
whblock : while expr expr

'''

SRC = '''
methyl math

def i fac(n:i acc:i)
    if n>1
        fac(n-1 acc*n)
    else
        acc
end
def i pr(a:i b:i)
    print(a)
    print(b)
end

dec i = 1
while 1000 > fac(i 1)
    i = i + 1
pr(i fac(i 1))
dec j = -1

while j <= i {
    print(math!sin(j))
    j = j + 1
}
'''

#TODO:
# creer un systeme d'import base sur les fichier methyl :
## carbon : binding c
## helium : objet natif
# lire les fichier modules avant de passer a la phase de check pour
# enrichir la base de fonctions / variable ...
BASECODE = '''
// This code was generated by the Helium Compiler

//Import
#include <stdlib.h>
#include <stdio.h>
{0}
//Definition of functions
{1}
//Main code
int main(int argc, char** argv){{
{2}
    return 0;
}}
'''
METHYLCODE = '''
//**************** Methyl code ********************
//**************** {0}

{1}
{2}
/**************************************************/
'''
# In[Classes d'erreurs]:

# Types d'erreurs

class StandardError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __repr__(self):
        return self.msg

class LexingError(StandardError):
    pass

class ParsingError(StandardError):
    pass

class NameError_(StandardError):
    pass

class TypeError_(StandardError):
    pass


PRECTABLE = {'or':2,
             'and':5,
             '==':10, '!=':10,
             '>':20, '>=':20, '<':20, '<=':20,
             '+':30, '-':30,
             '*':40, '/':40,
             '**':50,
             ':':60 }

# In[Classes de token]:

# Classes des tokens

class Number:
    def __init__(self, val, ntype, pos=(0,0)):
        self.pos = pos
        self.val = val
        self.type = ntype
    def __repr__(self):
        return str(self.val)

class Key:
    def __init__(self, name, pos=(0,0)):
        self.pos = pos
        self.key = name
    def __eq__(self, other):
        return type(other) is Key and other.key == self.key
    def __repr__(self):
        return 'Key ' + self.key + ' at ' + str(self.pos)

class Ident:
    def __init__(self, name, pos=(0,0)):
        self.name = name
        self.pos = pos
        self.type = None
    def __eq__(self, other):
        return type(other) is Ident and other.name == self.name
    def __repr__(self):
        return '"' + self.name + '" at ' + str(self.pos)
    def __hash__(self):
        return (self.name).__hash__()

class Char:
    def __init__(self, char, pos=(0,0)):
        self.pos = pos
        self.char = char
    def __eq__(self, other):
        return type(other) is Char and other.char == self.char
    def __repr__(self):
        return '"' + self.char + '"' + ' at ' + str(self.pos)
class Op:
    def __init__(self, opcode, pos=(0,0)):
        self.code = opcode
        self.pos = pos
        self.prec = PRECTABLE[self.code]
    def __eq__(self, other):
        return type(other) is Op and other.code == self.code
    def __repr__(self):
        return 'Op ' + self.code + ' at ' + str(self.pos)

class Eof:
    def __eq__(self, other):
        return type(other) is Eof
    def __repr__(self):
        return 'EOF'

# In[Classe de noeud]:
# Constantes de descriptions
ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
NUM = '0123456789'
BLANK = ' \t\n\r'
OPERATOR = ('or', 'and', '!=', '==', '>=', '<=', '<', '>', '+', '-',
        '*', '/', '**', '%', ':')
BEGOP = '!=><+-*/%oa:'
KEYWD = ('if', 'else', 'while', 'dec', 'alt', 'not', 'def', 'end',
            'methyl', 'helium', 'carbon')
TYPES = {Ident('i') : 'i', Ident('int') : 'i'
    , Ident('r') : 'r', Ident('real') : 'r'}
CTYPES = {'i' : 'int', 'r' : 'float'}
# Classes de l'AST

class Ast:
    def __init__(self):
        self.nodes = []
        self.c = -1

    def add(self, node):
        self.nodes.append(node)

class Function:
    def __init__(self, ident, args, define, outtype):
        self.ident = ident  #Ident
        self.args = args  #Ident list
        self.define = define  #block list
        self.scope = []
        self.type = [outtype]  #str list

    def __repr__(self):
        return 'def ' + self.ident.name

class Methyl:
    def __init__(self, filename):
        self.name = filename
        self.file = filename + '.ch3'
    def __repr__(self):
        return 'file ' + self.file

class Carbon:
    def __init__(self, name):
        pass

class Declare:
    def __init__(self, ident, val):
        self.ident = ident  #Ident
        self.val = val  #Expr
        self.type = None
    def __repr__(self):
        return 'declare ' + self.ident.name
class Alt:
    def __init__(self, ident, val):
        self.ident = ident  #Ident
        self.val = val  #Expr
        self.type = None
    def __repr__(self):
        return 'alt ' + self.ident.name

class BinExpr:
    def __init__(self, left, right, op):
        self.left = left
        self.right  = right
        self.op = op
        self.type = None
    def __repr__(self):
        return self.op.code

class Unary:
    def __init__(self, expr, mode):
        self.expr = expr
        self.mode = mode
        self.type = None
    def __repr__(self):
        return 'apply ' + self.mode

class If:
    def __init__(self, cond, exprif, exprelse = None):
        self.cond = cond  #Expr
        self.exprif = exprif  #Expr
        self.exprelse = exprelse  #Expr
        self.type = None
    def __repr__(self):
        return 'if'

class While:
    def __init__(self, cond, loop):
        self.cond = cond  #Expr
        self.loop = loop  #Expr
        self.type = None
    def __repr__(self):
        return 'while'

class Sequence:
    def __init__(self, exprs):
        self.exprs = exprs  #Expr[]
        self.type = None
    def __repr__(self):
        return str(self.exprs)

class Call:
    def __init__(self, name, args):
        self.ident = name  #Ident
        self.args = args  #Expr[]
        self.type = None
    def __repr__(self):
        return 'call of '+self.ident.name



# In[Lexer]:

# Lexer

class Lexer:
    def __init__(self, src):
        if src[-1] != '\n':
            self.src = src+'\n'
        else:
            self.src = src
        self.c = 0
        self.l = 1

    def count(self, nl=False):
        if nl:  #new line
            self.c = 0
            self.l += 1
        else:
            self.c += 1

    def pos(self):
        return self.c, self.l

    def tokenlist(self):
        t = None
        l = []
        while t != Eof():
            t = self.token()
            l.append(t)
        return l

    def token(self):
        if len(self.src) == 0:
            return Eof()
        c = ' '
        #Passer les blancs et les commentaires
        while c in BLANK:
            if not self.src:
                return Eof()
            c, self.src = self.src[0], self.src[1:]
            self.count(c=='\n')
        if c == '#':
            return self.lex_comment()
        if c in NUM or c == '.':
            return self.lex_nb(c)
        if c in ALPHA:
            return self.lex_id(c)
        if c in BEGOP:  #si c'est le premier caractere d'un operateur
            if c+self.src[0] in OPERATOR:
                op = c+self.src[0]
                self.count(self.src[0]=='\n')
                self.src = self.src[1:]
                return Op(op, self.pos())
            elif c in OPERATOR:
                return Op(c, self.pos())
        return Char(c, self.pos())

    def lex_comment(self):
        if len(self.src) == 0:
            return Eof()
        if self.src[0] == '*':
            level = 1
            while level:
                c, self.src = self.src[0], self.src[1:]
                if not self.src:
                    return Eof()
                if c == '*' and self.src[0] == '#':
                    level -= 1
                elif c == '#' and self.src[0] == '*':
                    level += 1
        else:
            c, self.src = self.src[0], self.src[1:]
            while c != '\n':
                c, self.src = self.src[0], self.src[1:]
                if not self.src:
                    return Eof()
        return self.token()

    def lex_id(self, first):
        '''
        [a-zA-Z_][a-zA-Z_0-9]*
        '''
        ident = ''
        c = first
        while (c in ALPHA or c in NUM) and len(self.src) > 0:
            ident += c
            c, self.src = self.src[0], self.src[1:]
            self.count()

        if len(self.src) != 0:
            self.src = c+self.src
        if ident in KEYWD:
            return Key(ident, self.pos())
        if ident in OPERATOR:
            return Op(ident, self.pos())
        return Ident(ident, self.pos())

    def lex_nb(self, nb):
        n = ''
        c = nb
        while (c in NUM or c == '.') and len(self.src) > 0:
            n += c
            c, self.src = self.src[0], self.src[1:]
            self.count()
        if len(self.src) != 0:
            self.src = c+self.src
        else:
            n += c
        dot = n.count('.')
        if dot == 0:
            t = 'i'
        elif dot == 1:
            t = 'r'
        else:
            raise LexingError('Too many "." in this number '+str(self.pos()))
        return Number(n, t, self.pos())

# In[Parser]:

# Parser

class Parser:
    def __init__(self, toklist):
        self.toklist = toklist
        self.tlen = len(self.toklist)
        self.cursor = 0
        self.types = []

    def tok(self, fail=0):
        'Passer au token suivant'
        if self.cursor < self.tlen - 1:
            self.cursor += 1
            return self.toklist[self.cursor]
        else :
            if fail:
                raise ParsingError('Unexpected end of file')
            return None

    def cur(self):
        'Token courant'
        return self.toklist[self.cursor]

    def nex(self):
        'Token suivant, permet l\'anticipation'
        if self.cursor < self.tlen - 1:
            return self.toklist[self.cursor +1]
        else :
            return None

    def parse(self):
        'Parser l\'ensemble d\'un fichier source'
        ast = Ast()
        block = self.parse_block()
        while block != None:
            ast.add(block)
            block = self.parse_block()
        return ast

    def parse_block(self, mode=0):
        'Parser un bloc a la racine du programme'
        if self.cur() == Eof():
            return None
        else:
            if self.cur() == Key('methyl'):
                 return self.parse_import()
            elif self.cur() == Key('helium'):
                raise ParsingError('Unimplemented option')
            elif self.cur() == Key('carbon'):
                raise ParsingError('Unimplemented option')
            elif self.cur() == Key('dec'):
                return self.parse_declare()
            elif self.cur() == Key('def'):
                if mode:
                    raise ParsingError('Function cannot be defined here : '
                            + str(self.cur().pos) )
                return self.parse_function()
            return self.parse_expr()

    def parse_expr(self):
        'Parser une expression'
        line = [self.parse_subexpr()]
        while type(self.cur()) == Op:
            line.append(self.cur())
            self.tok(1)
            line.append(self.parse_subexpr())
        return self.make_binexpr(line)

    def parse_subexpr(self):
        cur = self.cur()
        #Mots clés
        if cur == Key('if'):
            return self.parse_if()
        elif cur == Key('while'):
            return self.parse_while()
        #Liste d'expressions
        elif cur == Char('{'):
            self.tok(1)
            return self.parse_sequence()
        #Expressions paranthèsées
        elif cur == Char('('):
            self.tok(1)
            expr = self.parse_expr()
            if self.cur() != Char(')'):
                raise ParsingError('")" expected but ' + str(self.cur())
                    + ' found.')
            self.tok()
            return expr
        #Primitives
        elif type(cur) == Ident:
            nex = self.nex()
            if nex == Char('('):
                return self.parse_call()
            if nex == Char('='):
                return self.parse_alt()
            self.tok()
            return cur
        elif type(cur) == Number:
            self.tok()
            return cur
        #Operateurs unaires
        elif cur == Key('not'):
            self.tok(1)
            expr = self.parse_subexpr()
            return Unary(expr, 'not')
        elif cur == Op('-'):
            self.tok(1)
            expr = self.parse_subexpr()
            return Unary(expr, 'minus')
        #Echec
        raise ParsingError(str(cur)+' cannot be parsed as expression.')

    def parse_import(self):
        'Parser une importation de fichier methyl'
        cur = self.tok(1)
        self.tok()
        assert type(cur) == Ident, "Only identifier can be imported"
        return Methyl(cur.name)

    def parse_declare(self):
        'Parser une declaration de variable ou de fonction'
        cur = self.tok(1)
        if type(cur) != Ident:
            raise ParsingError(str(cur)+' cannot be an identifier.')
        ident = cur
        cur = self.tok(1)
        if cur != Char('='):
            raise ParsingError('Unexpected ' + str(self.nex()) + ' found.')
        self.tok(1)
        expr = self.parse_expr()
        return Declare(ident, expr)

    def parse_alt(self):
        'Parser un bloc alt'
        cur = self.cur()
        if type(cur) != Ident:
            raise ParsingError(str(cur)+' cannot be an identifier.')
        ident = cur
        cur = self.tok(1)
        if cur == Char('='):
            self.tok(1)
            expr = self.parse_expr()
            return Alt(ident, expr)
        elif cur == Char('['):
            pass
        else:
            raise ParsingError('Unexpected ' + str(cur) + ' found.')

    def parse_function(self):
        'Parser une declaration de variable ou de fonction'
        cur = self.tok(1)
        outtype = self.parse_type()
        cur = self.tok(1)
        if type(cur) != Ident:
            raise ParsingError(str(cur)+' cannot be used as function name.')
        ident = cur
        cur = self.tok(1)
        if cur != Char('('):
            raise ParsingError('Unexpected ' + str(cur) + ' found.')
        args = []
        cur = self.tok(1)

        while cur != Char(')'):
            if type(cur) != Ident:
                raise ParsingError('Identifier expected but ' + str(cur)
                    + ' found.')
            arg = cur
            if self.tok(1) != Op(':'):
                raise ParsingError('":" expected but ' + str(self.cur())
                    + ' found.')
            cur = self.tok(1)
            arg.type = self.parse_type()  #give a type to the argument
            args.append(arg)
            cur = self.tok(1)

        self.tok(1)
        define = []
        while self.cur() != Key('end'):
            define.append(self.parse_block(1))
        self.tok()
        return Function(ident, args, define, outtype)

    def parse_type(self):
        cur = self.cur()
        if cur not in TYPES and cur not in self.types:
            raise ParsingError(str(cur) + ' is not a type name.')
        
        else: #type(cur) == Ident
            return TYPES[cur]

    def parse_if(self):
        'Parser un bloc if'
        self.tok(1)
        cond = self.parse_expr()
        eif = self.parse_expr()
        if self.cur() == Key('else'):
            self.tok(1)
            eelse = self.parse_expr()
            return If(cond, eif, eelse)
        return If(cond, eif)

    def parse_while(self):
        'Parser un bloc while'
        self.tok(1)
        cond = self.parse_expr()
        loop = self.parse_expr()
        return While(cond, loop)

    def parse_call(self):
        'Parser un appelle de fonction'
        ident = self.cur()
        self.tok(1)
        self.tok(1)
        args = []
        while self.cur() != Char(')'):
            args.append(self.parse_expr())  # (expr, exprtype)
        self.tok()
        return Call(ident, args)


    def parse_sequence(self):
        'Parser une sequence d\'expression'
        exprs = []
        while self.cur() != Char('}'):
            exprs.append(self.parse_expr())
        self.tok()
        return Sequence(exprs)

    def make_binexpr(self, line):
        if len(line) == 1:
            return line[0]
        m = 1  #indice de l'operateur le moins prioritaire
        #on cherche l'operateur avec la plus petite priorité
        #A priorité egale on prend le plus à gauche sauf dans le cas de :
        #(operateur d'espace de nom)
        for i in range(3,len(line),2):
            if line[i].prec <= line[m].prec:
                if line[i].prec == line[m].prec and line[i].op == ":":
                    continue # on saute l'instruction m = i
                m = i

        left = self.make_binexpr(line[:m])
        right = self.make_binexpr(line[m+1:])
        op = line[m]
        return BinExpr(left,right,op)



# In[Checker]:


# Checker : Verifie la coherence de la source

class Checker:
    instance = -1
    CH3 = 1
    def __init__(self, basefunctable, basevartable, ast, name = '_main'):
        self.name = name
        self.ast = ast
        self.vartable = basevartable.copy()
        self.functable = basefunctable.copy()
        self.moduletable = {}
        self.localtable = [{}]
        Checker.instance += 1

    def check(self):
        for block in self.ast.nodes:
            t = type(block)
            if t == Declare:
                self.check_declare(block)
            elif t == Function:
                self.check_function(block)
            elif t == Methyl:
                self.check_methyl(block)
            else:
                block.type = self.check_expr(block)
            
        self.ast.scope = self.localtable[-1]
        return self.localtable[-1]


    def check_expr(self, b):
        t = type(b)
        if t == Number:
            return b.type
        elif t == Ident:
            return self.check_ident(b)
        elif t == BinExpr:
            return self.check_binexpr(b)
        elif t == Unary:
            return self.check_expr(b.expr)
        elif t == If:
            return self.check_if(b)
        elif t == While:
            return self.check_while(b)
        elif t == Call:
            return self.check_call(b)
        elif t == Sequence:
            return self.check_sequence(b)
        elif t == Alt:
            return self.check_alt(b)
        else:
            raise ParsingError('What the fuck !! '+str(b))

    def check_declare(self, b):
        t = self.check_expr(b.val)
        self.localtable[-1][b.ident.name] = t
        b.type = t

    def check_function(self, b):
        #on ajoute la fonction elle même (pour les appel récursif)
        self.functable[b.ident.name] = (Checker.instance, b.type)
        #on duplique la table locale
        self.localtable.append(self.localtable[-1])
        #on ajoute les arguments
        for ident in b.args:
            self.localtable[-1][ident.name] = ident.type
            b.type.append(ident.type)

       #on garde l'état du scope dans la fonction pour la compilation
        b.scope = self.localtable[-1]
        #on check la definition
        for block in b.define:
            t = type(block)
            if t == Declare:
                self.check_declare(block)
            else:
                self.check_expr(block)
        #on supprime la table locale
        print(self.localtable)
        self.localtable.pop()
        print(self.localtable)
    
    def check_methyl(self, b):
        'check l\'ensemble du fichier en question et ajoute les objets et fonction a la base de donnees'
        s = ''
        with open(b.file, 'r') as f:
            s = f.read()
        if s == '':
            raise 'File cannot be found or is empty'
        
        locpars = Parser(Lexer(s).tokenlist())
        locast = locpars.parse()
        loccheck = Checker(basefunctable, basevartable, locast, b.name)
        self.moduletable[b.name] = loccheck.check()
        self.functable.update(loccheck.functable)
        self.localtable[-1].update(loccheck.localtable[-1])
        print(loccheck.localtable[-1])
        locchainer = Chainer(locast)
        locchainer.chain_all()
        compiler = Compiler(basefunctable, basevartable, locchainer.chain,b.name )
        ccode = compiler.comp(locchainer.chain)
        with open(b.name + '.c', 'w') as f:
            f.write(ccode)

    def check_alt(self, b):
        t = self.check_expr(b.val)
        b.type = t
        name = b.ident.name
        if name in self.vartable:
            if t != self.vartable[name]:
                raise TypeError_('Wrong type for ' + str(b))
        elif name in self.localtable[-1]:
            if t != self.localtable[-1][name]:
                raise TypeError_('Wrong type for ' + str(b))
        else:
            raise NameError_('Unknown identifier '+str(b))
        return t

    def check_ident(self, b):
        if b.name in self.vartable:
            b.type = self.vartable[b.name]
        elif b.name in self.localtable[-1]:
            b.type = self.localtable[-1][b.name]
        elif b.name in self.moduletable:
            b.type = b.name
        else:
            raise NameError_('Unknown identifier '+str(b))
        return b.type

    def check_funcident(self, b, tpar):
        if b.name in self.functable:
            return self.functable[b.name][1]
        elif tpar is not None and b.name in self.moduletable[tpar]:
            return self.moduletable[tpar][b.name][1]
        raise NameError_('Unknown identifier '+str(b))

    def check_binexpr(self, b):
        
        tl = self.check_expr(b.left)
        if b.op == Op(':'):
            return self.check_call(b.right, tl)

        tr = self.check_expr(b.right)
        if b.op == Op('**'):
            if tl not in ('i', 'r') or tr not in ('i', 'r'):
                raise TypeError_('Both expressions have to be number '
                    + str(b.op.pos))
            b.type = 'r'
            return 'r'
        else:
            if tl != tr:
                raise TypeError_('Both expressions have to have the same type '
                    + str(b.op.pos))
            b.type = tl
            return tl

    def check_if(self, b):
        self.check_expr(b.cond)
        t = self.check_expr(b.exprif)
        if b.exprelse:
            if t != self.check_expr(b.exprelse):
                raise TypeError_('if and else blocks have to have the same type ' + b.pos())
        b.type = t
        return t

    def check_while(self, b):
        self.check_expr(b.cond)
        t = self.check_expr(b.loop)
        b.type = t
        return t

    def check_call(self, b, tparent=None):
        t = self.check_funcident(b.ident, tparent)
        for i in range(len(b.args)):
            targ = self.check_expr(b.args[i])
            if t[i+1] != targ:
                raise TypeError_('Wrong type in '+str(b))
        if tparent is not None and tparent != t[-1]:
            raise TyperError_(tparent + ' has not ' + b.ident + ' method')
        b.type = t[0]
        return t[0]

    def check_sequence(self, b):
        for expr in b.exprs:
            t = self.check_expr(expr)
        b.type = t
        return t


# In[Chainer]:

class Chain:
    def __init__(self):
        self.nodes = []

    def add(self, other):
        if type(other) == Chain:
            self.nodes += other.nodes
        else:
            self.nodes.append(other)

    def pop(self, val):
        if self.nodes[-1] == val:
            del self.nodes[-1]
    def __repr__(self):
        return str(self.nodes)

class Chainer:
    def __init__(self, ast):
        self.ast = ast
        self.chain = Chain()

    def chain_all(self):
        'Chainer l\'ensemble de l\'ast'
        for node in self.ast.nodes:
            t = type(node)
            if t == Declare:
               self.chain_declare(node)
            elif t == Function:
                self.chain_function(node)
            elif t == Methyl:
                self.chain.add(('methyl', node.name))
            else:
                self.chain_expr(node)
                self.chain.add(('flevel',))

    def chain_function(self, node):
        self.chain.add(('function', node.ident.name
            , [ arg.name for arg in node.args], node.type))
        for block in node.define:
            if type(block) == Declare:
               self.chain_declare(block)
            else:
                self.chain_expr(block)
                self.chain.add(('flevel',))
        self.chain.pop(('flevel',))
        self.chain.add(('endfunc',))

    def chain_declare(self, node):
        self.chain_expr(node.val)
        node.val = None
        self.chain.add(('declare',node.ident.name, node.type))

    def chain_alt(self, node):
        self.chain_expr(node.val)
        node.val = None
        self.chain.add(('alt',node.ident.name))

    def chain_expr(self, node):
        t = type(node)
        if t == Number:
            self.chain.add(('number', node.val))
        elif t == Ident:
            self.chain.add(('ident', node.name))
        elif t == BinExpr:
            self.chain_binexpr(node)
        elif t == Unary:
            self.chain_expr(node.expr)
            self.chain.add(('unary', node.mode))
        elif t == If:
            self.chain_if(node)
        elif t == While:
            self.chain_while(node)
        elif t == Call:
            self.chain_call(node)
        elif t == Sequence:
            self.chain_sequence(node)
        elif t == Alt:
            self.chain_alt(node)
        elif t == Array:
            self.chain_array(node)
        else:
            raise ParsingError('What the fuck ?! '+str(node))

    def chain_binexpr(self, node):
        self.chain_expr(node.right)
        self.chain_expr(node.left)
        self.chain.add(('binexpr', node.op.code))

    def chain_if(self, node):
        self.chain_expr(node.cond)
        #On empile le node if avant les expression a evalué pour
        #compilé de façon adapté
        self.chain.add(('if',node.type))
        self.chain_expr(node.exprif)
        if node.exprelse:
            # pseudo node pour marquer le changement
            self.chain.add(('else',))
            self.chain_expr(node.exprelse)
        # pseudo node pour marquer la fin du block
        self.chain.add(('endblock',))

    def chain_while(self, node):
        # la demarche est exactement la meme que pour le if
        self.chain_expr(node.cond)
        self.chain.add(('while',node.type))
        self.chain_expr(node.loop)
        self.chain.add(('endblock',))

    def chain_call(self, node):
        for arg in node.args:
            self.chain_expr(arg)
        self.chain.add(('call', node.ident.name, len(node.args)))

    def chain_sequence(self, node):
        for i in range(len(node.exprs)-1):
            self.chain_expr(node.exprs[i])
            #pseudo node, marque la limite entre deux membres d'une sequence
            self.chain.add(('seqsep',))
        self.chain_expr(node.exprs[-1])
# In[Compiler]:

def makeC(op):
    if op == 'or':
        return '||'
    elif op == 'and':
        return '&&'
    elif op == 'minus':
        return '-'
    elif op == 'not':
        return '!'
    return op

def makeCtype(t):
    return CTYPES[t]

# Compiler

class Compiler:
    def __init__(self, basefunctable, basevartable, chain, name = '_main'):
        self.name = name
        self.chain = chain
        self.vartable = basevartable.copy()
        self.functable = basefunctable.copy()
        self.localtable = [[]]
        self.mode = 0 #0 maincode 1 funcode/methylcode
        self.indent = 1
        self.imp = set()
        self.importcode = ''
        self.funcode = ''
        self.maincode = ''
        self.stack = []
        self.blocknb = 0
        self.blockres = ''

    def push(self, var):
        self.stack.append(var)

    def pop(self):
        return self.stack.pop()

    def cntblock(self):
        self.blockres = 'B_' + str(self.blocknb)
        self.blocknb += 1

    def write(self, code, offset=0):

        if self.mode:
            self.funcode += '    '*(self.indent+offset-1) + code
        else:
            self.maincode += '    '*(self.indent+offset-(self.name != '_main')) + code

    def make_import(self):
        for m, file in self.imp:
            if m == 0 :
                self.importcode += '#include <' + file + '>\n'
            else:
                self.importcode += '#include "' + file + '"\n'

    def comp(self, chain):
        for node in chain.nodes:
            t = node[0]
            if t == 'declare':
                self.comp_declare(node)
            elif t == 'function':
                self.mode = 1 
                self.comp_function(node)
            elif t == 'methyl':
                self.imp.add((1, node[1] + '.c'))
            #pseudo node
            elif t == 'else':
                self.write(self.blockres + ' = ' + self.pop() + ';\n')
                self.write('}else{\n', -1)
            elif t == 'endblock':
                self.write(self.blockres + ' = ' + self.pop() + ';\n')
                self.push(self.blockres)
                self.indent -= 1
                self.write('}\n')
            elif t == 'endfunc':
                self.write('return {};\n'.format(self.pop()))
                self.indent -= 1
                self.write('}\n')
                self.mode = 0
            elif t == 'begar':
                self.push('{')
            elif t == 'endar':
                a = self.pop()
                b = self.pop()
                self.push(b + a + '}')
            elif t == 'seqsep':
                self.write(self.pop() + ';\n')
            elif t == 'arsep':
                a = self.pop()
                b = self.pop()
                self.push(b + a + ',')
            elif t == 'flevel':
                self.write(self.pop() + ';\n')
            #expr
            else:
                self.comp_expr(node)
        self.make_import()
        if self.name != '_main':
            return METHYLCODE.format(self.name, self.funcode, self.maincode)
        return BASECODE.format(self.importcode, self.funcode, self.maincode)

    def comp_expr(self, node):
        t = node[0]
        if t == 'number':
            self.push(node[1])
        elif t == 'ident':
            self.push(node[1])
        elif t == 'binexpr':
            self.comp_binexpr(node)
        elif t == 'unary':
            self.push(makeC(node[1]) + self.pop())
        elif t == 'if':
            self.comp_if(node)
        elif t == 'while':
            self.comp_while(node)
        elif t == 'call':
            self.comp_call(node)
        elif t == 'alt':
            self.comp_alt(node)

    def comp_declare(self, node):
        self.write('{} {} = {};\n'.format(makeCtype(node[2]), node[1], self.pop()))

    def comp_function(self, node):
        code = '{} {} ('.format(makeCtype(node[3][0]), node[1])
        for arg,t in zip(node[2],node[3][1:]):
            code += '{} {},'.format(makeCtype(t), arg)
        self.write(code[:-1] + '){\n')
        self.indent += 1

    def comp_alt(self, node):
        self.write('{} = {};\n'.format(node[1], self.pop()))
        self.push(node[1])

    def comp_call(self, node):
        if node[1] in basefunctable:
            call = ' )'
            for i in range(node[2]):
                call = ', ' + self.pop() + call
            call = basefunctable[node[1]][0] + call[2:]
            self.push(call)
        else:
            call = ' )'
            for i in range(node[2]):
                call = ', ' + self.pop() + call
            call = node[1] + '( ' + call[2:]
            self.push(call)

    def comp_binexpr(self, node):
        l, r = self.pop(), self.pop()
        if node[1] == '**':
            self.imp.add((0, 'math.h'))
            self.push('pow( {}, {} )'.format(l, r))
        else:
            self.push(l + ' ' + makeC(node[1]) + ' ' + r)

    def comp_if(self, node):
        self.cntblock()
        self.write('//block ' + str(self.blockres) + '\n')
        self.write('{} {} = 0;\n'.format(makeCtype(node[1]), self.blockres))
        self.write('if( {} ){{\n'.format(self.pop()))
        self.indent += 1

    def comp_while(self, node):
        self.cntblock()
        self.write('//block ' + str(self.blockres) + '\n')
        self.write('{} {} = 0;\n'.format(makeCtype(node[1]), self.blockres))
        self.write('while( {} ){{\n'.format(self.pop()))
        self.indent += 1

# In[Test]:
# basefunctable :
#  contenu --> nom helium : (code C, [type out, type arg1,...])
basefunctable = {'print': (r'printf("%i\n", ',['i','i'])}
basevartable = {}
from sys import argv, stdin, setrecursionlimit

setrecursionlimit(100)

if len(argv) > 1:
    if argv[-1] == "-":
        SRC = stdin.read()
    else:
        with open(argv[-1]) as f:
            SRC = f.read()

    parser = Parser(Lexer(SRC).tokenlist())
    ast = parser.parse()
    checker = Checker(basefunctable, basevartable, ast)
    checker.check()
    chainer = Chainer(ast)
    chainer.chain_all()


    compiler = Compiler(basefunctable, basevartable, chainer.chain)

    print(compiler.comp(chainer.chain))
else:
    print("Usage :")
    print("\t" + argv[0] + " [infile][-]")

Je compte sur vous pour me faire vos critiques et si vous en avait vos suggestions.

Édité par WIP

+3 -0
Staff

Pourquoi tu n'utilises pas PLY plutôt que de recoder ton lexeur et le parseur de ta grammaire LALR à la main ?

Édité par nohar

I was a llama before it was cool

+1 -0

Salut,

Je mène un projet similaire depuis quelques temps, mais tu m'as largement dépassé :) . Bonne continuation j'ai hâte de voir le résultat.

AZ.

PS: @nohar: Parce que c'est fun peut-être ? :P

Anciennement AlphaZeta

+2 -0
Staff

Si tu veux gagner du temps, une solution plus propre que de générer du C serait d'utiliser llvm qui peut etre manipulé en python et te permet d'emmetre vers le langage intermédiaire sans trop de soucis.

Ensuite c'est assez étrange de faire ton lexeur a la main. Autant en C je pourrais comprendre (c'est facile a faire), autant en python ca risque de vite devenir lent.

Bon courage en tout cas

+1 -0
Staff

Au passage :

(Existe-il une balise qui masque le contenu ? Ou alors est-ce que vous avez un bon hébergeur pour du code sans durée maximale )

  • Oui, la balise secret : selectionne le texte que tu veux masquer et clique sur l'icone la plus en haut à droite de l'éditeur puis clique sur le dernier élément de la liste (la tooltip dit "bloc masqué")
  • Sinon pour poster des extraits de codes, gist est pas mal
+1 -0

Je ne doute pas qu'on puisse penser y voir quelque chose de noble ou de marrant, m'enfin c'est surtout casse-gueule.

Pourquoi ? Mon lexer/parser marche assez bien, après je me suis pas encore rendu compte de ses désavantages car je ne l'ai pas encore fini. J'ai pas encore vu les problèmes que ça pouvait engendrer quand on fait un vrai langage (mon but final). J'ai déjà testé ANTLR4, PLY ou PyBison mais comme la création d'un langage est un domaine qui m'est complètement inconnu, j'ai préféré faire mon propre code que je maîtrise. Et surtout qui pallie aux soucis que j'ai rencontré lors de ma courte expérience avec ces librairies, sans doute à cause de ma méconnaissance de l'API. Devrais-je changer tout de suite pour une lib plus pro ?

Que l'OP dise s'il veut qu'on arrête de "polluer" le post :)

Édité par felko

Anciennement AlphaZeta

+0 -0
Staff

Pourquoi ?

Parce que quand on crée un langage, on passe son temps à modifier sa grammaire et son vocabulaire.

Il est beaucoup plus facile de modifier (ou faire grandir) une grammaire qui s'exprime naturellement par des règles comme dans Yacc (donc PLY), ou des tokens qui s'expriment par des regex, plutôt que de patcher régulièrement un truc fait à la mano.

Je ne suis pas un grand spécialiste du domaine, par contre j'ai bossé exclusivement toute l'année dernière sur un compilateur à mon boulot. PLY m'a permis de me concentrer sur les parties vraiment importantes du compilateur : l'analyse sémantique, la représentation intermédiaire (le design des AST notamment), et la production de code final.

Maintenir/faire évoluer le lexeur et le parseur, quand on commence à faire un vrai langage, ça devrait représenter même pas 10% du temps. Écrire un compilateur correct est déjà suffisamment difficile comme ça sans qu'on ait en plus besoin de se battre avec son propre code. ;)

Édité par nohar

I was a llama before it was cool

+3 -0
Staff

Ouais enfin je me méfie des trucs de ce genre pour écrire le parseur d'un langage de programmation, contrairement à PLY dont le fonctionnement est calqué sur des règles établies, documentées et étudiées depuis un bon demi-siècle, et pour lesquelles on peut se référer au dragon book.

Édité par nohar

I was a llama before it was cool

+0 -0
Auteur du sujet

Salut, D'abord merci pour toute vos réponse ça me fait vraiment plaisir d'avoir autant de réaction. Ensuite en ce qui concerne mon choix d'écrire le lexer et le parser je savais que j'aurais ce genre de commentaire :), j'ai donc plusieurs chose à répondre :

  • Mon lexer est fait de façon à ce que je puisse y ajouter très rapidement un nouveau mot clé ou un nouvel opérateur qui sont les deux seuls objets susceptible d'etre encore ajouté plus tard.

  • Mon parser est en revanche plus rigide je l'avoue mais la structure du code et de la représentation intermédiaire devrait me permettre de ne pas me faire trop ch***.

  • J'ai déjà essayer OCamlYacc/OCamllex et Bison/Flex mais je ne maitrise pas suffisament OCaml pour faire ce que je veux et C est trop lourd à mon gout. En ce qui concerne PLY je l'ai aussi utilisé et j'aurais pu me lancer avec mais comme je l'ai dit dans ma présentation la conception complète du compilateur et aussi importante que la création du langage. En effet je n'ai pas la prétention de créer un langage mieux que ceux existant, seulement un langage qui marche avec un compilo qui marche.

  • Dans mes précédentes expériences ou j'ai utilisé les outils consacré je me suis heurté au traitement de l'AST qui avait une structure d'arbre. Le fait de passer par toute les étapes de l'analyse du code est extrement pédagogique et j'ai beaucoup apris en codant tout ça donc même si c'est potentielement de la prise de tête en perspective j'aime bien cette démarche.

  • Je fait tout ça pour le fun donc la productivité n'est pas une contrainte.

A part ça vous me parler de Git, je connais mais je ne pense pas que mon code mérite encore une page git. Je veux juste donner accés à mon code pour ce topic donc la balise secret suffirat.

En ce qui concerne LLVM je me suis déjà un peu intéresser à ça et j'avoue que ça me plait vraiment bien. J'ai juste pas eu le courage d'apprendre à m'en servir mais comme je suis pas spécialement presser, si vous avait une bonne adresse pour apprendre la syntaxe je suis preneur (j'ai déjà essayer de faire le tuto avec OCamlyacc sur le site officiel mais c'est pas ce qu'il me faut) si possible en français même si je peux débrouiller en anglais.

N'hésitez pas à tester mon code et à me dire ce que vous pensez de la structure, de la façon dont je fait telle ou telle chose etc…

+0 -0

A part ça vous me parler de Git, je connais mais je ne pense pas que mon code mérite encore une page git. Je veux juste donner accés à mon code pour ce topic donc la balise secret suffirat.

git est un gestionnaire de versions, qui te permet de garder un hitorique des modifications apportées à ton code et de faciliter le travail collaboratif. Github est un site web qui propose d'héberger des dépôts git (entre autres choses). Attention à ne pas confondre les 2 choses. Si tu peux te passer de Github, l'utilisation d'un gestionnaire de versions est très conseillée, quel que soit le projet.

Mon lexer est fait de façon à ce que je puisse y ajouter très rapidement un nouveau mot clé ou un nouvel opérateur qui sont les deux seuls objets susceptible d'etre encore ajouté plus tard.

Les outils que tu cites permettent ça. Si tu veux te passer de ces outils et tout faire à la main parce que tu trouves ça plus intéressant, rien ne t'en empêche. En revanche, ton code sera certainement plus difficilement maintenable et moins lisible (un yacc est une transcription immédiate de la grammaire de ton langage). Si le problème est que tu trouves ces outils difficiles à utiliser, essaie quand même de persévérer. Je ne connais pas les équivalents Python de lex et yacc, mais si tu as des questions, je pense que pas mal de gens sur ce forum seront prêtes à y répondre.

Édité par Bibibye

Auteur du sujet

git est un gestionnaire de versions, qui te permet de garder un hitorique des modifications apportées à ton code et de faciliter le travail collaboratif. Github est un site web qui propose d'héberger des dépôts git (entre autres choses). Attention à ne pas confondre les 2 choses. Si tu peux te passer de Github, l'utilisation d'un gestionnaire de versions est très conseillée, quel que soit le projet.

Merci pour cette précision je n'étais en effet pas trop au courant de ce qu'étais git. Pour l'instant je ne pense pas que ce soit très utile d'utiliser un gestionnaire de version, je fait des sauvegarde de temps en temps et tout mon code tien sur un seul fichier mais quand le tout prendras de l'ampleur je verrais comment marche git.

Les outils que tu cites permettent ça. Si tu veux te passer de ces outils et tout faire à la main parce que tu trouves ça plus intéressant, rien ne t'en empêche. En revanche, ton code sera certainement plus difficilement maintenable et moins lisible (un yacc est une transcription immédiate de la grammaire de ton langage). Si le problème est que tu trouves ces outils difficiles à utiliser, essaie quand même de persévérer. Je ne connais pas les équivalents Python de lex et yacc, mais si tu as des questions, je pense que pas mal de gens sur ce forum seront prêtes à y répondre.

La programmation du lexer et du parser est vraiment un choix assumé malgré tout les défauts que cela peu avoir. En fait j'ai eu plusieurs expérience avec ces outils en générale avec un tuto : La première fois j'ai testé bison/flex, j'ai rien compris et j'ai quasiment rien fait. La fois suivante j'ai essayer OCamlYacc et OCamlLex, j'ai réussi à faire un petit truc mais ma syntaxe était mal pensé et j'ai abandonné avant d'avoir un parser qui marche. Ensuite j'ai essayer PLY j'ai fait un parser qui marchait mais je savais pas comment traité mon AST. Ensuite je suis revenu sur OCaml et j'ai encore bloqué sur l'AST mais j'ai encore compris plein de truc. En fait à chaque fois ça m'a fait beaucoup progressé et ce projet m'a paru une bonne façon d'aller encore plus loin.

Aujourd'hui je relance mes recherche sur LLVM donc le code python va pas bouger pendant un certain temps. Si je trouve des trucs intéressant je les posterais ici pour ceux que ça intéresse aussi.

+0 -0
Auteur du sujet

Une présentation assez intéressante de la syntaxe de l'IR au paragraphe "Understanding the LLVM IR" :

http://www.llvmpy.org/llvmpy-doc/0.12.7/doc/llvm.core.Builder.html

La doc de llvmpy, un outils pour générer le code IR :

http://www.llvmpy.org/llvmpy-doc/0.12.7/index.html

J'étudis tous ça en ce moment je pense que llvmpy sera parfait pour mon projet.

Édité par WIP

+1 -0
Auteur du sujet

J'ai laissé tombé l'utilisation de llvmpy ne pouvant pas l'installer par AUR. J'ai choisi finalement de générer le code directement comme je le faisais en C.

Je suis en train de fouiller la doc de llvm IR pour faire une fiche récapitulative des éléments essentiel pour générer le code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Operation :
[op] [type] arg1, arg2
  [op] binaire :
      shl
      ashr
      lshr
      and
      or
      xor
  [op] pour [type] = iXX :
      mul
      add
      sub
      sdiv # s signed, u unsigned
      udiv
      urem # reminder 
      srem
  [op] pour [type] = float/double :
      fmul
      fadd
      fsub
      fdiv
      frem 

Variable
  %name = alloca [type]
  store [type] [valeur], [type]* %name
Tableau
  Creation
      %name = alloca [size x type]
  Modification d'une case (name[offset] en C)
      %1 = getelementptr inbounds [size x type]* %name, i32 0, i64 [offset]
      store [type] 1, [type]* %1

Cast
  Int en Char
      %2 = trunc i32 %1 to i8
  Char en Int signé
      %2 = sext i8 %1 to i32
  Char en Int non signé
      %2 = zext i8 %1 to i32
  Double en Float
      %2 = fptrunc double %1 to float
  Float en Double
      %5 = fpext float %4 to double
  entier signé en flottant
      %2 = sitofp/uitofp iXX %1 to double/float
  flottant en entier
      %2 = fptosi/fptoui float/double %1 to iXX

Saut inconditionel
  br label %lab
  ...
  lab:
      ...

Saut conditionel
  br i1 <cond>, label %labif, label %labelse
  ...
  labif:
      ...
  labelse:
      ...

+0 -0
Auteur du sujet

Je suis toujours là ! Je bosse sur ce projet de façon très occasionel car je n'ai vraiment pas beaucoup de temps à lui consacrer mais je ne l'oublie pas. Actuellement j'ai eu des problèmes avec llvm IR dû au fait qu'il n'est pas pensé pour être généré comme je veux le faire mais en utilisant l'API C et surtout au fait que je ne le maitrise pas. Ça m'a fait penser que je serais finalement beaucoup plus efficace avec le C (peut être C++ plus tard). J'ai donc repris mon ancien code et retravailler la façon dont je génère le code pour faire un truc plus souple et plus propre. J'ai aussi corriger un certain nombre d'erreurs et de maladresses. Avant j'avais un système de stack avec des fonctions POP et PUSH dans le code C. Maintenant j'ai des méthodes pop et push dans la classe Compiler et le code généré ressemble à du code écrit à la main :

Avant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// This code was generated by the Helium Compiler

//Import
#include "helium.h"

//

x//Definition of functions
float fac(float n,float acc){
    Elem * top = NULL;
    PUSH(1);
    PUSH(n);
    PUSH(POP()>POP());
    if(POP()){
        PUSH(1);
        PUSH(n);
        PUSH(POP()-POP());
        PUSH(n);
        PUSH(acc);
        PUSH(POP()*POP());
        PUSH(fac(POP(),POP()));
    }else{
        PUSH(acc);
    }
    float res = POP();
    freestack(top);
    return res;
}

//

//Main code
int main(int argc, char** argv){
    Elem * top = NULL;
        PUSH(1);
    float i = POP();
    PUSH(i);
    PUSH(1);
    PUSH(fac(POP(),POP()));
    PUSH(1000);
    PUSH(POP()>POP());
    while(POP()){
        PUSH(1);
        PUSH(i);
        PUSH(POP()+POP());
        i = POP();
        PUSH(i);
        PUSH(1);
        PUSH(fac(POP(),POP()));
        PUSH(1000);
        PUSH(POP()>POP());
    }
    PUSH(i);
    PUSH(print(POP()));

    freestack(top);
    return 0;
}

Après :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// This code was generated by the Helium Compiler

//Import
#include <stdlib.h>
#include <stdio.h>

//

//Definition of functions
float fac(float n,float acc){
    //block B_0
    float B_0 = 0;
    if( n > 1 ){
        B_0 = fac( n - 1, acc * n );
    }else{
        B_0 = acc;
    }
    return B_0;
}

//

//Main code
int main(int argc, char** argv){
    float i = 1;
    //block B_1
    float B_1 = 0;
    while( 1000 > fac( i, 1 ) ){
        i = i + 1;
        B_1 = i;
    }
    printf("%f\n", i );

    return 0;
}

Accessoirement la syntaxe à un tout petit peu changer juste pour correspondre à un meilleur anglais ( let est devenu dec (declare) et set est devenu alt (alter))

Le compilateur est maintenant complet au niveau des flottants. Je vais donc maintenant réfléchir à une façon plus élaboré de gérer la mémoire pour ajouter les deux premiers types : l'entier et le tableau.

Édité par WIP

+0 -0
Auteur du sujet

Salut, J'ai eu l'occasion de rebosser un peu sur mon projet, j'ai mis à jour le premier post. J'ai pas réussi à faire quelque chose de satisfaisant pour les tableaux mais le système de type est en place pour les flotants et les entier (real et int). Par contre j'ai ajouter un système de module : les methyl (ouais je suis en math spé physique chimie) et j'ai préparer le terrain pour faire des bindings C : les carbon (élément de symbole C). Cela dit ça fait encore bricolage il faut que je retravaille ça. Le code complet est dans le premier post.

Édité par WIP

+1 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte