From 6bdc8cce0985fecc573a247312f3cb6ee3ca33f5 Mon Sep 17 00:00:00 2001 From: Egor Vorontsov Date: Thu, 11 Jan 2024 02:55:07 +0300 Subject: [PATCH] Latest changes back from 2021, before starting from scratch. --- .gitattributes | 2 + .gitignore | 11 +- README.md | 1 - SBC/builtins.c | 50 -- SBC/bytecode.md | 171 ---- SBC/dissbc.py | 50 -- SBC/sbc | Bin 22264 -> 0 bytes SBC/sbc.c | 217 ----- SBC/stack.c | 53 -- SBC/tests/hw.sbc | Bin 26 -> 0 bytes Slang.md | 63 +- Slang.py | 34 +- TODO.md | 14 +- asm/hw | Bin 6032 -> 0 bytes asm/hw.asm | 16 - asm/hw.c | 1 - asm/hw.o | Bin 768 -> 0 bytes ast.py | 1122 ++++++++++++++++++-------- compilers/__init__.py | 38 +- compilers/esolang/gibberish.py | 6 +- compilers/native/__init__.py | 13 + compilers/native/x86_64.py | 235 ++++++ compilers/pyssembly/.gitignore | 1 + compilers/pyssembly/__init__.py | 67 +- compilers/pyssembly/genstdlib.py | 181 +++++ compilers/pyssembly/requirements.txt | 1 + compilers/pyssembly/std.py | 3 + compilers/pyssembly/typeshed | 2 +- compilers/sbc.py | 351 -------- compilers/sbc/__init__.py | 289 +++++++ lexer.py | 10 +- repl.py | 164 +++- sl/Slang/Slang.sl | 3 +- sl/Slang/lexer.sl | 48 +- sl/Slang/tokens.sl | 46 +- slang.lang | 5 +- sld.py | 2 +- stdlib.py | 132 +-- stdlib/std.sld | 4 +- tests/class.sl | 18 +- tests/colonkw.sl | 7 + tests/divzero.sl | 7 + tests/each.sl | 8 + tests/example.sl | 22 +- tests/fib.sl | 2 + tests/for.sl | 2 + tests/funcdef.sl | 2 + tests/list.sl | 4 + tests/{fmap.sl => map.sl} | 2 + tests/nestedcomm.sl | 5 + tests/nl.sl | 4 +- tests/opti.sl | 13 +- tests/overload.sl | 3 + tests/println.sl | 7 +- tests/range.sl | 5 + tests/redefine.sl | 4 +- tests/scope.sl | 16 + tests/strcat.sl | 2 + tests/sum.sl | 8 +- tests/test.sl | 15 +- tests/tuple.sl | 6 + tests/underscore.sl | 4 +- tests/wrongnl.sl | 4 +- tokens.py | 114 +-- 64 files changed, 2113 insertions(+), 1577 deletions(-) create mode 100644 .gitattributes delete mode 120000 README.md delete mode 100644 SBC/builtins.c delete mode 100644 SBC/bytecode.md delete mode 100755 SBC/dissbc.py delete mode 100755 SBC/sbc delete mode 100644 SBC/sbc.c delete mode 100644 SBC/stack.c delete mode 100644 SBC/tests/hw.sbc delete mode 100755 asm/hw delete mode 100644 asm/hw.asm delete mode 100644 asm/hw.c delete mode 100644 asm/hw.o create mode 100644 compilers/native/__init__.py create mode 100644 compilers/native/x86_64.py create mode 100644 compilers/pyssembly/.gitignore create mode 100755 compilers/pyssembly/genstdlib.py create mode 100644 compilers/pyssembly/requirements.txt delete mode 100644 compilers/sbc.py create mode 100644 compilers/sbc/__init__.py create mode 100644 tests/colonkw.sl create mode 100644 tests/divzero.sl create mode 100644 tests/each.sl rename tests/{fmap.sl => map.sl} (72%) create mode 100644 tests/nestedcomm.sl create mode 100644 tests/range.sl create mode 100644 tests/scope.sl create mode 100644 tests/tuple.sl diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0eb0036 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.sl linguist-detectable=false +*.sld linguist-detectable=false diff --git a/.gitignore b/.gitignore index 9df6826..6a1ce14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ -old.c -**.db -**.log -**.pyc -**.old -**.bak -**__pycache__ -**_config.py \ No newline at end of file +*.old +*.bak +__pycache__/ diff --git a/README.md b/README.md deleted file mode 120000 index 197480c..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -Slang.md \ No newline at end of file diff --git a/SBC/builtins.c b/SBC/builtins.c deleted file mode 100644 index 6320749..0000000 --- a/SBC/builtins.c +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include - -typedef struct atom { - enum {b, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128} type; - union { - _Bool* b; - int8_t* i8; - uint8_t* u8; - int16_t* i16; - uint16_t* u16; - int32_t* i32; - uint32_t* u32; - int64_t* i64; - uint64_t* u64; - void* data; - }; -} atom_t; - -typedef atom_t (*builtin_function)(int nargs, atom_t args[nargs]); - -typedef struct builtin { - const char* name; - builtin_function fp; -} builtin_t; - - -/// XXX /// - - -atom_t _builtin_println(int nargs, atom_t args[nargs]) { - static int res; - res = puts(args[0].data); - return (atom_t){i32, .i32 = &res}; -} - - -/// XXX /// - - -builtin_t builtins[] = { - {"println", _builtin_println}, -{NULL, NULL}}; - -builtin_function get_builtin(const char* name) { - for (builtin_t* i = builtins; i->name != NULL; i++) - if (strcmp(i->name, name) == 0) - return i->fp; - return NULL; -} diff --git a/SBC/bytecode.md b/SBC/bytecode.md deleted file mode 100644 index 9b83a46..0000000 --- a/SBC/bytecode.md +++ /dev/null @@ -1,171 +0,0 @@ -# Slang Bytecode - - -## Standalone - -### NOP -0x00 -> Does nothing. -### END -0x01 -> Closes previously opened block. -### POP -0x02 -> Drops `TOS`. -### RET -0x03 -> Returns `TOS` to caller. -### BLTIN -0x04 -> Reads string from the bytecode until null byte (max 255 bytes) and pushes builtin function with that name. -### CODE -0x05 -> Reads code from the bytecode until the corresponding `END` instruction and pushes a reference to it and then its length. -> Opens a block. - - -## Unary - -### POS -0x10 -> Pushes `abs(TOS)`. -### NEG -0x11 -> Pushes `-TOS`. -### NOT -0x12 -> Pushes `!TOS`. -### INV -0x13 -> Pushes `~TOS`. -### ATOI -0x14 -> Pushes integer of smallest possible width parsed from string `TOS`. -### ITOA -0x15 -> Pushes string representation of integer `TOS`. -### ITOF -0x16 -> Pushes real of smallest possible width equal to integer `TOS`. -### CEIL -0x17 -> Pushes smallest integer of smallest possible width greater or equal to real `TOS`. -### FLR -0x18 -> Pushes largest integer of smallest possible width less or equal to real `TOS`. -### RND -0x19 -> Pushes integer of smallest possible width equal to rounded `TOS`. -### CTOS -0x1A -> Pushes string consisting of char `TOS` and a null byte. - - -## Binary - -### ADD -0x20 -> Pushes `TOS1 + TOS`. -### SUB -0x21 -> Pushes `TOS1 - TOS`. -### MUL -0x22 -> Pushes `TOS1 * TOS`. -### DIV -0x23 -> Pushes `TOS1 / TOS`. -### IDIV -0x24 -> Pushes `TOS1 // TOS`. -### MOD -0x25 -> Pushes `TOS1 % TOS`. -### POW -0x26 -> Pushes `TOS1 ** TOS`. -### LSH -0x27 -> Pushes `TOS1 << TOS`. -### RSH -0x28 -> Pushes `TOS1 >> TOS`. -### AND -0x29 -> Pushes `TOS1 & TOS`. -### OR -0x2A -> Pushes `TOS1 | TOS`. -### XOR -0x2B -> Pushes `TOS1 ^ TOS`. - - -## Comparisons - -### EQ -0x30 -> Pushes `TOS1 == TOS`. -### NE -0x31 -> Pushes `TOS1 != TOS`. -### LT -0x32 -> Pushes `TOS1 < TOS`. -### GT -0x33 -> Pushes `TOS1 > TOS`. -### LE -0x34 -> Pushes `TOS1 <= TOS`. -### GE -0x35 -> Pushes `TOS1 >= TOS`. -### IS -0x36 -> Pushes `TOS1 is TOS`. -### ISNOT -0x37 -> Pushes `TOS1 is not TOS`. - - -## Flow control -### IF -0x40 -> If `TOS` is false, skips bytecode until corresponding `ELSE` (if exists) or `END`. -> Opens a block. -### ELSE -0x41 -> Pops last `IF` result from `IF`-stack, and if it is true, skips bytecode to corresponding `END`. -> Opens a block. -### EXEC -0x42 -> Executes code block `TOS1` of length `TOS` and pushes the result. - - -## With argument - -### ALLOC*(bytes)* -0xA0 -> Pushes reference to `calloc(1, bytes)`. -### EXTEND*(bytes)* -0xA1 -> Extends integer `TOS` width to `bytes` bytes if narrower. -### CONST*(bytes)* -0xA2 -> Reads next `bytes` bytes of bytecode and pushes a reference to a copy of them. -### JUMPF*(offset)* -0xA3 -> Jumps `offset` bytes of bytecode forward. -### JUMPB*(offset)* -0xA4 -> Jumps `offset` bytes of bytecode backward. -### SCPGET*(cell)* -0xA5 -> Pushes the value of cell `cell` of local scope variables array. -### SCPSET*(cell)* -0xA6 -> Sets the value of cell `cell` of local scope variables array to `TOS`. -### CALL*(nargs)* -0xA7 -> Calls `TOS` with `nargs` arguments popped from stack (below the callable). diff --git a/SBC/dissbc.py b/SBC/dissbc.py deleted file mode 100755 index 7441c1a..0000000 --- a/SBC/dissbc.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/python3 -# SBC Disassembler - -from utils.nolog import * - -opcodes = {int(m[2], 16): m[1] for m in re.finditer(r'### (\w+).*?\n(\w+)', open(os.path.join(os.path.dirname(sys.argv[0]), 'bytecode.md')).read())} -HASARG = 0xa0 - -def dissbc(code, no_opnames=False): - cp = 0 - codesize = len(code) - blocklvl = 0 - while (cp < codesize): - opcode = code[cp]; cp += 1 - opname = opcodes.get(opcode) - if (opname == 'END'): blocklvl -= 1 - print('\t'*blocklvl, end='') - if (opname is None): print("\033[2mUNKNOWN: %02x\033[0m" % opcode); continue - print(f"\033[1m0x{opcode:02x}{' '+opname if (not no_opnames) else ''}\033[0m", end='') - if (opcode > HASARG): - arg = code[cp]; cp += 1 - print("(%d|0x%02x)" % (arg, arg), end='') - - if (opname == 'CONST'): - const = code[cp:cp+arg] - cp += arg - print(':', '0x'+const.hex(), end='') - print(' |', str().join(chr(i) if (32 <= i < 128) else '.' for i in const), end='') - elif (opname == 'BLTIN'): - name = bytearray() - while (code[cp] != 0): - name.append(code[cp]); cp += 1 - else: cp += 1 - print(':', name.decode('ascii'), end='') - - if (opname in ('IF', 'ELSE', 'CODE')): - print(':', end='') - blocklvl += 1 - - print() - -@apmain -@aparg('file', metavar='') -@aparg('--no-opnames', action='store_true') -def main(cargs): - dissbc(open(cargs.file, 'rb').read(), no_opnames=cargs.no_opnames) - -if (__name__ == '__main__'): exit(main(nolog=True), nolog=True) - -# by Sdore, 2020 diff --git a/SBC/sbc b/SBC/sbc deleted file mode 100755 index f245d59abb6ede8df2f49e9e87c26ac0df73cc3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22264 zcmeHPeQ;aVmA|s0#0iP*grs!?Eov5TlWlFBPn=MLl0v6rV9NxSriKA3#S{Vs>e4pa-?{f5 z={;GZO#8>q?C6fAbI#J7fB8!%3XP8>`C^m^&rI%P|r!^D+q$W*SkHSHE6IyRV>oFbE zfndtQa->B=$7ngGF zKU00V;k3wOb@ucRR1Vavs9dqED$-lEL|*q?I6|RZyS_!VK4VOLwf4l$ zpRJtx*y&Hdd1lSU4~&!FFttCN)48kNiIQWeW$!dJc%8aE|8gFDQ6BucJa}Usd~+WBP#*k0^WgXA!9SJ~=NNn zpsTC5jiiG8eJBq^BB5|p6}Cr0p{=4Fnr)&x)J;~}+q!xqA zL*cM!za`um4T*M`4njHH*%NJt;kJQ*zrC|3(A9Zsh^lMrrs~LX@(ERT2ReI1YakNp z33NlEM{B}PNUZfW*01(2sal%OEnb$+W^zlamJ5Gl^Ey8q32o_&L_^``b*sC2dqT~D z)~*oh+tS_JqwDi4(VT(^Z;>1|=(`AGiK3Zr8jmv^4J5?5@S)^DDVhQg`{KgR&RH}= zy{xfa-@FgQ&=fJQ`MI*(Dc;w7|NPGBa`Yl2NIbvr{obw{$2mQJFkY?kM3d5EoOzMe zZi=h5To!I>JE!%!fW}Oo%1C3(f>R&il(68`r#PLn;N=X0PFV2y7M$BR&FR*Gxm;h@ zG~hZkQp;_@)g4gA7Fuwg+o+`4g3EP-5*IZVT+S03ue0D(Hz&h_bF8VX$%5lXrqlHn zTrXv0*_SN%%(S!+9Tpr7D4qH&cu5)+V!(ozTJY@_e6|JOX~C`I&9DW(&?3Lbg43ML zX|DyJ#~|o_3r=$Zrvn!J;|zixvf!Vv;3F3NA`4!~79voHKp_H!2;`2y?@K@Rfid_- zu@Q5HEIkVFFqy}Qp;ZfoT_*g-=&w-AxC@%l_kegNEI&W$!f`eop_o;lWxi1N<2-S$uh}@iKi(uDJ1`8 z;%TZ(o;weqqLuhE;>RTa1>$MSOpZ!^Bk?pCJ#uyo_LxXlY1qolGWz7-_S)Z}ZqdKA#Tcx81d_rqUyIH%Vzu`HPtDs4DA9&kop2oo z;Mjbuy{hz*9pr{M)}F~zdm?+uEs$-24Nr@w`C7Eba`X0=46}dKhlrNiGTk~ zDrLI1!JZF+p~p{odyG$`v7T}@n#XQO1HOdRjX0 zb2eIbgc@Yx2=vIN6g){T!m4*7Emke-HlLxGn;#hFQxG0C%$CzfXvv${o>pH+CUEI3 zrw!9#nAf0tdK2U$?rEvYD{ssuq9|&aFwC-HRTG7<234Ix5k92Ve+in;xlamlxPqLD zFQ1D=GupyDki?!p!)+~UdQT&Zk2HJ=Mm1YZhD`4m+(mVeK%CzClT;j2p*w1r&&i0_ z+^C}Mxz4jm-O@YX*pJ1UVLsmRI_~15L3{^|*p!NehD-OG*Y_r7m6wU=VQp2 zBSw7g5ag*3wqofSn^I)#G2-(!ArGHYW3~IFwY3=IlHY^|{G|(|0ItXNCckgNs2Mg< zsFN>Z2#d)pw6=lT1#BgRvpHX{7QZD#SfH1@`37Fuvv3|6_cAxEDKz z1|hutar$;e=cr6wbh^rT`lk;nYvW}Kar93c{Gqfz_&5>Vyql;J*Ea0Y(Cweshm1+L zpL{VKPvTCK@tsA=z}}lbpVuSAGUlpgL!X&s$iYVT_1_Y6O+)!$!eOaPHMQGN+qmz; zD|vdQ_V!6+EEeD`DuK&n1jcvD%=X8L75)D)9#9uCrT_X5x4)I`x8}01blLYzH$juw zugb2FH@5WaSD{^vJBDMf`O;48!JTjzJV4mV+jZ zG&bc2pqy+)2h%8xRhT#Ed$;Usu{lQU>f(Q0Ji2JqbR=sgOJbOFmBBv@jkEE*iH5F5 zq#xjlvW%AL{)2HmK5rZktxwQuBCWB?s!?v~YYs%sFjp5Re}dHy+eC+IlpP9_0;Yq} z7&^Zi9`q%^F)QzQ$}CF`*bNlh3}h)}8(j2^NhX%tP5jp%$+wT_!^&q$A9`|US@LbH z>exG6$DKAC!W`PA8V3({0ZaMr5tPMtYYAdh%d+}0=`J5c0F$?&(!4!FPXS+tJ?ns1 zdhkJl?8B)8Gw;y#fYAr3l*YF6?-6~`h&@ULguDdg!v@F&bW?42C{|p0aJMuVb3L&f z5sEMWIhK3r_VEQ+)Kxm3&1^4sfbkBwV z%izB~)}%5xf_Z{6h&_>2Lv%smA7PjyQ$SUO;&i!K%$g@t=+mlOPwGxv&=7s*yW^Gc zhZao;Y{^o2r`F=}C4Hy9|4u4(gr46L?%!jnkr94*i4O*rDvK}2?5ihRspBod#VJ!M~lu%pE>gZWK?w{)yi)B!1tu=L^ zro@HP{q!wXqdOL?M(GnHh68`2rH_fy$3`kdoe)&vgSbE#{3;nVj~=Ew@C1CocvAZg z+FW8EtlAwwJKKX$j9+iWKG^`Z#6wbq>LfgWW%NwJB5-Hib*PFeGS8qP#?GOdL0Rg^ zNMZeOKb$*Lt(?O!lXmU~a?T}#OY3u9UwF|!GxIyHNY3HjQ$b^?dJc%X5^rI=!4pJ0 zbXq>Md2aIfv!5ge-@t>-19&(b&U%2LK4%^qy#0Zq=;cPtJ4{OexIdHJrQrr2LBpTQ9O2+7+c&(G(c>wNLr zpMDmP51YvImPYeEPqWATbBhtL`~l!=eT)7=dq{~N{|+{e{>2%+Wbyy#{RW@;j?a9v z!ThZ!HTO5h;4z1>^4I-mX@BX)&GcMO_t?58K>Gp5aOFhFW@aafVpK?l2oxevh(I9% zg$NWPP>4Vw0)+?^B2b7xAp(DE1RVPJi1MeDT|M~OC4T%_MZfl}yfWI`=l*o9dwq+~ zCn_u56$3LwMUZk`wH4J%23AP~Y+FUpUF)t0&Y-fMKzK`}mKd4V_eR}~-F;o5?odxO z6m<9Ywe}`0=q|cF8 zQ>o|hN#YZIEtP5l_EA0;B__*xb71*w9fzzdIoU=&`FyzXciOlc`Yn;b7KF1R;{JSl zXIH2y(%N?A3=wYS_Mm>u+t=F%z;TiDiZ6lEk0F+wNu_pye)~6wCFp}=snpLv&x5`S zdhN@p)D-ND+zNUr=&PU&pk1$|QuJ$&gV-+E1-cBoBga9%4LTFMBiQMd$#LsO;TR}$ zTr_i9@i58>$N!pz8}(gA5)IR6gF{@34|a3Ko?oX@eUK9+Wot^xuP(jdmg4Q=s`*#^ z)6z>90F`oI1bqSe0RlWFWp@>=o;9^G?!0@-psVOwR0Npn*n!U+T!;LXKg$(0NdYSV zHa=@n{u$7;oz{&0alq?Q{$*=bs^IB{hT7)|S-ul+<`isy!tO>r34ACFS)cW%VV+ z@^{BSfSqC3!GFUFsStre1PT!-M4%9XLIesCC`91@Sp@ifFn<4wmYS59(lS*hxhfZe z-zC~3SKmT>LKpLUX8eu~t+^@9S48mpX0*INlQUZ7sk*Q2)__+i>|CEW}DH_!dGJZ;D*l$|LQ{s2D zxPJWdRbCH@q$qwpC;0)bk6$!Pp5u$hBgwDQ<@ha>c>J_>jtkHCn zrtO;Etm$2v-lyqzG(D*42~D5Z^mR?&*K{g=C_(9Cn*O7vD>PlB=_XCvHN9EWyEIi+ z@_#3qa+mS@jr=}iw$$3yt5>-f;*Ie|?v+(bsusJKR4=KnUc9tgs1i@Ot;6l3SJGAa zXSuv`$s~9e0WZH*`yZ6Yq@{r#nQZz=>YR{VRh~-TEd+<)x&48i@(j-yxWc3{V63U^L?R{C5DyL^e@amtRD zVnDGXD(^(4eBObz)QO+cHQ|>+*~y7Ft$EzDmN~_R>G95v&lS1)l@p^Dzb|W-$B#&J zv`?q_xERZcXWs9&+u>0NSX;9B)e^koP7BgwnDl6rZKO<%4Oll`@0e4_mEW4ddX!-2+ zc7Y7w*|#f6zG(8e z@lqZ;6M66wTvx7o7v;e}p9jAYI9*qEf2TL;a@oHv5B^Pw&k{VJ1$2P+<&l3h5B@Z8 zjupq4=!?K-7kyl0&!@l5W9I{sN5A87nET^&v`=b>?gh$vNLTU+iEG9}&LUri@uWug zQ!Wa~dNcFFWxAj;kDakJ7iu4&p#2p%M z*SOqUM%g#=*m)oiegHVdKikir0e9=VtmOGTcFszCmKfFk?9@u{YW#Y=V3zyWDElLD zeXFeI#XQ{5T69w zZ4Gq!gVEk_#2@G%5N*BmuO$2*sp|5jE0^XLq9a5){ef^eu+5MEIfZc|Nf^h4_=El3 z-P@pI;r!%`O%(q=fk)v`q-yCh{Ak$kZ`$Z>Zoa|4re*!==Ee={{eBeGtQ2jneN{`B zuecl%Yc_h;dHvq?4SpOKVz(F+{)QXYd)76swwK7$OaNh7>GvAig3+*1_}BV2)O&pX z4Qtk1>uvTod+L2&vMSF&X^ZsBYol{hLUna~;0c{*qEB$KgIhvTe`|kdR}_b)eAtmE zbnJ^=Qy$D>hy0;nAR5qzyV#3#9g~la$%rjWAJ&q=|%0J z9o?djkFg8rlVGgL6dV)AcMxBL|k;hpy{>BaP zE!f%P?~jDgGBP)nTA@BKC!;~(k*Dj}A$=N;9rXKKBN2V!@{AmLx{qB({v%NRd)MFB zAL)?C0NF*Fn*XF@BVVLtOz<(7C>@sHl=RuPPcEK%Pb(l=aH4QWfgZ zV@*d8y38pJHRvb}&ckq^yR!|o_QDpvs$>Pq7;dnr!th7`Mz;;Xe98PkTzN&(Fn759tEhm!deaKJWi*0){Cut-uL4Av#)=L*00wF z`T3qH@4Jyco=zsk?;cd4Ex((pSzCHSw|PyR{xu`C9^uLY8^4D@&ix zyJYH`ZBa&+^%=cCOP}{2nDYK9%TuhaX)hS^pX1N#W2R@b8VtvuZS2p|=lu+(K^D|B zyZ(b&`n>PKbmXtpKLY&{e5eml;^$R9*D>2)=9&HiI(RBb>+`uZv>;!fvO<~ -#include -#include -#include -#include -#include -#include "builtins.c" -#include "stack.c" - -typedef uint8_t code_t; - -enum { - // Standalone - NOP = 0x00, - END = 0x01, - POP = 0x02, - RET = 0x03, - BLTIN = 0x04, - CODE = 0x05, - - // Unary - POS = 0x10, - NEG = 0x11, - NOT = 0x12, - INV = 0x13, - ATOI = 0x14, - ITOA = 0x15, - ITOF = 0x16, - CEIL = 0x17, - FLR = 0x18, - RND = 0x19, - CTOS = 0x1A, - - // Binary - ADD = 0x20, - SUB = 0x21, - MUL = 0x22, - DIV = 0x23, - IDIV = 0x24, - MOD = 0x25, - POW = 0x26, - SHL = 0x27, - SHR = 0x28, - AND = 0x29, - OR = 0x2A, - XOR = 0x2B, - - // Comparisons - EQ = 0x30, - NE = 0x31, - LT = 0x32, - GT = 0x33, - LE = 0x34, - GE = 0x35, - IS = 0x36, - ISNOT = 0x37, - - // Flow control - IF = 0x40, - ELSE = 0x41, - EXEC = 0x42, - - // With argument - ALLOC = 0xA0, - EXTEND = 0xA1, - CONST = 0xA2, - JUMPF = 0xA3, - JUMPB = 0xA4, - SCPGET = 0xA5, - SCPSET = 0xA6, - CALL = 0xA7, - - HASARG = 0xA0, -}; - -atom_t exec(code_t* code, uint32_t codesize) { // TODO: freeing - stack_t* st = stack(); - atom_t scp[255]; // TODO - code_t* cb[255]; // TODO - uint32_t cbi = 0; - - uint32_t cp = 0; - while (cp < codesize) { - code_t opcode = code[cp++]; - uint32_t ocp = cp; - - switch (opcode) { - // Standalone - case NOP: break; - case END: break; - case POP: stack_pop(st); break; - case RET: return st->top->data; - case BLTIN: { - char name[256]; - strncpy(name, (char*)code+cp, 255); - while (code[cp++] != '\0'); - stack_push(st, (atom_t){.data = get_builtin(name)}); - }; break; - case CODE: { - code_t* code_block = malloc(codesize); - static uint32_t cbp = 0; - uint32_t blocklvl = 1; - while (cp < codesize) { - code_t c = code[cp++]; - if (c == CODE || - c == IF || - c == ELSE) blocklvl++; - else if (c == END) blocklvl--; - if (blocklvl <= 0) break; - code_block[cbp++] = c; - if (c > HASARG) code_block[cbp++] = code[cp++]; - if (c == CONST) - for (uint8_t i = code_block[cbp-1]; i > 0; i--) - code_block[cbp++] = code[cp++]; - else if (c == BLTIN) - do code_block[cbp++] = code[cp++]; - while (code[cp-1] != '\0'); - } - cb[cbi++] = realloc(code_block, cbp); - free(code_block); - stack_push(st, (atom_t){u32, .u32 = &cbp}); - stack_push(st, (atom_t){.data = &cb[cbi-1]}); - }; break; - - // Unary - case POS: *st->top->data.i32 = abs(*st->top->data.i32); break; - case ITOA: { - char s[12]; - fprintf(stderr, "-- %x\n", *st->top->data.i32); - snprintf(s, sizeof(s)/sizeof(*s), "%d", *st->top->data.i32); - st->top->data.data = strdup(s); - }; break; - - // Binary (TODO) - case ADD: *st->top->data.i32 += *stack_pop(st).i32; break; - case SUB: *st->top->data.i32 -= *stack_pop(st).i32; break; - - // Comparisons - case LT: *st->top->data.b = *stack_pop(st).i32 > *st->top->data.i32; break; - - // Flow control - case EXEC: { - uint32_t exec_codesize = *stack_pop(st).u32; - code_t* exec_code = stack_pop(st).data; - stack_push(st, exec(exec_code, exec_codesize)); - }; break; - - // With argument - case CONST: { - uint8_t len = code[cp++]; - stack_push(st, (atom_t){.data = memcpy(malloc(len), code+cp, len)}); - fprintf(stderr, "-- l=%02x: %x\n", len, *st->top->data.i32); - cp += len; - }; break; - case SCPGET: { - uint8_t cell = code[cp++]; - stack_push(st, scp[cell]); - }; break; - case SCPSET: { - uint8_t cell = code[cp++]; - scp[cell] = stack_pop(st); - fprintf(stderr, "-- c%d = %d\n", cell, *scp[cell].i32); - }; break; - case CALL: { - uint8_t nargs = code[cp++]; - fprintf(stderr, "-- nargs=%d\n", nargs); - builtin_function func = stack_pop(st).data; - atom_t args[nargs]; - for (uint8_t i = 0; i < nargs; i++) - args[i] = stack_pop(st); - stack_push(st, func(nargs, args)); - }; break; - - default: fprintf(stderr, "Not Implemented opcode: 0x%02x\n", opcode); exit(3); - } - - fprintf(stderr, "[%02x", opcode); - if (opcode > HASARG) fprintf(stderr, "(%d|0x%02x)", code[ocp], code[ocp++]); - fputc(':', stderr); - do fprintf(stderr, " %02x", code[ocp++]); - while (ocp < cp && ocp < codesize); - if (st->top != NULL) { - fprintf(stderr, ", TOS = %u(0x%02x)", *st->top->data.i32, *st->top->data.i32); - if (isprint(*(char*)st->top->data.data)) { - fprintf(stderr, " | "); - for (char* p = st->top->data.data; *p != '\0'; p++) - fputc(isprint(*p)?*p:'.', stderr); - } - } - fprintf(stderr, "]\n"); - } - - return (atom_t){.data = NULL}; -} - -int main(int argc, char* argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", basename(argv[0])); - exit(1); - } - - FILE* fd = fopen(argv[1], "rb"); - fseek(fd, 0, SEEK_END); - long fsize = ftell(fd); - fseek(fd, 0, SEEK_SET); - - code_t code[fsize]; - fread(code, sizeof(*code), fsize, fd); - fclose(fd); - - exec(code, fsize); -} - -// by Sdore, 2020 diff --git a/SBC/stack.c b/SBC/stack.c deleted file mode 100644 index 53238d9..0000000 --- a/SBC/stack.c +++ /dev/null @@ -1,53 +0,0 @@ -/* -#define CAT(a, b) a##_##b -#define TEMPLATE(name, type) CAT(name, type) - -#define stack TEMPLATE(stack, T) -#define stack_t TEMPLATE(stack_t, T) -#define stack_item TEMPLATE(stack_item, T) -#define stack_push TEMPLATE(stack_push, T) -#define stack_pop TEMPLATE(stack_pop, T) -*/ - -#define T atom_t - -typedef struct stack { - struct stack_item* top; -} stack_t; - -struct stack_item { - T data; - struct stack_item* below; -}; - -stack_t* stack() { - stack_t* st = malloc(sizeof(*st)); - st->top = NULL; - return st; -} - -void stack_push(stack_t* st, T data) { - struct stack_item* new = malloc(sizeof(*new)); - new->data = data; - if (st->top != NULL) new->below = st->top; - st->top = new; -} - -T stack_pop(stack_t* st) { - assert (st->top != NULL); - struct stack_item* item = st->top; - st->top = item->below; - T data = item->data; - free(item); - return data; -} - -/* -#undef stack -#undef stack_t -#undef stack_item -#undef stack_push -#undef stack_pop -*/ - -#undef T diff --git a/SBC/tests/hw.sbc b/SBC/tests/hw.sbc deleted file mode 100644 index cf0a31fea4085e1334783a817f2dae312845440c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26 hcmZ3)>yet1ldn*oUzC%g$iPxil$lqOlgF@}5deAL2*3aU diff --git a/Slang.md b/Slang.md index 49c220d..fc12273 100644 --- a/Slang.md +++ b/Slang.md @@ -1,4 +1,4 @@ -# Slang +# Slang ### Example code @@ -8,26 +8,26 @@ #| and that is a multiline one. |# -const u32 n = 123123 # n is of type const u32 -const i64 m = 10**18 # m is of type const i64 -const int z = 2**128 # z is of type const int (unsized) -const auto q = 2**256 # q is of type const int +const u32 n = 123123 # n: const u32 (unsigned 32 bit) +const i64 m = 10**18 # m: const i64 (signed 64 bit) +const int z = 2**128 # z: const int (signed unsized) +const auto q = 2**256 # q: const int (signed unsized) -char f(str x) { # f() is of type char, x is of type str - auto c = x[1] # c is of type char - return c +char f(str x) { # f(): char, x: str + auto c = x[1] # c: char + return c # char } -auto g(str x) { # g() is of type char, x is of type str - return x[0] # retval is of type char +auto g(str x) { # g(): char, x: str + return x[0] # char } -int h(int x) = x+1 # h() is of type int, x is of type int +int h(int x) = x+1 # h(): int, x: int main { - stdio.println(h(n), \ # comment here too - f('123asd') + g('32') + 1) #--> «123124 f» - stdio.println(q/z/2**96) #--> «4294967296.0» + stdio.println(h(n), \ # comments allowed here too + f('123asd') + g('32') + 1) #--> «123124 f» + stdio.println(q/z/2**96) #--> «4294967296.0» } ``` @@ -58,8 +58,7 @@ _Note: `*` after syntax unit means any number of them._ ### Primitive -* `< | | | | | >` — `value` -* `\[\]` — `itemget` +* `< | | | | | >` — `value` * `<() | | | >` — `expr` (`f(x+3)` is an instance of `expr`, also `f`, `x+3` and `x` are `expr`s too) ### Non-final @@ -73,15 +72,17 @@ _Note: `*` after syntax unit means any number of them._ > `=` — default value if argument not specified. * `([[, ]*]) -> = ` — `lambda` (lambda function) * `<[, ]*[, *] | *>` — `callargs` -* `<=[, =]*[, **] | **>` — `callkwargs` +* `< =|: [, =|: ]*[, **] | **>` — `callkwargs` +* `\[\]` — `itemget` +* `.` — `attrget` ### Final (ordered by resolution order) * ` ([[, ]*]) < | = >` — `funcdef` (function definition) * ` [expr]` — `keywordexpr` (keyword expression) -* ` [= ]` — `vardef` (variable definition) -* ` = ` — `assignment` -* `[, ]* = ` — `unpackassignment` +* ` [= ]` — `vardef` (variable definition) +* ` = ` — `assignment` +* `[, ]* = ` — `unpackassignment` * `([ | | , ])` — `funccall` (function call) * `` — expr evaluation (only in REPL) * `if () ` — `conditional` @@ -100,12 +101,22 @@ _Note: `*` after syntax unit means any number of them._ ### Reserved keywords * `def` +* `try` +* `catch` +* `except` +* `finally` +* `raise` +* `with` +* `yield` +* `include` +* `using` +* `default` ## Identifiers Non-empty sequence of alphanumeric characters plus underscore («_»), not starting with a digit character. -Regex: `[_\w][_\w\d]*` +Regex: `[^\W\d][\w]*` ### Data types @@ -113,10 +124,14 @@ Regex: `[_\w][_\w\d]*` * `u8`, `u16`, `u32`, `u64`, `u128` — fixed size unsigned integer * `f8`, `f16`, `f32`, `f64`, `f128` — fixed size IEEE-754 floating point number * `uf8`, `uf16`, `uf32`, `uf64`, `uf128` — fixed size unsigned floating point number +* `c8`, `c16`, `c32`, `c64`, `c128` — fixed size complex number +* `uc8`, `uc16`, `uc32`, `uc64`, `uc128` — fixed size unsigned complex number * `int` — unsized («big») integer * `uint` — unsized unsigned integer * `float` — unsized floating point number * `ufloat` — unsized unsigned floating point +* `complex` — unsized complex number +* `ucomplex` — unsized unsigned complex number * `bool` — logical (boolean) value * `byte` — single byte * `char` — UTF-8 character @@ -171,5 +186,9 @@ A set of pre-defined keyword operators: All character class checks are performed in current locale. + +
+ --- -_by Sdore, 2020_ +_by Sdore, 2021-22_
+_slang.sdore.me_ diff --git a/Slang.py b/Slang.py index d2ae515..4fee1ae 100755 --- a/Slang.py +++ b/Slang.py @@ -5,41 +5,45 @@ from .ast import * from .compilers import * from utils.nolog import *; logstart('Slang') -def compile(src, filename='', *, compiler): +def compile(src, filename='', *, compiler, optimize=0): try: #print(f"Source: {{\n{S(src).indent()}\n}}\n") tl = parse_string(src) #print(f"Tokens:\n{pformat(tl)}\n") - ast = build_ast(tl, filename) - #print(f"Code: {ast.code}\n") + ast = build_ast(tl, filename.join('""')) + print(f"Code: {ast.code}\n") #print(f"Nodes: {pformat(list(walk_ast_nodes(ast)))}\n") - optimize_ast(ast, validate_ast(ast)) - #print(f"Optimized: {ast.code}\n") + if (optimize): + optimize_ast(ast, validate_ast(ast), optimize) + print(f"Optimized: {ast.code}\n") + #print(f"Optimized Nodes: {pformat(list(walk_ast_nodes(ast)))}\n") - code = compiler.compile_ast(ast, validate_ast(ast), filename=filename) + ns = validate_ast(ast) + + code = compiler.compile_ast(ast, ns, filename=filename) #print("Compiled.\n") - except (SlSyntaxError, SlValidationError, SlCompilationError) as ex: - if (not ex.line): ex.line = src.split('\n')[ex.lineno-1] - sys.exit(ex) + except (SlSyntaxException, SlNodeException) as ex: + if (not ex.srclines): ex.srclines = src.split('\n') + sys.exit(str(ex)) return code @apmain -@aparg('file', metavar='', type=argparse.FileType('r')) -@aparg('-o', dest='output') -@aparg('-f', dest='compiler', required=True) +@aparg('file', metavar='', type=argparse.FileType('r')) +@aparg('-o', metavar='output', dest='output') +@aparg('-f', metavar='compiler', dest='compiler', default='pyssembly')#required=True) +@aparg('-O', metavar='level', help='Code optimization level', type=int, default=DEFAULT_OLEVEL) def main(cargs): if (cargs.output is None and not cargs.file.name.rpartition('.')[0]): argparser.add_argument('-o', dest='output', required=True) cargs = argparser.parse_args() src = cargs.file.read() filename = cargs.file.name - _cns = importlib.import_module('.compilers.'+cargs.compiler, package=__package__).__dict__.values() - compiler = first(i for i in allsubclasses(Compiler) if i in _cns) - code = compile(src, filename=filename.join('""'), compiler=compiler) + compiler = importlib.import_module('.compilers.'+cargs.compiler, package=__package__).__dict__['compiler'] + code = compile(src, filename=filename, compiler=compiler, optimize=cargs.O) open(cargs.output or cargs.file.name.rpartition('.')[0]+compiler.ext, 'wb').write(code) if (__name__ == '__main__'): exit(main()) diff --git a/TODO.md b/TODO.md index ea47a6f..85dbb43 100644 --- a/TODO.md +++ b/TODO.md @@ -3,12 +3,16 @@ - https://esolangs.org/wiki/Stlang - https://esolangs.org/wiki/Object_disoriented - https://esolangs.org/wiki/Funciton ? -- `O.each { code }` (i.e. `O.each({code})`) -- Proposal: `if`, `for` and `while` without brackets +- Proposal: `if`, `for` and `while` without `()` - OOP - Increments -- FIXME: whitespace in srclength -- renew all `__repr__`s *AND* `__str__`s - Proposal: `f(kw: arg)` = `f(kw=arg)` - Proposal: [https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/default] -- FIXME: `auto' rettype +- FIXME: https://docs.python.org/3/reference/expressions.html#the-power-operator +- Proposal: `lazy` modifier and `lazy: expr` -- expr that evaluates/runs on first request after declaration +- time literal +- Proposal: arbitrary size numeric data types +- Proposal: single argument lambda without `()` +- https://lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science +- ${lang} as logo ? +- Inheritance as in Ruby: `class A < B { }` diff --git a/asm/hw b/asm/hw deleted file mode 100755 index a25226f5b7b7e788659a21717931eca424e2d521..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6032 zcmeHL&1)1%6o2WCI%fTdUl?{5c0ffhwi=A+L8-(|9GCH1K@Zx~$&AiGG6~ZilYn4g z*@Fimf5NcrAFxM{3wsd%124i}g!QHeJvzQu^?se6WFmrivMZ#1@BQ9;U9YO9UsLsX zygcFc^%0s!=SZ~QJ!2`?6J6b7Rpn08W-_#k1{sO^`QKQJpp$@C_0hnB`uU6!x1#oy zJ{JevseV9YD7Z;&4vcCJ+n!T_L`6$ToaFlV4y!U&tN@(T0f2~;$9*mi^8#Xi*Iy@9 zncqg^!5^r@)3NEsWuuky-1xye{sL_7sHb1 zb@6@kU#HWlFVL;hlh>uEXO}%g|G#3Y^!y*T(%pZh&s)9%`3mGKkgq_#0{IH$E0C{1 zz5@9Qe7`GzvjtGjSkl^hetiG2Gb{Qt)p1rVct&V@mJ@ub_5o42{p^6`d?ani0qs)+ z_z?8GqsPtEf3Mo*93*YO11fpON*_Bezay&idB^69`Mg#ApxSNFH|%i!T|30XFBsqF zlOE3N*?6DNh1id7-cR?Xo9|EeqnqDE*tc$eGxb^@pSBRzwcEdtu75Ybl`5XeQdah9 zfR;UDo`+zgd4|4v+HTjzj_}?tQKL7Cz{HeYWMDYxTzuvh#dgK*VO9|Q5s!d11+;l=gQd{J{RuAvD zqM4eFj%FHD95I_%51#B-wx2j&Y60pB-meuc5V#M+FsK{QWk03nAs+8xTOCoKP&eiG zA`SWhmOpR}LOkl|GmS@`gg@f#L%2K2|58H{k2?EO!ezZ2Yokm?5rb7SivW z(^li5H&~E(bx-SE8w4B`v45ar79@9kC-Hs3Jt*!w)9->!&&t)gOTI zf!W;QBUse<+d2<weg<- DUuV#; diff --git a/asm/hw.asm b/asm/hw.asm deleted file mode 100644 index c4d77a3..0000000 --- a/asm/hw.asm +++ /dev/null @@ -1,16 +0,0 @@ -bits 64 - -;section .rodata -hw: db "hw", 0 - -.text -global _start -extern puts -_start: - push rbp - mov rbp, rsp - lea rdi, [hw+rip] - call puts - mov rax, 0 - pop rbp - ret diff --git a/asm/hw.c b/asm/hw.c deleted file mode 100644 index 5264558..0000000 --- a/asm/hw.c +++ /dev/null @@ -1 +0,0 @@ -main() {puts("hw");} \ No newline at end of file diff --git a/asm/hw.o b/asm/hw.o deleted file mode 100644 index c5bdd3d8bf6d1b998d5ede36386afb670e9b7521..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 768 zcmb<-^>JfjWMqH=Mg}_u1P><4z|eps=l~XWU|<8P%qV9F_2_)+(Q6A5d;uanjvoL~ zv4^=p6cB)D7BCA!@I%>5K$;DR|NMsl2ECHhiV_CBqSTy3D6_bvs3b9oL9e(n7eZ%1 zMSvFTB^Kv0#21$&7L_m*l$I1jO~s@@PG@2G_#aaSB*MbLj8y{J#Rvt=C_aIRa{ws; zEargxDS*&Q6P6OtRBZ0=f!d}+z*Zp~`gcI}i4f2)1(XNHBRV((6<7hKL26im7!+0@ sAOWGkX$=%Vpmcx?9H1N-WI-SsJv`ZgV%TCT2CB~hN`t}(qz>JF0K7>q`Tzg` diff --git a/ast.py b/ast.py index 942b511..e87a717 100644 --- a/ast.py +++ b/ast.py @@ -6,11 +6,19 @@ from . import sld from .lexer import * from utils import * +DEFAULT_OLEVEL = 0 +TAB_SIZE = 4 DEBUG_PRECEDENCE = False def warn(class_, msg, node, ns): - if (ns.warnclasses[class_] == False): return - logexception(Warning(f"{msg} \033[8m({class_})\033[0m"), raw=True) + if (ns.warnclasses and class_ not in ns.warnclasses): return + logexception(Warning(f"{msg} \033[2m(at line {node.lineno}, offset {node.offset})\033[0m \033[8m({class_})\033[0m"), raw=True, once=True) + +def eval_literal(x): + return eval(literal_repr(x)) + +def literal_repr(x): + return (str if (isinstance(x, ASTNode)) else repr)(x) def literal_type(x): r = eval(str(x)) @@ -18,13 +26,17 @@ def literal_type(x): return type(r) def common_type(l, ns): # TODO - r = tuple(Signature.build(i, ns) for i in l) + r = Slist(Signature.build(i, ns) for i in l).uniquize() + r.reverse(); r = r.uniquize() if (not r): return None + r = [i for i in r if not isinstance(i, stdlib.auto)] if (len(r) > 1): raise TODO(r) return first(r) -class ASTNode(abc.ABC): - __slots__ = ('lineno', 'offset', 'flags') +class ASTNode(ABCSlots): + lineno: ... + offset: ... + flags: ... @abc.abstractmethod @init_defaults @@ -32,39 +44,40 @@ class ASTNode(abc.ABC): self.lineno, self.offset, self.flags = lineno, offset, flags def __repr__(self): - return f"<{self.typename} '{self.__str__()}' on line {self.lineno}, offset {self.offset}>" + return f"<{self.typename} `{self.__str__()}' on line {self.lineno}, offset {self.offset}>" @abc.abstractmethod def __str__(self): - pass + return '' @abc.abstractclassmethod def build(cls, tl): if (not tl): raise SlSyntaxNoToken() def validate(self, ns): - for i in self.__slots__: + for i in allslots(self): v = getattr(self, i) - if (isiterable(v) and not isinstance(v, str)): + if (isiterablenostr(v)): for jj, j in enumerate(v): if (hasattr(j, 'validate')): j.validate(ns) - elif (hasattr(v, 'validate') and i != 'code'): + elif (hasattr(v, 'validate') and not isinstance(v, ASTCodeNode)): v.validate(ns) def optimize(self, ns): - for i in self.__slots__: + for i in allslots(self): v = getattr(self, i) - if (isiterable(v) and not isinstance(v, str)): + if (isiterablenostr(v)): for jj, j in enumerate(v.copy()): if (hasattr(j, 'optimize')): r = j.optimize(ns) if (r is not None): v[jj] = r if (v[jj].flags.optimized_out): del v[jj] - elif (hasattr(v, 'optimize') and i != 'code'): + elif (hasattr(v, 'optimize') and not isinstance(v, ASTCodeNode)): r = v.optimize(ns) if (r is not None): setattr(self, i, r) if (getattr(self, i).flags.optimized_out): setattr(self, i, None) + self.flags.optimized = 1 @classproperty def typename(cls): @@ -72,13 +85,14 @@ class ASTNode(abc.ABC): @property def length(self): - return sum(getattr(self, i).length for i in self.__slots__ if hasattr(getattr(self, i), 'length')) + #dlog(max((getattr(self, i) for i in allslots(self) if hasattr(getattr(self, i), 'offset')), key=lambda x: (x.lineno, x.offset) + return sum(getattr(self, i).length for i in allslots(self) if hasattr(getattr(self, i), 'length')) class ASTRootNode(ASTNode): - __slots__ = ('code',) + code: ... def __init__(self, code, **kwargs): - super().__init__(lineno=None, offset=None, **kwargs) + super().__init__(**kwargs) self.code = code def __repr__(self): @@ -89,44 +103,69 @@ class ASTRootNode(ASTNode): @classmethod def build(cls, name=None): - return cls((yield from ASTCodeNode.build(name))) + code = (yield from ASTCodeNode.build([], name=name)) + return cls(code, lineno=code.lineno, offset=code.offset) + + def validate(self, ns=None): + if (ns is None): ns = Namespace(self.code.name) + super().validate(ns) + self.code.validate(ns) + return ns + + def optimize(self, ns, level=DEFAULT_OLEVEL): + ns.olevel = level + super().optimize(ns) + self.code.optimize(ns) class ASTCodeNode(ASTNode): - __slots__ = ('nodes', 'name') + nodes: ... + name: ... def __init__(self, nodes, *, name='', **kwargs): - super().__init__(lineno=None, offset=None, **kwargs) + super().__init__(**kwargs) self.nodes, self.name = nodes, name def __repr__(self): - return f"""""" + return f"""""" def __str__(self): - return (S('\n').join(self.nodes).indent().join('\n\n') if (self.nodes) else '').join('{}') + return (S('\n').join(map(lambda x: x.join('\n\n') if ('\n' in x) else x, map(str, self.nodes))).indent().replace('\n\n\n', '\n\n').strip('\n').join('\n\n') if (self.nodes) else '').join('{}') @classmethod - def build(cls, name): + def build(cls, tl, *, name): + if (tl): + cdef = ASTSpecialNode.build(tl) + if (cdef.special != '{'): raise SlSyntaxExpectedError("'{'", cdef) + lineno, offset = cdef.lineno, cdef.offset + else: lineno = offset = None + yield name - code = cls([], name=name) + nodes = list() while (True): c = yield if (c is None): break - code.nodes.append(c) - return code + if (lineno is None): lineno, offset = c.lineno, c.offset + nodes.append(c) - def validate(self, ns=None): - if (ns is None): ns = Namespace(self.name) - for i in self.nodes: - i.validate(ns) - return ns + return cls(nodes, name=name, lineno=lineno, offset=offset) + + #def validate(self, ns): # super + # for i in self.nodes: + # i.validate(ns) def optimize(self, ns): for ii, i in enumerate(self.nodes): r = i.optimize(ns) if (r is not None): self.nodes[ii] = r - if (self.nodes[ii].flags.optimized_out): del self.nodes[ii] + self.nodes = [i for i in self.nodes if not i.flags.optimized_out] class ASTTokenNode(ASTNode): + length: ... + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.length = sum(len(getattr(self, i)) for i in allslots(self) if isinstance(getattr(self, i, None), str)) + @abc.abstractclassmethod def build(cls, tl): super().build(tl) @@ -135,16 +174,12 @@ class ASTTokenNode(ASTNode): if (i.typename == 'SPECIAL' and (i.token[0] == '#' or i.token == '\\')): del tl[ii-off]; off += 1 if (not tl): raise SlSyntaxEmpty() - @property - def length(self): - return sum(len(getattr(self, i)) for i in self.__slots__) - class ASTIdentifierNode(ASTTokenNode): - __slots__ = ('identifier',) + identifier: ... def __init__(self, identifier, **kwargs): - super().__init__(**kwargs) self.identifier = identifier + super().__init__(**kwargs) def __str__(self): return str(self.identifier) @@ -160,11 +195,11 @@ class ASTIdentifierNode(ASTTokenNode): return cls(identifier, lineno=lineno, offset=offset) class ASTKeywordNode(ASTTokenNode): - __slots__ = ('keyword',) + keyword: ... def __init__(self, keyword, **kwargs): - super().__init__(**kwargs) self.keyword = keyword + super().__init__(**kwargs) def __str__(self): return str(self.keyword) @@ -180,11 +215,11 @@ class ASTKeywordNode(ASTTokenNode): return cls(keyword, lineno=lineno, offset=offset) class ASTLiteralNode(ASTTokenNode): - __slots__ = ('literal',) + literal: ... def __init__(self, literal, **kwargs): - super().__init__(**kwargs) self.literal = literal + super().__init__(**kwargs) def __str__(self): return str(self.literal) @@ -200,11 +235,11 @@ class ASTLiteralNode(ASTTokenNode): return cls(literal, lineno=lineno, offset=offset) class ASTOperatorNode(ASTTokenNode): - __slots__ = ('operator',) + operator: ... def __init__(self, operator, **kwargs): - super().__init__(**kwargs) self.operator = operator + super().__init__(**kwargs) def __str__(self): return str(self.operator) @@ -219,12 +254,28 @@ class ASTOperatorNode(ASTTokenNode): return cls(operator, lineno=lineno, offset=offset) +class ASTUnaryOperatorNode(ASTOperatorNode): + @classmethod + def build(cls, tl): + operator = super().build(tl) + if (not isinstance(operator.operator, UnaryOperator)): raise SlSyntaxExpectedError('UnaryOperator', operator) + operator.operator = UnaryOperator(operator.operator) + return operator + +class ASTBinaryOperatorNode(ASTOperatorNode): + @classmethod + def build(cls, tl): + operator = super().build(tl) + if (not isinstance(operator.operator, BinaryOperator)): raise SlSyntaxExpectedError('BinaryOperator', operator) + operator.operator = BinaryOperator(operator.operator) + return operator + class ASTSpecialNode(ASTTokenNode): - __slots__ = ('special',) + special: ... def __init__(self, special, **kwargs): - super().__init__(**kwargs) self.special = special + super().__init__(**kwargs) def __str__(self): return str(self.special) @@ -243,7 +294,7 @@ class ASTSpecialNode(ASTTokenNode): class ASTPrimitiveNode(ASTNode): pass class ASTValueNode(ASTPrimitiveNode): - __slots__ = ('value',) + value: ... def __init__(self, value, **kwargs): super().__init__(**kwargs) @@ -253,37 +304,37 @@ class ASTValueNode(ASTPrimitiveNode): return str(self.value) @classmethod - def build(cls, tl, *, fcall=False): + def build(cls, tl, *, fcall=False, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset - if (tl[0].typename == 'LITERAL'): value = ASTLiteralNode.build(tl) - else: - types = allsubclasses(ASTLiteralStructNode)+[ASTLiteralNode, ASTFunccallNode, ASTAttrgetNode, ASTItemgetNode, ASTIdentifierNode, ASTLambdaNode] - if (fcall): types.remove(ASTFunccallNode); types.remove(ASTLambdaNode) # XXX lambda too? - err = set() - for i in types: - tll = tl.copy() - try: value = i.build(tll) - except SlSyntaxExpectedError as ex: err.add(ex); continue - except SlSyntaxException: continue - else: tl[:] = tll; break - else: raise SlSyntaxMultiExpectedError.from_list(err) + types = [*allsubclasses(ASTUnaryOperationNode), ASTFunccallNode, ASTItemgetNode, ASTAttrgetNode, ASTIdentifierNode, ASTLambdaNode, ASTLiteralNode, *allsubclasses(ASTLiteralStructNode)] + if (fcall): types.remove(ASTFunccallNode); types.remove(ASTLambdaNode) # XXX lambda too? + if (attrget): types.remove(ASTAttrgetNode) + err = set() + for i in types: + tll = tl.copy() + try: value = i.build(tll, **{'attrget': attrget} if (i in (ASTFunccallNode,)) else {}) + except SlSyntaxExpectedError as ex: err.add(ex); continue + except SlSyntaxException: continue + else: tl[:] = tll; break + else: raise SlSyntaxMultiExpectedError.from_list(err) return cls(value, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) if (isinstance(self.value, ASTIdentifierNode)): - if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, scope=ns.scope) - if (self.value.identifier not in ns.values): warn('uninitialized', f"using value of possibly uninitialized variable '{self.value}'", self, ns) + if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, self, scope=ns.scope) + if (ns.values.get(self.value.identifier) is None): raise SlValidationError(f"{self.value.identifier} is not initialized", self.value, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) - if (isinstance(self.value, ASTIdentifierNode) and ns.values.get(self.value)): self.value = ASTLiteralNode(repr(ns.values[self.value]), lineno=self.lineno, offset=self.offset) # TODO FIXME in functions + if (isinstance(self.value, ASTIdentifierNode) and ns.values.get(self.value) not in (None, ...)): self.value = ns.values[self.value] if (isinstance(ns.values[self.value], ASTNode)) else ASTLiteralNode(literal_repr(ns.values[self.value]), lineno=self.lineno, offset=self.offset) # TODO FIXME in functions class ASTItemgetNode(ASTPrimitiveNode): - __slots__ = ('value', 'key') + value: ... + key: ... def __init__(self, value, key, **kwargs): super().__init__(**kwargs) @@ -321,10 +372,12 @@ class ASTItemgetNode(ASTPrimitiveNode): super().validate(ns) valsig = Signature.build(self.value, ns) keysig = Signature.build(self.key, ns) - if ((keysig, self.key) not in valsig.itemget): raise SlValidationError(f"'{valsig}' does not support itemget by key of type '{keysig}'", self, scope=ns.scope) + if ((keysig, self.key) not in valsig.itemget): raise SlValidationError(f"`{valsig}' does not support itemget by key of type `{keysig}'", self.key, self, scope=ns.scope) class ASTAttrgetNode(ASTPrimitiveNode): - __slots__ = ('value', 'optype', 'attr') + value: ... + optype: ... + attr: ... def __init__(self, value, optype, attr, **kwargs): super().__init__(**kwargs) @@ -338,7 +391,7 @@ class ASTAttrgetNode(ASTPrimitiveNode): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset - value = ASTIdentifierNode.build(tl) + value = ASTValueNode.build(tl, attrget=True) optype = ASTSpecialNode.build(tl) if (optype.special not in attrops): raise SlSyntaxExpectedError(f"one of {attrops}", optype) attr = ASTIdentifierNode.build(tl) @@ -348,11 +401,11 @@ class ASTAttrgetNode(ASTPrimitiveNode): def validate(self, ns): super().validate(ns) valsig = Signature.build(self.value, ns) - if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"'{valsig}' does not support attribute operation '{self.optype}' with attr '{self.attr}'", self, scope=ns.scope) + if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"`{valsig}' does not support attribute operation `{self.optype}' with attr `{self.attr}'", self.optype, self, scope=ns.scope) class ASTExprNode(ASTPrimitiveNode): @classmethod - def build(cls, tl, *, fcall=False): + def build(cls, tl, *, fcall=False, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset @@ -362,13 +415,19 @@ class ASTExprNode(ASTPrimitiveNode): except SlSyntaxException: continue else: tl[:] = tll; return value + for i in allsubclasses(ASTUnaryOperationNode): + tll = tl.copy() + try: value = i.build(tll) + except SlSyntaxException: pass + else: tl[:] = tll; return value + tll = tl.copy() try: value = ASTUnaryExprNode.build(tll) except SlSyntaxException: pass else: tl[:] = tll; return value tll = tl.copy() - try: value = ASTValueNode.build(tll, fcall=fcall) + try: value = ASTValueNode.build(tll, fcall=fcall, attrget=attrget) except SlSyntaxException as ex: pass else: tl[:] = tll; return value @@ -392,7 +451,8 @@ class ASTExprNode(ASTPrimitiveNode): raise SlSyntaxExpectedError('Expr', lineno=lineno, offset=offset) class ASTUnaryExprNode(ASTExprNode): - __slots__ = ('operator', 'value') + operator: ... + value: ... def __init__(self, operator, value, **kwargs): super().__init__(**kwargs) @@ -406,8 +466,7 @@ class ASTUnaryExprNode(ASTExprNode): ASTPrimitiveNode.build(tl) lineno, offset = tl[0].lineno, tl[0].offset - operator = ASTOperatorNode.build(tl) - if (not isinstance(operator.operator, UnaryOperator)): raise SlSyntaxExpectedError('UnaryOperator', operator) + operator = ASTUnaryOperatorNode.build(tl) value = ASTExprNode.build(tl) return cls(operator, value, lineno=lineno, offset=offset) @@ -416,21 +475,25 @@ class ASTUnaryExprNode(ASTExprNode): super().validate(ns) valsig = Signature.build(self.value, ns) op = self.operator.operator - if (op not in valsig.operators): raise SlValidationError(f"'{valsig}' does not support unary operator '{op}'", self, scope=ns.scope) + if (op not in valsig.operators): raise SlValidationError(f"`{valsig}' does not support unary operator `{op}'", self.operator, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) - if (ns.values.get(self.value)): return ASTValueNode(ASTLiteralNode(eval(f"{'not' if (self.operator.operator == '!') else self.operator} {ns.values[self.value]}"), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) + if (isinstance(self.value, ASTUnaryExprNode)): return self.value.value + elif (ns.values.get(self.value) not in (None, ...)): return ASTValueNode(ASTLiteralNode(literal_repr(eval(f"{'not' if (self.operator.operator == '!') else self.operator} ({literal_repr(ns.values[self.value])})")), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) class ASTBinaryExprNode(ASTExprNode): - __slots__ = ('lvalue', 'operator', 'rvalue') + lvalue: ... + operator: ... + rvalue: ... def __init__(self, lvalue, operator, rvalue, **kwargs): super().__init__(**kwargs) self.lvalue, self.operator, self.rvalue = lvalue, operator, rvalue def __str__(self): - return f"{str(self.lvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.lvalue, ASTBinaryExprNode) and operator_precedence(self.lvalue.operator.operator) > operator_precedence(self.operator.operator)) else self.lvalue}{str(self.operator).join(' ') if (operator_precedence(self.operator.operator) > 0) else self.operator}{str(self.rvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.rvalue, ASTBinaryExprNode) and operator_precedence(self.rvalue.operator.operator) > operator_precedence(self.operator.operator)) else self.rvalue}" + opp = operator_precedence(self.operator.operator) + return f"{str(self.lvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.lvalue, ASTBinaryExprNode) and operator_precedence(self.lvalue.operator.operator) > opp) else self.lvalue}{str(self.operator).join(' ') if (self.operator.operator not in '+-**/') else self.operator}{str(self.rvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.rvalue, ASTBinaryExprNode) and operator_precedence(self.rvalue.operator.operator) > opp) else self.rvalue}" @classmethod def build(cls, tl, opset): @@ -449,7 +512,7 @@ class ASTBinaryExprNode(ASTExprNode): try: lvalue = ASTExprNode.build(tlr) if (tlr): raise SlSyntaxExpectedNothingError(tlr[0]) - operator = ASTOperatorNode.build(tll) + operator = ASTBinaryOperatorNode.build(tll) rvalue = ASTExprNode.build(tll) except SlSyntaxException: pass else: tl[:] = tll; break @@ -462,24 +525,25 @@ class ASTBinaryExprNode(ASTExprNode): lsig = Signature.build(self.lvalue, ns) rsig = Signature.build(self.rvalue, ns) op = self.operator.operator - if ((op, rsig) not in lsig.operators): raise SlValidationError(f"'{lsig}' does not support operator '{op}' with operand of type '{rsig}'", self, scope=ns.scope) + if ((op, rsig) not in lsig.operators): raise SlValidationError(f"`{lsig}' does not support operator `{op}' with operand of type `{rsig}'", self.operator, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) - if (self.operator.operator == '**' and ns.values.get(self.lvalue) == 2 and (ns.values.get(self.rvalue) or 0) > 0): self.operator.operator, self.lvalue.value, ns.values[self.lvalue] = BinaryOperator('<<'), ASTLiteralNode('1', lineno=self.lvalue.value.lineno, offset=self.lvalue.value.offset), 1 - if (ns.values.get(self.lvalue) and ns.values.get(self.rvalue) and self.operator.operator != 'to'): return ASTValueNode(ASTLiteralNode(repr(eval(str(self))), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) + if (self.operator.operator == '**' and ns.values.get(self.lvalue) == 2 and ns.values.get(self.rvalue) is not ... and (ns.values.get(self.rvalue) or 0) > 0): self.operator.operator, self.lvalue.value = BinaryOperator('<<'), ASTLiteralNode('1', lineno=self.lvalue.value.lineno, offset=self.lvalue.value.offset) + if (ns.values.get(self.lvalue) not in (None, ...) and ns.values.get(self.rvalue) not in (None, ...) and self.operator.operator != 'to'): return ASTValueNode(ASTLiteralNode(literal_repr(eval(f"({literal_repr(ns.values[self.lvalue])}) {self.operator} ({literal_repr(ns.values[self.rvalue])})")), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) class ASTLiteralStructNode(ASTNode): pass class ASTListNode(ASTLiteralStructNode): - __slots__ = ('type', 'values') + type: ... + values: ... def __init__(self, type, values, **kwargs): super().__init__(**kwargs) self.type, self.values = type, values def __repr__(self): - return f"" + return f"" def __str__(self): return f"[{self.type}{': ' if (self.values) else ''}{S(', ').join(self.values)}]" @@ -505,12 +569,14 @@ class ASTListNode(ASTLiteralStructNode): return cls(type, values, lineno=lineno, offset=offset) def validate(self, ns): + super().validate(ns) typesig = Signature.build(self.type, ns) for i in self.values: - if (Signature.build(i, ns) != typesig): raise SlValidationError(f"List item '{i}' does not match list type '{self.type}'", self, scope=ns.scope) + if (Signature.build(i, ns) != typesig): raise SlValidationError(f"List item `{i}' does not match list type `{self.type}'", i, self, scope=ns.scope) class ASTTupleNode(ASTLiteralStructNode): - __slots__ = ('types', 'values') + types: ... + values: ... def __init__(self, types, values, **kwargs): super().__init__(**kwargs) @@ -541,13 +607,15 @@ class ASTTupleNode(ASTLiteralStructNode): return cls(types, values, lineno=lineno, offset=offset) def validate(self, ns): + super().validate(ns) for i in range(len(self.values)): - if (Signature.build(self.values[i], ns) != Signature.build(self.types[i], ns)): raise SlValidationError(f"Tuple item '{self.values[i]}' does not match its type '{self.types[i]}'", self, scope=ns.scope) + if (Signature.build(self.values[i], ns) != Signature.build(self.types[i], ns)): raise SlValidationError(f"Tuple item `{self.values[i]}' does not match its type `{self.types[i]}'", self.values[i], self, scope=ns.scope) class ASTNonFinalNode(ASTNode): pass class ASTTypedefNode(ASTNonFinalNode): - __slots__ = ('modifiers', 'type') + modifiers: ... + type: ... def __init__(self, modifiers, type, **kwargs): super().__init__(**kwargs) @@ -570,14 +638,17 @@ class ASTTypedefNode(ASTNonFinalNode): return cls(modifiers, type, lineno=lineno, offset=offset) class ASTArgdefNode(ASTNonFinalNode): - __slots__ = ('type', 'name', 'modifier', 'value') + type: ... + name: ... + modifier: ... + defvalue: ... - def __init__(self, type, name, modifier, value, **kwargs): + def __init__(self, type, name, modifier, defvalue, **kwargs): super().__init__(**kwargs) - self.type, self.name, self.modifier, self.value = type, name, modifier, value + self.type, self.name, self.modifier, self.defvalue = type, name, modifier, defvalue def __str__(self): - return f"{f'{self.type} ' if (self.type) else ''}{self.name}{self.modifier or ''}{f'={self.value}' if (self.value is not None) else ''}" + return f"{f'{self.type} ' if (self.type) else ''}{self.name}{self.modifier or ''}{f'={self.defvalue}' if (self.defvalue is not None) else ''}" @classmethod def build(cls, tl): @@ -587,16 +658,22 @@ class ASTArgdefNode(ASTNonFinalNode): type = ASTTypedefNode.build(tl) name = ASTIdentifierNode.build(tl) modifier = ASTOperatorNode.build(tl) if (tl and tl[0].typename == 'OPERATOR' and tl[0].token in '+**') else ASTSpecialNode.build(tl) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token in '?=') else None - value = ASTValueNode.build(tl) if (isinstance(modifier, ASTSpecialNode) and modifier.special == '=') else None + defvalue = ASTExprNode.build(tl) if (isinstance(modifier, ASTSpecialNode) and modifier.special == '=') else None - return cls(type, name, modifier, value, lineno=lineno, offset=offset) + return cls(type, name, modifier, defvalue, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) - if (isinstance(Signature.build(self.type, ns), stdlib.void)): raise SlValidationError(f"Argument cannot have type '{self.type}'", self, scope=ns.scope) + assert (self.modifier != '=' or self.defvalue is not None) + if (isinstance(Signature.build(self.type, ns), stdlib.void)): raise SlValidationError(f"Argument cannot have type `{self.type}'", self.type, self, scope=ns.scope) + + @property + def mandatory(self): + return not (self.modifier is not None and self.modifier in '?=') class ASTCallargsNode(ASTNonFinalNode): - __slots__ = ('callargs', 'starargs') + callargs: ... + starargs: ... def __init__(self, callargs, starargs, **kwargs): super().__init__(**kwargs) @@ -617,7 +694,7 @@ class ASTCallargsNode(ASTNonFinalNode): if (tl[0].typename == 'OPERATOR' and tl[0].token == '*'): ASTOperatorNode.build(tl) starargs.append(ASTExprNode.build(tl)) - elif (len(tl) >= 2 and tl[1].typename == 'SPECIAL' and tl[1].token == '='): break + elif (len(tl) >= 2 and tl[1].typename == 'SPECIAL' and tl[1].token in '=:'): break else: callargs.append(ASTExprNode.build(tl)) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token == ')'): break comma = ASTSpecialNode.build(tl) @@ -626,7 +703,8 @@ class ASTCallargsNode(ASTNonFinalNode): return cls(callargs, starargs, lineno=lineno, offset=offset) class ASTCallkwargsNode(ASTNonFinalNode): - __slots__ = ('callkwargs', 'starkwargs') + callkwargs: ... + starkwargs: ... def __init__(self, callkwargs, starkwargs, **kwargs): super().__init__(**kwargs) @@ -650,7 +728,7 @@ class ASTCallkwargsNode(ASTNonFinalNode): else: key = ASTIdentifierNode.build(tl) eq = ASTSpecialNode.build(tl) - if (eq.special != '='): raise SlSyntaxExpectedError("'='", eq) + if (eq.special not in '=:'): raise SlSyntaxExpectedError("'=' or ':'", eq) value = ASTExprNode.build(tl) callkwargs.append((key, value)) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token == ')'): break @@ -660,31 +738,41 @@ class ASTCallkwargsNode(ASTNonFinalNode): return cls(callkwargs, starkwargs, lineno=lineno, offset=offset) class ASTCallableNode(ASTNode): - def validate(self, ns): + def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) - code_ns.define(self, redefine=True) + if (hasattr(self, 'name')): + code_ns.define(self, redefine=True) + code_ns.values[self.name] = ... for i in self.argdefs: code_ns.define(i, redefine=True) + code_ns.values[i.name] = ... self.code.validate(code_ns) def optimize(self, ns): super().optimize(ns) code_ns = ns.derive(self.code.name) + for i in self.argdefs: + code_ns.values[i.name] = ... + code_ns.values.parent = None # XXX self.code.optimize(code_ns) class ASTFunctionNode(ASTCallableNode): - def validate(self, ns): + def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) - rettype = common_type((i.value for i in self.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')), code_ns) or stdlib.void() - if (self.type.type.identifier == 'auto'): self.type.type.identifier = rettype.typename - else: - expected = Signature.build(self.type, ns) - if (rettype != expected): raise SlValidationError(f"Returning value of type '{rettype}' from function with return type '{expected}'", self, scope=ns.scope) + rettype = Signature.build(self.type, ns) + return_nodes = tuple(i.value for i in self.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')) + if (not return_nodes and rettype != stdlib.void()): raise SlValidationError(f"Not returning value from function with return type `{rettype}'", self.code, self, scope=ns.scope) + for i in return_nodes: + fsig = Signature.build(i, code_ns) + if (rettype == stdlib.void() and fsig != rettype): raise SlValidationError(f"Returning value from function with return type `{rettype}'", i, self, scope=ns.scope) + if (common_type((fsig, rettype), code_ns) is None): raise SlValidationError(f"Returning value of incompatible type `{fsig}' from function with return type `{rettype}'", i, self, scope=ns.scope) class ASTLambdaNode(ASTNonFinalNode, ASTFunctionNode): - __slots__ = ('argdefs', 'type', 'code') + argdefs: ... + type: ... + code: ... def __init__(self, argdefs, type, code, **kwargs): super().__init__(**kwargs) @@ -694,7 +782,7 @@ class ASTLambdaNode(ASTNonFinalNode, ASTFunctionNode): return f"({S(', ').join(self.argdefs)}) -> {self.type}" def __repr__(self): - return f"" + return f"" def __str__(self): return f"{self.__fsig__()} {f'= {self.code.nodes[0].value}' if (len(self.code.nodes) == 1 and isinstance(self.code.nodes[0], ASTKeywordExprNode) and self.code.nodes[0].keyword.keyword == 'return') else self.code}" @@ -708,22 +796,24 @@ class ASTLambdaNode(ASTNonFinalNode, ASTFunctionNode): if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): - argdefs.append(ASTArgdefNode.build(tl)) + argdef = ASTArgdefNode.build(tl) + if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") + argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) - arrow = ASTOperatorNode.build(tl) - if (arrow.operator != '->'): raise SlSyntaxExpectedError("'->'", arrow) + arrow = ASTSpecialNode.build(tl) + if (arrow.special != '->'): raise SlSyntaxExpectedError("'->'", arrow) type = ASTTypedefNode.build(tl) if (tl and (tl[0].typename != 'SPECIAL' or tl[0].token not in (*'={',))): raise SlSyntaxExpectedError("'=' or '{'", tl[0]) cdef = ASTSpecialNode.build(tl) if (cdef.special != '='): raise SlSyntaxExpectedError('=', cdef) - code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name='') + code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name='', lineno=lineno, offset=offset) return cls(argdefs, type, code, lineno=lineno, offset=offset) class ASTBlockNode(ASTNonFinalNode): - __slots__ = ('code',) + code: ... def __init__(self, code, **kwargs): super().__init__(**kwargs) @@ -737,24 +827,35 @@ class ASTBlockNode(ASTNonFinalNode): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset - if (tl[0].typename == 'SPECIAL' and tl[0].token == '{'): - ASTSpecialNode.build(tl) - code = (yield from ASTCodeNode.build('')) + if (tl[0].typename == 'SPECIAL' and tl[0].token == '{'): code = (yield from ASTCodeNode.build(tl, name='')) else: yield '' - code = ASTCodeNode([ASTExprNode.build(tl)], name='') + expr = ASTExprNode.build(tl) + code = ASTCodeNode([expr], name='', lineno=expr.lineno, offset=expr.offset) return cls(code, lineno=lineno, offset=offset) + def validate(self, ns): + super().validate(ns) + self.code.validate(ns) + + def optimize(self, ns): + super().optimize(ns) + self.code.optimize(ns) + class ASTFinalNode(ASTNode): pass class ASTDefinitionNode(ASTNode): - def validate(self, ns): + def validate(self, ns): # XXX. Signature.build(self, ns) + ns.define(self) super().validate(ns) class ASTFuncdefNode(ASTFinalNode, ASTDefinitionNode, ASTFunctionNode): - __slots__ = ('type', 'name', 'argdefs', 'code') + type: ... + name: ... + argdefs: ... + code: ... def __init__(self, type, name, argdefs, code, **kwargs): super().__init__(**kwargs) @@ -764,12 +865,12 @@ class ASTFuncdefNode(ASTFinalNode, ASTDefinitionNode, ASTFunctionNode): return f"{self.type or 'def'} {self.name}({S(', ').join(self.argdefs)})" def __repr__(self): - return f"" + return f"" def __str__(self): isexpr = (len(self.code.nodes) == 1 and isinstance(self.code.nodes[0], ASTKeywordExprNode) and self.code.nodes[0].keyword.keyword == 'return') r = f"{self.__fsig__()} {f'= {self.code.nodes[0].value}' if (isexpr) else self.code}" - return r if (isexpr) else r.join('\n\n') + return r @classmethod def build(cls, tl): @@ -783,24 +884,27 @@ class ASTFuncdefNode(ASTFinalNode, ASTDefinitionNode, ASTFunctionNode): argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): argdef = ASTArgdefNode.build(tl) - if (argdefs and argdef.value is None and argdefs[-1].value is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") + if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) if (not tl): raise SlSyntaxExpectedError("'=' or '{'", lineno=lineno, offset=-1) if (tl[0].typename != 'SPECIAL' or tl[0].token not in (*'={',)): raise SlSyntaxExpectedError("'=' or '{'", tl[0]) - cdef = ASTSpecialNode.build(tl) - if (cdef.special == '='): + if (tl[0].token == '{'): code = (yield from ASTCodeNode.build(tl, name=name.identifier)) + else: + cdef = ASTSpecialNode.build(tl) + assert (cdef.special == '=') yield name.identifier - code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name=name.identifier) - elif (cdef.special == '{'): - code = (yield from ASTCodeNode.build(name.identifier)) + code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name=name.identifier, lineno=lineno, offset=offset) return cls(type, name, argdefs, code, lineno=lineno, offset=offset) class ASTClassdefNode(ASTFinalNode, ASTDefinitionNode, ASTCallableNode): - __slots__ = ('name', 'bases', 'code', 'type') + name: ... + bases: ... + code: ... + type: ... argdefs = () @@ -810,7 +914,7 @@ class ASTClassdefNode(ASTFinalNode, ASTDefinitionNode, ASTCallableNode): self.type = ASTTypedefNode([], self.name, lineno=self.lineno, offset=self.offset) def __repr__(self): - return f"" + return f"" def __str__(self): return f"class {self.name}{S(', ').join(self.bases).join('()') if (self.bases) else ''} {self.code}" @@ -832,14 +936,14 @@ class ASTClassdefNode(ASTFinalNode, ASTDefinitionNode, ASTCallableNode): if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) - cdef = ASTSpecialNode.build(tl) - if (cdef.special != '{'): raise SlSyntaxExpectedError("'{'", cdef) - code = (yield from ASTCodeNode.build(name.identifier)) + if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token != '{'): raise SlSyntaxExpectedError("'{'", tl[0]) + code = (yield from ASTCodeNode.build(tl, name=name.identifier)) return cls(name, bases, code, lineno=lineno, offset=offset) class ASTKeywordExprNode(ASTFinalNode): - __slots__ = ('keyword', 'value') + keyword: ... + value: ... def __init__(self, keyword, value, **kwargs): super().__init__(**kwargs) @@ -859,9 +963,7 @@ class ASTKeywordExprNode(ASTFinalNode): if (not tl): raise SlSyntaxExpectedMoreTokensError('import', lineno=lineno) lineno_, offset_ = tl[0].lineno, tl[0].offset value = ASTIdentifierNode(str().join(tl.pop(0).token for _ in range(len(tl))), lineno=lineno_, offset=offset_) # TODO Identifier? (or document it) - elif (keyword.keyword == 'delete'): - value = ASTIdentifierNode.build(tl) - #if (not value): raise SlSyntaxExpectedError('identifier', lineno=lineno, offset=-1) + elif (keyword.keyword == 'delete'): value = ASTIdentifierNode.build(tl) elif (tl): value = ASTExprNode.build(tl) else: value = None @@ -889,18 +991,20 @@ class ASTKeywordExprNode(ASTFinalNode): optimize_ast(ast, validate_ast(ast)) module_ns = validate_ast(ast) except (SlSyntaxError, SlValidationError) as ex: - ex.line = src.split('\n')[ex.lineno-1] - raise SlValidationError(f"Error importing {self.value}", self, scope=ns.scope) from ex + ex.srclines = src.split('\n') + raise SlValidationError(f"Error importing {self.value}", self.value, self, scope=ns.scope) from ex if (name != '*'): ns.define(ASTIdentifierNode(name, lineno=self.value.lineno, offset=self.value.offset)) # TODO object else: ns.signatures.update(module_ns.signatures) # TODO? - #return # XXX? elif (self.keyword.keyword == 'delete'): - if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, scope=ns.scope) + if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, self, scope=ns.scope) ns.delete(self.value) super().validate(ns) class ASTKeywordDefNode(ASTFinalNode): - __slots__ = ('keyword', 'name', 'argdefs', 'code') + keyword: ... + name: ... + argdefs: ... + code: ... def __init__(self, keyword, name, argdefs, code, **kwargs): super().__init__(**kwargs) @@ -908,7 +1012,7 @@ class ASTKeywordDefNode(ASTFinalNode): if (self.name is None): self.name = ASTIdentifierNode(self.code.name, lineno=self.lineno, offset=self.offset) def __repr__(self): - return f"" + return f"" def __str__(self): return f"{self.keyword}{' '+S(', ').join(self.argdefs).join('()') if (isinstance(self.keyword.keyword, DefArgsKeyword)) else f' {self.name}' if (isinstance(self.keyword.keyword, DefNamedKeyword)) else ''} {self.code}" @@ -930,41 +1034,54 @@ class ASTKeywordDefNode(ASTFinalNode): argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): argdef = ASTArgdefNode.build(tl) - if (argdefs and argdef.value is None and argdefs[-1].value is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") + if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) - cdef = ASTSpecialNode.build(tl) - if (cdef.special != '{'): raise SlSyntaxExpectedError('{', cdef) - code = (yield from ASTCodeNode.build(f"<{keyword}>")) + if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token != '{'): raise SlSyntaxExpectedError('{', tl[0]) + code = (yield from ASTCodeNode.build(tl, name=f"<{keyword}>")) return cls(keyword, name, argdefs, code, lineno=lineno, offset=offset) - def validate(self, ns): + def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) if (isinstance(self.keyword.keyword, DefArgsKeyword)): for i in self.argdefs: code_ns.define(i, redefine=True) + code_ns.values[i.name] = ... self.code.validate(code_ns) def optimize(self, ns): super().optimize(ns) code_ns = ns.derive(self.code.name) + if (isinstance(self.keyword.keyword, DefArgsKeyword)): + for i in self.argdefs: + code_ns.values[i.name] = ... self.code.optimize(code_ns) class ASTAssignvalNode(ASTNode): def validate(self, ns): super().validate(ns) - if (self.name.identifier not in ns): raise SlValidationNotDefinedError(self.name, scope=ns.scope) - vartype = Signature.build(self.name, ns) + if (self.name.identifier not in ns): raise SlValidationNotDefinedError(self.name, self, scope=ns.scope) + varsig = Signature.build(self.name, ns) if (self.value is not None): - valtype = Signature.build(self.value, ns) - if (valtype != vartype and vartype != valtype): raise SlValidationError(f"Assignment of value '{self.value}' of type '{valtype}' to variable '{self.name}' of type '{vartype}'", self, scope=ns.scope) + valsig = Signature.build(self.value, ns) + if (valsig != varsig and varsig != valsig): raise SlValidationError(f"Assignment of value `{self.value}' of type `{valsig}' to variable `{self.name}' of type `{varsig}'", self.value, self, scope=ns.scope) + varsig.flags.modified = True + ns.values[self.name] = self.value if (not varsig.modifiers.volatile) else ... + + def optimize(self, ns): + super().optimize(ns) + varsig = Signature.build(self.name, ns) + varsig.flags.modified = True + ns.values[self.name] = self.value if (not varsig.modifiers.volatile) else ... class ASTVardefNode(ASTFinalNode, ASTAssignvalNode, ASTDefinitionNode): - __slots__ = ('type', 'name', 'value') + type: ... + name: ... + value: ... def __init__(self, type, name, value, **kwargs): super().__init__(**kwargs) @@ -989,60 +1106,78 @@ class ASTVardefNode(ASTFinalNode, ASTAssignvalNode, ASTDefinitionNode): return cls(type, name, value, lineno=lineno, offset=offset) - def validate(self, ns): + def validate(self, ns): # XXX. if (self.type.type.identifier == 'auto'): self.type.type.identifier = Signature.build(self.value, ns).typename - ns.define(self) + self.flags.optimized_out = False super().validate(ns) + if (self.value is not None): + varsig = Signature.build(self.name, ns) + varsig.flags.modified = False def optimize(self, ns): super().optimize(ns) - #if (Signature.build(self.name, ns).modifiers.const): self.flags.optimized_out = True # TODO + varsig = Signature.build(self.name, ns) + self.flags.optimized_out = False + if (self.value is not None): varsig.flags.modified = False + #if (not varsig.flags.modified and not varsig.modifiers.volatile): # TODO + # self.value = None + # self.flags.optimized_out = True class ASTAssignmentNode(ASTFinalNode, ASTAssignvalNode): - __slots__ = ('name', 'isattr', 'assignment', 'inplace_operator', 'value') + name: ... + isattr: ... + assignment: ... + inplace_operator: ... + value: ... def __init__(self, name, isattr, assignment, inplace_operator, value, **kwargs): super().__init__(**kwargs) self.name, self.isattr, self.assignment, self.inplace_operator, self.value = name, isattr, assignment, inplace_operator, value def __str__(self): - return f"{'.'*self.isattr}{self.name} {self.inplace_operator or ''}= {self.value}" + return f"{'.'*self.isattr}{self.name} {self.inplace_operator or ''}{self.assignment} {self.value}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset - isattr = bool() + isattr = False if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True name = ASTIdentifierNode.build(tl) inplace_operator = None - if (tl and tl[0].typename == 'OPERATOR'): - inplace_operator = ASTOperatorNode.build(tl) - if (not isinstance(inplace_operator.operator, BinaryOperator)): raise SlSyntaxExpectedError('BinaryOperator', inplace_operator) + if (tl and tl[0].typename == 'OPERATOR'): inplace_operator = ASTBinaryOperatorNode.build(tl) assignment = ASTSpecialNode.build(tl) if (assignment.special not in ('=', ':=')): raise SlSyntaxExpectedError('assignment', assignment) value = ASTExprNode.build(tl) return cls(name, isattr, assignment, inplace_operator, value, lineno=lineno, offset=offset) - def validate(self, ns): - valtype = Signature.build(self.value, ns) - if (self.assignment.special == ':='): ns.define(self.name, valtype, redefine=True) + def validate(self, ns): # XXX. + valsig = Signature.build(self.value, ns) + if (self.assignment.special == ':='): ns.define(self.name, valsig, redefine=True) if (self.isattr): return # TODO super().validate(ns) - vartype = Signature.build(self.name, ns) - if (vartype.modifiers.const): raise SlValidationError(f"Assignment to const '{self.name}'", self, scope=ns.scope) + varsig = Signature.build(self.name, ns) + if (varsig.modifiers.const): raise SlValidationError(f"Assignment to const `{self.name}'", self.name, self, scope=ns.scope) + if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding + + def optimize(self, ns): + super().optimize(ns) + if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding class ASTUnpackAssignmentNode(ASTFinalNode, ASTAssignvalNode): - __slots__ = ('names', 'assignment', 'inplace_operator', 'value') + names: ... + assignment: ... + inplace_operator: ... + value: ... def __init__(self, names, assignment, inplace_operator, value, **kwargs): super().__init__(**kwargs) self.names, self.assignment, self.inplace_operator, self.value = names, assignment, inplace_operator, value def __str__(self): - return f"{S(', ').join(self.names)} {self.inplace_operator or ''}= {self.value}" + return f"{S(', ').join(self.names)} {self.inplace_operator or ''}{self.assignment} {self.value}" @classmethod def build(cls, tl): @@ -1061,18 +1196,76 @@ class ASTUnpackAssignmentNode(ASTFinalNode, ASTAssignvalNode): return cls(names, assignment, inplace_operator, value, lineno=lineno, offset=offset) - def validate(self, ns): - valtype = Signature.build(self.value, ns) + def validate(self, ns): # XXX. + valsig = Signature.build(self.value, ns) if (self.assignment.special == ':='): - for name, type in zip(self.names, valtype.valtypes): + for name, type in zip(self.names, valsig.valtypes): ns.define(name, type, redefine=True) - vartypes = tuple(Signature.build(i, ns) for i in self.names) - for name, vartype in zip(self.names, vartypes): - if (vartype.modifiers.const): raise SlValidationError(f"Assignment to const '{name}'", self, scope=ns.scope) - if (vartypes != valtype.valtypes): raise SlValidationError(f"Unpacking assignment of '{valtype}' to variables of types {vartypes}", self, scope=ns.scope) + ASTNode.validate(self, ns) + for name, (ii, valtype) in zip(self.names, enumerate(valsig.valtypes)): + if (name.identifier not in ns): raise SlValidationNotDefinedError(name, self, scope=ns.scope) + varsig = Signature.build(name, ns) + if (varsig.modifiers.const): raise SlValidationError(f"Assignment to const `{name}'", name, self, scope=ns.scope) + if (varsig != valtype): raise SlValidationError(f"Assignment of `{valtype}' to variable {name} of type {varsig}", self.value.value.values[ii] if (isinstance(self.value, ASTValueNode) and hasattr(self.value.value, 'values')) else name, self, scope=ns.scope) + varsig.flags.modified = True + if (self.inplace_operator is not None): ns.values[name] = ... # TODO folding + + def optimize(self, ns): + super().optimize(ns) + if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding + +class ASTUnaryOperationNode(ASTPrimitiveNode): + name: ... + isattr: ... + unary_operator: ... + + def __init__(self, name, isattr, unary_operator, **kwargs): + super().__init__(**kwargs) + self.name, self.isattr, self.unary_operator = name, isattr, unary_operator + + def validate(self, ns): + if (self.isattr): return # TODO + super().validate(ns) + varsig = Signature.build(self.name, ns) + if (varsig.modifiers.const): raise SlValidationError(f"Unary operation `{self.unary_operator}' on const `{self.name}'", self.name, self, scope=ns.scope) + +class ASTUnaryPreOperationNode(ASTUnaryOperationNode, ASTFinalNode): + def __str__(self): + return f"{self.unary_operator}{'.'*self.isattr}{self.name}" + + @classmethod + def build(cls, tl): + super().build(tl) + lineno, offset = tl[0].lineno, tl[0].offset + + unary_operator = ASTUnaryOperatorNode.build(tl) + if (unary_operator.operator not in unaryops): raise SlSyntaxExpectedError('Unary operation', unary_operator) + isattr = False + if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True + name = ASTIdentifierNode.build(tl) + + return cls(name, isattr, unary_operator, lineno=lineno, offset=offset) + +class ASTUnaryPostOperationNode(ASTUnaryOperationNode, ASTFinalNode): + def __str__(self): + return f"{'.'*self.isattr}{self.name}{self.unary_operator}" + + @classmethod + def build(cls, tl): + super().build(tl) + lineno, offset = tl[0].lineno, tl[0].offset + + isattr = False + if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True + name = ASTIdentifierNode.build(tl) + unary_operator = ASTUnaryOperatorNode.build(tl) + if (unary_operator.operator not in unaryops): raise SlSyntaxExpectedError('Unary operation', unary_operator) + + return cls(name, isattr, unary_operator, lineno=lineno, offset=offset) class ASTAttrsetNode(ASTFinalNode): - __slots__ = ('value', 'assignment') + value: ... + assignment: ... def __init__(self, value, assignment, **kwargs): super().__init__(**kwargs) @@ -1097,10 +1290,12 @@ class ASTAttrsetNode(ASTFinalNode): super().validate(ns) # TODO: attr check #valsig = Signature.build(self.value, ns) - #if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"'{valsig}' does not support attribute operation '{self.optype}' with attr '{self.attr}'", self, scope=ns.scope) + #if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"`{valsig}' does not support attribute operation `{self.optype}' with attr `{self.attr}'", self.optype, self, scope=ns.scope) class ASTFunccallNode(ASTFinalNode): - __slots__ = ('callable', 'callargs', 'callkwargs') + callable: ... + callargs: ... + callkwargs: ... def __init__(self, callable, callargs, callkwargs, **kwargs): super().__init__(**kwargs) @@ -1110,11 +1305,11 @@ class ASTFunccallNode(ASTFinalNode): return f"{str(self.callable).join('()') if (isinstance(self.callable, ASTValueNode) and isinstance(self.callable.value, (ASTFunccallNode, ASTLambdaNode))) else self.callable}({self.callargs}{', ' if (str(self.callargs) and str(self.callkwargs)) else ''}{self.callkwargs})" @classmethod - def build(cls, tl): + def build(cls, tl, *, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset - callable = ASTExprNode.build(tl, fcall=True) + callable = ASTExprNode.build(tl, fcall=True, attrget=attrget) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) callargs = ASTCallargsNode.build(tl) @@ -1127,19 +1322,27 @@ class ASTFunccallNode(ASTFinalNode): def validate(self, ns): super().validate(ns) fsig = Signature.build(self.callable, ns) - if (not isinstance(fsig, Callable)): raise SlValidationError(f"'{self.callable}' of type '{fsig}' is not callable", self.callable, scope=ns.scope) - callargssig = CallArguments.build(self, ns) # TODO: starargs - if (callargssig not in fsig.call): raise SlValidationError(f"Parameters '({callargssig})' don't match any of '{self.callable}' signatures:\n{S(fsig.callargssigstr).indent()}\n", self, scope=ns.scope) + if (not isinstance(fsig, Callable)): raise SlValidationError(f"`{self.callable}' of type `{fsig}' is not callable", self.callable, self, scope=ns.scope) + callarguments = CallArguments.build(self, ns) + if (fsig.compatible_call(callarguments, ns) is None): raise SlValidationError(f"Parameters `({callarguments})' don't match any of `{self.callable}' signatures:\n{S(fsig.callargssigstr).indent()}\n", self, scope=ns.scope) + + def optimize(self, ns): + fsig = Signature.build(self.callable, ns) + if (fsig.code is not None): + code_ns = ns.derive(fsig.code.name) + fsig.code.validate(code_ns) + super().optimize(ns) class ASTConditionalNode(ASTFinalNode): - __slots__ = ('condition', 'code') + condition: ... + code: ... def __init__(self, condition, code, **kwargs): super().__init__(**kwargs) self.condition, self.code = condition, code def __repr__(self): - return f"" + return f"" def __str__(self): return f"if {self.condition} {self.code}" @@ -1157,14 +1360,16 @@ class ASTConditionalNode(ASTFinalNode): return cls(condition, code, lineno=lineno, offset=offset) class ASTForLoopNode(ASTFinalNode): - __slots__ = ('name', 'iterable', 'code') + name: ... + iterable: ... + code: ... def __init__(self, name, iterable, code, **kwargs): super().__init__(**kwargs) self.name, self.iterable, self.code = name, iterable, code def __repr__(self): - return f"" + return f"" def __str__(self): return f"for {self.name} in {self.iterable} {self.code}" @@ -1185,20 +1390,26 @@ class ASTForLoopNode(ASTFinalNode): return cls(name, iterable, code, lineno=lineno, offset=offset) def validate(self, ns): - super().validate(ns) # TODO: validate iterability ns.define(self.name, Signature.build(self.iterable, ns).valtype) ns.weaken(self.name) + ns.values[self.name] = ... + super().validate(ns) + + def optimize(self, ns): + self.code.validate(ns) + super().optimize(ns) class ASTWhileLoopNode(ASTFinalNode): - __slots__ = ('condition', 'code') + code: ... # needs to be validated/optimized first (case when the condition is modified from loop body) + condition: ... def __init__(self, condition, code, **kwargs): super().__init__(**kwargs) self.condition, self.code = condition, code def __repr__(self): - return f"" + return f"" def __str__(self): return f"while {self.condition} {self.code}" @@ -1215,8 +1426,16 @@ class ASTWhileLoopNode(ASTFinalNode): return cls(condition, code, lineno=lineno, offset=offset) + #def validate(self, ns): + # Signature.build(self.condition, ns).modifiers.volatile = True + # super().validate(ns) + + def optimize(self, ns): + self.code.validate(ns) + super().optimize(ns) + class ASTElseClauseNode(ASTFinalNode): - __slots__ = ('code',) + code: ... def __init__(self, code, **kwargs): super().__init__(**kwargs) @@ -1260,7 +1479,7 @@ def build_ast(code, name=None, *, interactive=False): if (inspect.isgenerator(r)): code_stack.append((r, next(r))) try: r = next(r) - except StopIteration as ex: code_stack.pop(); r = ex.args[0] + except StopIteration as ex: code_stack.pop(); r = ex.value else: assert (r is None) if (c): @@ -1271,54 +1490,62 @@ def build_ast(code, name=None, *, interactive=False): assert (r is not None) if (c): raise SlSyntaxExpectedNothingError(c[0]) except SlSyntaxEmpty: err.clear(); break - except SlSyntaxNoToken: err.add(SlSyntaxExpectedMoreTokensError(i.__name__[3:-4], lineno=lineno, offset=-2)) + except SlSyntaxNoToken: err.add(SlSyntaxExpectedMoreTokensError(i.__name__[3:-4], lineno=lineno, offset=-1)) except SlSyntaxMultiExpectedError as ex: pass#err.add(ex) # TODO FIXME except SlSyntaxExpectedError as ex: ex.usage = i.__name__[3:-4]; err.add(ex) - else: code_stack[-1][0].send(r); err.clear(); break + else: + code_stack[-1][0].send(r) + err.clear() + break else: if (len(code_stack) > 1 and tl and tl[0].typename == 'SPECIAL' and tl[0].token == '}'): if (tl[1:]): code.insert(ii+1, tl[1:]) try: next(code_stack.pop()[0]) - except StopIteration as ex: code_stack[-1][0].send(ex.args[0]); err.clear() + except StopIteration as ex: code_stack[-1][0].send(ex.value); err.clear() else: raise WTFException() elif (not err): raise SlSyntaxError("Unknown structure", lineno=lineno, offset=offset, length=0, scope='.'.join(i[1] for i in code_stack if i[1])) if (err): raise SlSyntaxMultiExpectedError.from_list(err, scope='.'.join(i[1] for i in code_stack if i[1]) if (code_stack[0][1] is not None) else None) + if (len(code_stack) > 1): raise SlSyntaxExpectedMoreTokensError(code_stack[-1][1], lineno=lineno) + assert (len(code_stack) == 1) try: next(code_stack.pop()[0]) - except StopIteration as ex: return ex.args[0] + except StopIteration as ex: return ex.value def walk_ast_nodes(node): if (isiterable(node) and not isinstance(node, str)): for i in node: yield from walk_ast_nodes(i) if (not isinstance(node, ASTNode)): return yield node - for i in node.__slots__: yield from walk_ast_nodes(getattr(node, i)) + for i in allslots(node): yield from walk_ast_nodes(getattr(node, i)) -class _SignatureBase: pass +class _SignatureBase(ABCSlots): pass class Signature(_SignatureBase): - __slots__ = ('typename', 'modifiers') + operators = {} + typename: ... + modifiers: paramset + flags: paramset - operators = dict() - - @init_defaults - @autocast - def __init__(self, *, typename, modifiers: paramset): - self.typename, self.modifiers = typename, modifiers + @init(typename=..., modifiers=..., flags=...) + def __init__(self): + super().__init__() @property def __reprname__(self): return self.__class__.__name__ def __repr__(self): - return f"<{self.__reprname__} '{self.name}'>" + return f"<{self.__reprname__} `{self.name}'>" + + def __str__(self): + return self.name def __eq__(self, x): return (x is not None and self.typename == x.typename) def __hash__(self): - return hash(tuple(getattr(self, i) for i in self.__slots__)) + return hash(tuple(getattr(self, i) for i in allslots(self))) @property def name(self): @@ -1342,7 +1569,7 @@ class Signature(_SignatureBase): def build(cls, x: ASTAssignvalNode, ns): r = cls.build(x.type, ns) #ns.signatures[x.name.identifier] = r - if (x.value is not None): ns.values[x.name] = x.value if (not r.modifiers.volatile and r.modifiers.const) else None + #if (x.value is not None): ns.values[x.name] = x.value if (False and not r.modifiers.volatile and r.modifiers.const) else ... # XXX TODO (see False) return r @classmethod @@ -1372,17 +1599,25 @@ class Signature(_SignatureBase): @classmethod @dispatch def build(cls, x: ASTListNode, ns): - return Collection(keytype=stdlib.int, valtype=Signature.build(x.type, ns)) + #return Collection(keytype=stdlib.int(), valtype=Signature.build(x.type, ns)) + return stdlib.list(valtype=Signature.build(x.type, ns)) @classmethod @dispatch def build(cls, x: ASTTupleNode, ns): - return MultiCollection(keytype=stdlib.int, valtypes=tuple(Signature.build(t if (t is not None) else v, ns) for t, v in zip(x.types, x.values))) + #return MultiCollection(keytype=stdlib.int(), valtypes=tuple(Signature.build(t if (t is not None) else v, ns) for t, v in zip(x.types, x.values))) + return stdlib.tuple(valtypes=tuple(Signature.build(t if (t is not None) else v, ns) for t, v in zip(x.types, x.values))) @classmethod @dispatch def build(cls, x: ASTFunccallNode, ns): - return cls.build(x.callable, ns).call[CallArguments.build(x, ns)] + callarguments = CallArguments.build(x, ns) + return cls.build(x.callable, ns).compatible_call(callarguments, ns)[1] + + @classmethod + @dispatch + def build(cls, x: ASTLambdaNode, ns): + return Lambda.build(x, ns) @classmethod @dispatch @@ -1399,6 +1634,11 @@ class Signature(_SignatureBase): def build(cls, x: ASTKeywordDefNode, ns): return KeywordDef.build(x, ns) + @classmethod + @dispatch + def build(cls, x: ASTUnaryOperationNode, ns): + return cls.build(x.name, ns) + @classmethod @dispatch def build(cls, x: ASTItemgetNode, ns): @@ -1424,24 +1664,34 @@ class Signature(_SignatureBase): def build(cls, x: _SignatureBase, ns): return x -class Callable(Signature, abc.ABC): - __slots__ = ('call',) +class Callable(Signature): + call: ... + code: ... + + def __init__(self, *, code=None, **kwargs): + super().__init__(**kwargs) + self.code = code + + def compatible_call(self, callarguments, ns): + try: return first((k, v) for k, v in self.call.items() if callarguments.compatible(k)) + except StopIteration: return None @abc.abstractproperty def callargssigstr(self): pass class Function(Callable): - __slots__ = ('name',) + typename = 'function' + name: ... - def __init__(self, *, name, **kwargs): - super().__init__(typename='function', **kwargs) - self.name = name + def __init__(self, *, name, code=None, **kwargs): + super().__init__(**kwargs) + self.name, self.code = name, code self.call = listmap() @property def callargssigstr(self): - return '\n'.join(f"{self.name}({args})" for args, ret in self.call.items()) + return '\n'.join(f"{ret.typename} {self.name}({S(', ').join(args)})" for args, ret in self.call.items()) @staticitemget def attrops(optype, attr): @@ -1453,45 +1703,94 @@ class Function(Callable): @dispatch def build(cls, x: ASTFuncdefNode, ns, *, redefine=False): name = x.name.identifier - if (name not in ns): ns.signatures[name] = cls(name=name) - callargssig = CallArguments(args=tuple(Signature.build(i, ns) for i in x.argdefs)) - if (not redefine and callargssig in ns.signatures[name].call): raise SlValidationRedefinedError(x.name, ns.signatures[name].call[callargssig], scope=ns.scope) - ns.signatures[name].call[callargssig] = Signature.build(x.type, ns) - return ns.signatures[name] + if (name not in ns): fsig = ns.signatures[name] = cls(name=name, code=x.code) + else: fsig = ns.signatures[name] + + argdefs = tuple(x.argdefs) + if (not redefine and argdefs in fsig.call and name not in ns.weak): raise SlValidationRedefinedError(x.name, x.__fsig__(), scope=ns.scope) + if (x.type.type.identifier == 'auto'): # XXX@ TODO FIXME + code_ns = ns.derive(x.code.name) + rettype = common_type((i.value for i in x.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')), code_ns) or stdlib.void() + else: rettype = Signature.build(x.type, ns) + fsig.call[argdefs] = rettype + dlog(fsig.call, x) + return fsig + +class Lambda(Callable): + typename = 'lambda' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.call = listmap() + + @property + def callargssigstr(self): + return '\n'.join(f"({S(', ').join(args)}) -> {ret.typename}" for args, ret in self.call.items()) + + @staticitemget + def attrops(optype, attr): + if (optype == '.'): + if (attr == 'map'): return stdlib._map() + raise KeyError() + + @classmethod + @dispatch + def build(cls, x: ASTLambdaNode, ns, *, redefine=False): + fsig = cls(code=x.code) + argdefs = tuple(x.argdefs) + if (x.type.type.identifier == 'auto'): + code_ns = ns.derive(x.code.name) + rettype = common_type((i.value for i in x.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')), code_ns) or stdlib.void() + else: rettype = Signature.build(x.type, ns) + fsig.call[argdefs] = rettype + return fsig class KeywordDef(Callable): - __slots__ = ('name',) + typename = 'keyworddef' + name: ... def __init__(self, *, name, **kwargs): - super().__init__(typename='keyworddef', **kwargs) + super().__init__(**kwargs) self.name = name self.call = listmap() @property def callargssigstr(self): - return '\n'.join(f"{self.name}({args})" for args, ret in self.call.items()) + return '\n'.join(f"{self.name}({S(', ').join(args)})" for args, ret in self.call.items()) @classmethod @dispatch def build(cls, x: ASTKeywordDefNode, ns, *, redefine=False): name = x.name.identifier - if (name not in ns): ns.signatures[name] = cls(name=name) - callargssig = CallArguments(args=tuple(Signature.build(i, ns) for i in x.argdefs or ())) - if (not redefine and callargssig in ns.signatures[name].call): raise SlValidationRedefinedError(x.name, ns.signatures[name].call[callargssig], scope=ns.scope) - ns.signatures[name].call[callargssig] = stdlib.void - return ns.signatures[name] + if (name not in ns): fsig = ns.signatures[name] = cls(name=name, code=x.code) + else: fsig = ns.signatures[name] -class Object(Signature): pass + argdefs = tuple(x.argdefs) if (x.argdefs is not None) else () + if (not redefine and argdefs in fsig.call and name not in ns.weak): raise SlValidationRedefinedError(x.name, fsig.call[argdefs], scope=ns.scope) + fsig.call[argdefs] = stdlib.void + return fsig + +class Object(Signature): + typename = 'Object' class Collection(Object): - __slots__ = ('keytype', 'valtype') + typename = 'collection' + keytype: ... + valtype: ... - def __init__(self, *, keytype, valtype): + def __init__(self, *, keytype, valtype, **kwargs): + super().__init__(**kwargs) self.keytype, self.valtype = keytype, valtype + def __repr__(self): + return f"" + @property def typename(self): return self.valtype.typename + @typename.setter + def typename(self, x): + pass @itemget @instantiate @@ -1500,11 +1799,12 @@ class Collection(Object): raise KeyError() class MultiCollection(Collection): - __slots__ = ('valtypes', 'typename') + typename = 'MultiCollection' + valtypes: ... def __init__(self, *, keytype, valtypes): - Object.__init__(self, typename='tuple') - self.keytype, self.valtypes = keytype, valtypes + super().__init__(keytype=keytype, valtype=stdlib.Any) + self.valtypes = valtypes @itemget @instantiate @@ -1513,23 +1813,30 @@ class MultiCollection(Collection): raise KeyError() class Class(Object, Callable): - __slots__ = ('name', 'scope', 'constructor') + typename = 'class' + name: ... + scope: ... + constructor: ... def __init__(self, *, name, scope, **kwargs): - super().__init__(typename='class', **kwargs) + super().__init__(**kwargs) self.name, self.scope = name, scope self.constructor = listmap() def __str__(self): return self.name + def compatible_call(self, callarguments, ns): + try: return first((k, v) for k, v in self.constructor.items() if callarguments.compatible(k)) + except StopIteration: return None + @property def callargssigstr(self): - return '\n'.join(f"{self.name}({args})" for args, ret in self.call.items()) + return '\n'.join(f"{self.name}({S(', ').join(args)})" for args, ret in self.constructor.items()) @itemget - def call(self, callargssig): - return self.constructor[callargssig] + def call(self, argdefs): + return self.constructor[argdefs] @itemget def attrops(self, optype, attr): @@ -1541,57 +1848,166 @@ class Class(Object, Callable): @dispatch def build(cls, x: ASTClassdefNode, ns, *, redefine=False): name = x.name.identifier - if (not redefine and name in ns): raise SlValidationRedefinedError(x.name, ns.signatures[name], scope=ns.scope) - else: ns.signatures[name] = cls(name=name, scope=ns.derive(x.code.name)) + #if (not redefine and name in ns and name not in ns.weak): raise SlValidationRedefinedError(x.name, ns.signatures[name], scope=ns.scope) + fsig = ns.signatures[name] = cls(name=name, scope=ns.derive(x.code.name), code=x.code) + for i in x.code.nodes: if (isinstance(i, ASTKeywordDefNode) and i.keyword.keyword == 'constr'): - callargssig = CallArguments(args=tuple(Signature.build(j, ns) for j in i.argdefs or ())) - if (not redefine and callargssig in ns.signatures[name].constructor): raise SlValidationRedefinedError(x.name, ns.signatures[name].constructor[callargssig], scope=ns.scope) - ns.signatures[name].constructor[callargssig] = ns.signatures[name] - return ns.signatures[name] + argdefs = tuple(i.argdefs) if (i.argdefs is not None) else () + if (not redefine and argdefs in fsig.constructor and name not in ns.weak): raise SlValidationRedefinedError(x.name, fsig.constructor[argdefs], scope=ns.scope) + fsig.constructor[argdefs] = fsig -class CallArguments: - __slots__ = ('args', 'starargs', 'kwargs', 'starkwargs') + return fsig + +class CallArguments(Slots): + args: ... + starargs: ... + kwargs: ... + starkwargs: ... + ns: ... @init_defaults @autocast - def __init__(self, *, args: tuple, starargs: tuple, kwargs: tuple, starkwargs: tuple): - self.args, self.starargs, self.kwargs, self.starkwargs = args, starargs, kwargs, starkwargs + def __init__(self, *, args: tuple, starargs: tuple, kwargs: tuple, starkwargs: tuple, ns): + self.args, self.starargs, self.kwargs, self.starkwargs, self.ns = args, starargs, kwargs, starkwargs, ns - def __repr__(self): + def __str__(self): return S(', ').join((*self.args, *('*'+i for i in self.starargs), *(f"{v} {k}" for k, v in self.kwargs), *('**'+i for i in self.starkwargs))) def __eq__(self, x): - return all(getattr(self, i) == getattr(x, i) for i in self.__slots__) + return all(getattr(self, i) == getattr(x, i) for i in allslots(self)) - def __hash__(self): - return hash(tuple(getattr(self, i) for i in self.__slots__)) + @dispatch + def compatible(self, x: typing.Iterable[ASTArgdefNode]): + # type(x[i]) = ASTArgdefNode + # type(args[i]) = ASTExprNode + # type(starargs[i]) = ASTExprNode + # type(kwargs[i]) = tuple(ASTIdentifierNode, ASTExprNode) + # type(starkwargs[i]) = ASTExprNode - @property - def nargs(self): - return sum(len(getattr(self, i)) for i in self.__slots__) + x = Slist(x) + args, starargs, kwargs, starkwargs = list(self.args), list(self.starargs), dict(self.kwargs), list(self.starkwargs) + has_mandatory_left = bool(x) + optional_left = max(0, len(args) - len(tuple(i for i in x if i.mandatory))) + to_fill_optionals = list() + + #dlog(1, x, args) + + while (x): + x.discard() + for ii, arg in enumerate(x): # type, name, modifier, defvalue + if (not arg.mandatory): + if (has_mandatory_left): + if (len(args) > optional_left): to_fill_optionals.append(args.pop(0)) + continue + elif (to_fill_optionals): + args += to_fill_optionals + to_fill_optionals.clear() + + if (args): + posarg = args.pop(0) + sig = Signature.build(posarg, self.ns) + if (common_type((arg.type, sig), self.ns) is None): return False + x.to_discard(ii) + continue + + if (starargs): + stararg = starargs.pop(0) + sig = Signature.build(stararg, self.ns) + #if (common_type((arg.type, *stararg.type), self.ns) is not None): return False # TODO: typecheck! + x.to_discard(ii) + continue + + if (kwargs): + continue + + if (starkwargs): + continue + + # XXX! + + break + else: has_mandatory_left = False; continue + x.discard() + break + + return not any((has_mandatory_left, args, starargs, kwargs, starkwargs)) + + #@property # XXX needed? | FIXME star[kw]args + #def nargs(self): + # return len(self.args) + sum(i.length for i in self.starargs) + len(self.kwargs) + sum(i.length for i in self.starkwargs) + # #return sum(len(getattr(self, i)) for i in allslots(self)) @classmethod @dispatch - def build(cls, x: ASTFunccallNode, ns): # TODO: starargs - return cls(args=tuple(Signature.build(i, ns) for i in x.callargs.callargs), kwargs=tuple((k, Signature.build(v, ns)) for k, v in x.callkwargs.callkwargs)) + def build(cls, x: ASTFunccallNode, ns): + return cls(args=x.callargs.callargs, + starargs=x.callargs.starargs, + kwargs=x.callkwargs.callkwargs, + starkwargs=x.callkwargs.starkwargs, + ns=ns) -class Namespace: - __slots__ = ('signatures', 'scope', 'values', 'weak', 'refcount', 'flags', 'warnclasses') +class Namespace(Slots): + class _Signatures(Slots): + signatures: dict + parent: None - class _Values: - @init_defaults - def __init__(self, values: dict): - self.values = values + @init(signatures=..., parent=...) + def __init__(self): + super().__init__() + assert (self.parent not in (self, self.signatures)) + if (isinstance(self.parent, Namespace._Signatures)): assert (self.parent.parent not in (self, self.signatures)) - def __contains__(self, x): - try: self[x] - except (DispatchError, KeyError): return False - else: return True + def __iter__(self): + return iter(self.keys()) + + @dispatch + def __contains__(self, x: str): + return x in self.signatures or (self.parent is not None and x in self.parent) + + @dispatch + def __getitem__(self, x: str): + try: return self.signatures[x] + except KeyError: + if (self.parent is not None): + try: return self.parent[x] + except KeyError: pass + raise + + @dispatch + def __setitem__(self, k: str, v: Signature): + self.signatures[k] = v + + @dispatch + def __delitem__(self, x: ASTIdentifierNode): + del self[x.identifier] + + @dispatch + def __delitem__(self, x: str): + del self.signatures[x] + + def items(self): + return self.signatures.items() if (self.parent is None) else {**self.parent, **self.signatures}.items() + + def keys(self): + return self.signatures.keys() if (self.parent is None) else (*self.signatures.keys(), *self.parent.keys()) + + def copy(self): + return self.__class__(signatures=self.signatures.copy(), parent=self.parent) + + class _Values(Slots): + values: dict + parent: None + + @init(values=..., parent=...) + def __init__(self): + super().__init__() + assert (self.parent not in (self, self.values)) + if (isinstance(self.parent, Namespace._Values)): assert (self.parent.parent not in (self, self.values)) @dispatch def __getitem__(self, x: ASTLiteralNode): - return eval(str(x.literal)) + return eval_literal(x) @dispatch def __getitem__(self, x: ASTValueNode): @@ -1603,7 +2019,13 @@ class Namespace: @dispatch def __getitem__(self, x: str): - return self.values[x] + try: return self.values[x] + except KeyError: + if (self.parent is not None): + try: return self.parent[x] + except KeyError: pass + #except RecursionError: pass # TODO FIXME XXX! ??? (tests/fib.sl) + raise @dispatch def __setitem__(self, k, v: ASTValueNode): @@ -1611,19 +2033,16 @@ class Namespace: @dispatch def __setitem__(self, x: ASTIdentifierNode, v: ASTLiteralNode): - self.values[x.identifier] = eval(str(v.literal)) + self[x.identifier] = eval_literal(v) @dispatch - def __setitem__(self, x: ASTIdentifierNode, v: NoneType): - pass + def __setitem__(self, x: ASTIdentifierNode, v): + self[x.identifier] = v @dispatch def __setitem__(self, k: str, v): - self.values[k] = v - - @dispatch - def __setitem__(self, k, v): - pass + if (self.parent is None or k in self.values): self.values[k] = v + else: self.parent[k] = v @dispatch def __delitem__(self, x: ASTValueNode): @@ -1639,65 +2058,59 @@ class Namespace: def get(self, x): try: return self[x] - except (DispatchError, KeyError): return None + except KeyError: return None def items(self): - return self.values.items() + return self.values.items() if (self.parent is None) else {**self.parent, **self.values}.items() def copy(self): - return self.__class__(values=self.values.copy()) + return self.__class__(values=self.values.copy(), parent=self.parent) - @init_defaults - def __init__(self, scope, *, signatures: dict, values: _Values, weak: set, refcount: lambda: Sdict(int), warnclasses: paramset, **kwargs): - self.scope, self.signatures, self.values, self.weak, self.refcount, self.warnclasses = scope, signatures, values, weak, refcount, warnclasses - self.flags = paramset(k for k, v in kwargs.items() if v) - #self.derive.clear_cache() # TODO FIXME (also cachedproperty) + scope: ... + signatures: lambda: Namespace._Signatures(parent=builtin_names) + values: lambda: Namespace._Values(parent={i: ... for i in builtin_names}) + weak: set + refcount: lambda: Sdict(int) + warnclasses: paramset + flags: lambda: Sdict(paramset) + olevel: int + + @init(signatures=..., values=..., weak=..., refcount=..., warnclasses=..., flags=..., olevel=...) + def __init__(self, scope): + self.scope = scope def __repr__(self): - return f"" + return f"" def __contains__(self, x): - return (x in builtin_names or x in self.signatures) + return x in self.signatures - @cachedfunction - def derive(self, scope): - return Namespace(signatures=self.signatures.copy(), values=self.values.copy(), weak=self.weak.copy(), scope=self.scope+'.'+scope) - - #@dispatch - #def define(self, x: ASTIdentifierNode): - # if (x.identifier in self and x.identifier not in self.weak): raise SlValidationRedefinedError(x, self.signatures[x.identifier], scope=self.scope) - # self.values[x] = None - # self.signatures[x.identifier] = None - # self.weak.discard(x.identifier) + @lrucachedfunction + def derive(self, scope, *, append=True): + assert (type(scope) is str) + #return Namespace(signatures=self.signatures.copy(), values=self._Values(parent=self.values), weak=self.weak, scope=self.scope+'.'+scope if (append) else scope) + return Namespace(signatures=self.signatures, values=self._Values(parent=self.values), weak=self.weak | set(self.signatures), scope=self.scope+'.'+scope if (append) else scope) # XXX. + #return Namespace(signatures=self._Signatures(parent=self.signatures), values=self._Values(parent=self.values), weak=self.weak | set(self.signatures), scope=self.scope+'.'+scope if (append) else scope) @dispatch def define(self, x: ASTFuncdefNode): - return self.define(x, redefine=True) - - #@dispatch - #def define(self, x: str, *, value=None): - # assert (x not in self) - # self.values[x] = value - # self.signatures[x] = None + self.define(x, redefine=True) + self.values[x.name] = ... @dispatch def define(self, x: lambda x: hasattr(x, 'name'), sig=None, *, redefine=False): if (redefine): - try: del self.values[x.name] + try: del self.signatures[x.name] except KeyError: pass - try: del self.signatures[x.name.identifier] + try: del self.values[x.name] except KeyError: pass self.define(x.name, sig if (sig is not None) else Signature.build(x, self), redefine=redefine) @dispatch def define(self, x: ASTIdentifierNode, sig, *, redefine=False): - if (redefine): - try: del self.values[x] - except KeyError: pass - try: del self.signatures[x.identifier] - except KeyError: pass - elif (x.identifier in self and x.identifier not in self.weak): raise SlValidationRedefinedError(x, self.signatures[x.identifier], scope=self.scope) + if (not redefine and x.identifier in self and x.identifier not in self.weak): raise SlValidationRedefinedError(x, self.signatures[x.identifier], scope=self.scope) self.signatures[x.identifier] = sig + self.values.values[x.identifier] = None self.weak.discard(x.identifier) @dispatch @@ -1720,41 +2133,78 @@ from . import stdlib from .stdlib import builtin_names def validate_ast(ast, ns=None): - Namespace.derive.clear_cache() - return ast.code.validate(ns) + return ast.validate(ns) -class SlValidationException(Exception): pass +class SlNodeException(Exception, ABCSlots): + node: ... + ctxnode: ... + srclines: ... + scope: ... -class SlValidationError(SlValidationException): - __slots__ = ('desc', 'node', 'line', 'scope') - - def __init__(self, desc, node, line='', *, scope=None): - self.desc, self.node, self.line, self.scope = desc, node, line, scope + def __init__(self, node, ctxnode=None, *, srclines=(), scope=None): + self.node, self.ctxnode, self.srclines, self.scope = node, ctxnode if (ctxnode is not None) else node, srclines, scope def __str__(self): - l, line = lstripcount(self.line.partition('\n')[0].replace('\t', ' '), ' \t') - offset = (self.node.offset-l) if (self.node.offset >= 0) else (len(line)+self.node.offset+1) - return (f'\033[2m(in {self.scope})\033[0m ' if (self.scope is not None) else '')+f"Validation error: {self.desc}{self.at}"+(':\n'+\ - ' \033[1m'+line[:offset]+'\033[91m'*(self.node.offset >= 0)+line[offset:]+'\033[0m\n'+\ - ' '+' '*offset+'\033[95m^'+'~'*(self.node.length-1)+'\033[0m' if (line) else '') + \ - (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__}" if (self.__cause__ is not None) else '') + line = self.srclines[self.lineno-1].partition('\n')[0].rstrip() if (self.srclines) else '' + l = lstripcount(line)[0] + + ctx = self.ctxnode + minlineno = min((getattr(ctx, i).lineno for i in allslots(ctx) if getattr(getattr(ctx, i), 'lineno', 0) > 0), default=min(ctx.lineno, self.lineno)) + maxlineno = max((getattr(ctx, i).lineno for i in allslots(ctx) if getattr(getattr(ctx, i), 'lineno', 0) > 0), default=max(ctx.lineno, self.lineno)) + minoffset = min((getattr(ctx, i).offset for i in allslots(ctx) if getattr(getattr(ctx, i), 'offset', -1) >= 0 and getattr(getattr(ctx, i), 'lineno') == self.lineno), default=self.node.offset) + maxoffsetlength, maxoffset = max(((getattr(ctx, i).length, getattr(ctx, i).offset) for i in allslots(ctx) if getattr(getattr(ctx, i), 'offset', -1) >= 0 and getattr(getattr(ctx, i), 'lineno') == self.lineno), default=(self.node.length, self.node.offset)) + + loff = min((lstripcount(i)[0] for i in self.srclines[minlineno-1:maxlineno]), default=0) + srclines = tuple(i[loff:] for i in self.srclines) + line, srclines = line[loff:].expandtabs(TAB_SIZE), tuple(i.expandtabs(TAB_SIZE) for i in srclines) + + loff = lstripcount(line)[0] + + return (f'\033[2m(in {self.scope})\033[0m ' if (self.scope is not None) else '')+\ + f"{self.__exline__()} {self.at}"+(':\n'+\ + '\033[1m'+(' '+'\n '.join(srclines[minlineno-1:self.lineno-1])+'\n' if (minlineno < self.lineno) else '')+\ + ' '+line[:minoffset-l]+'\033[91m'*(self.node.offset >= 0)+line[minoffset-l:maxoffset+maxoffsetlength]+'\033[0m'+line[maxoffset+maxoffsetlength:]+'\033[0m\n'+\ + '\033[95m'+' '*(2+loff+minoffset-l)+'~'*(self.node.offset-minoffset)+'^'+'~'*(maxoffset+maxoffsetlength-(2+loff+minoffset-l)-(self.node.offset-minoffset)+1)+\ + ('\n\033[0;91m '+'\n '.join(srclines[self.lineno:maxlineno]) if (maxlineno > self.lineno and len(srclines) > self.lineno) else '')+\ + '\033[0m' if (srclines) else '')+\ + self.__exsubline__()+\ + (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__ if (isinstance(self.__cause__, (SlSyntaxException, SlNodeException))) else ' '+str().join(traceback.format_exception(type(self.__cause__), self.__cause__, self.__cause__.__traceback__))}" if (self.__cause__ is not None) else '') + + @abc.abstractmethod + def __exline__(self): + return '' + + def __exsubline__(self): + return '' @property def at(self): - return f" at line {self.node.lineno}, offset {self.node.offset}" + return f"at line {self.node.lineno}, offset {self.node.offset}" @property def lineno(self): return self.node.lineno +class SlValidationException(SlNodeException): pass + +class SlValidationError(SlValidationException): + desc: ... + + def __init__(self, desc, *args, **kwargs): + super().__init__(*args, **kwargs) + self.desc = desc + + def __exline__(self): + return f"Validation error: {self.desc}" + class SlValidationNotDefinedError(SlValidationError): - def __init__(self, identifier, **kwargs): - super().__init__(f"'{identifier.identifier}' is not defined", identifier, **kwargs) + def __init__(self, identifier, *args, **kwargs): + super().__init__(f"`{identifier.identifier}' is not defined", identifier, *args, **kwargs) class SlValidationRedefinedError(SlValidationError): - def __init__(self, identifier, definition, **kwargs): - super().__init__(f"'{identifier}' redefined (defined as '{definition}')", identifier, **kwargs)# at lineno {definition.lineno} + def __init__(self, identifier, definition, *args, **kwargs): + super().__init__(f"`{identifier}' redefined (defined as `{definition}')", identifier, *args, **kwargs)# at lineno {definition.lineno} -def optimize_ast(ast, ns): return ast.code.optimize(ns) +def optimize_ast(ast, ns, level=DEFAULT_OLEVEL): return ast.optimize(ns) -# by Sdore, 2020 +# by Sdore, 2021 diff --git a/compilers/__init__.py b/compilers/__init__.py index e563661..27c887f 100644 --- a/compilers/__init__.py +++ b/compilers/__init__.py @@ -1,40 +1,24 @@ #!/usr/bin/python3 # Slang compilers -from ..ast import SlSyntaxError, SlValidationError +from ..ast import Slots, lstripcount, SlSyntaxError, SlNodeException, SlValidationError import abc, traceback class Compiler(abc.ABC): + ext = '' + @abc.abstractclassmethod def compile_ast(cls, ast): pass -def lstripcount(s, chars): # TODO: commonize - for ii, i in enumerate(s): - if (i not in chars): break - else: ii = 0 - return (ii, s[ii:]) +class SlCompilationError(SlNodeException): + desc: ... -class SlCompilationError(Exception): - __slots__ = ('desc', 'node', 'line', 'scope') + def __init__(self, desc, *args, **kwargs): + super().__init__(*args, **kwargs) + self.desc = desc - def __init__(self, desc, node, line='', *, scope=None): - self.desc, self.node, self.line, self.scope = desc, node, line, scope + def __exline__(self): + return f"Compilation error: {self.desc}" - def __str__(self): - l, line = lstripcount(self.line.partition('\n')[0].replace('\t', ' '), ' \t') - offset = (self.node.offset-l) if (self.node.offset != -1) else len(line) - return (f'\033[2m(in {self.scope})\033[0m ' if (self.scope is not None) else '') + f"Compilation error: {self.desc}{self.at}"+(':\n' + \ - ' \033[1m'+line[:offset]+'\033[91m'+line[offset:]+'\033[0m\n' + \ - ' '+' '*offset+'\033[95m^'+'~'*(self.node.length-1)+'\033[0m' if (line) else '') + \ - (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__ if (isinstance(self.__cause__, (SlSyntaxError, SlValidationError, SlCompilationError))) else ' '+str().join(traceback.format_exception(type(self.__cause__), self.__cause__, self.__cause__.__traceback__))}" if (self.__cause__ is not None) else '') - - @property - def at(self): - return f" at line {self.node.lineno}, offset {self.node.offset}" - - @property - def lineno(self): - return self.node.lineno - -# by Sdore, 2019 +# by Sdore, 2021 diff --git a/compilers/esolang/gibberish.py b/compilers/esolang/gibberish.py index 0256dbd..814f8d9 100644 --- a/compilers/esolang/gibberish.py +++ b/compilers/esolang/gibberish.py @@ -3,7 +3,7 @@ # https://esolangs.org/wiki/Gibberish_(programming_language) from .. import * -from Slang.ast import * +from ...ast import * from utils import * class Instrs: @@ -132,4 +132,6 @@ class GibberishCompiler(Compiler): dlog("Code:\n"+code.decode()) return code -# by Sdore, 2019 +compiler = GibberishCompiler + +# by Sdore, 2020 diff --git a/compilers/native/__init__.py b/compilers/native/__init__.py new file mode 100644 index 0000000..171db82 --- /dev/null +++ b/compilers/native/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 +# Slang Native (ASM) compiler target + +from .. import * +from ...ast import * +from utils import * + +class NativeCompiler(Compiler): pass + +try: compiler = importlib.import_module(f".{platform.machine()}", __package__).compiler +except ModuleNotFoundError: compiler = NativeCompiler + +# by Sdore, 2021 diff --git a/compilers/native/x86_64.py b/compilers/native/x86_64.py new file mode 100644 index 0000000..a08e299 --- /dev/null +++ b/compilers/native/x86_64.py @@ -0,0 +1,235 @@ +#!/usr/bin/python3 +# Slang Native (ASM) x86_64 compiler target + +from . import * + +word_size = 8 +registers = ('rax', 'rcx', 'rdx', 'rbx', 'rsi', 'rdi', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15') +regs64 = {i: () for i in registers} +regs32 = {'e'+i: ('r'+i, *regs64['r'+i]) for i in ('ax', 'cx', 'dx', 'bx', 'si', 'di')} +regs16 = {i: ('e'+i, *regs32['e'+i]) for i in ('ax', 'cx', 'dx', 'bx', 'si', 'di')} +regs8 = {i+j: (i+'x', *regs16[i+'x']) for i in 'acdb' for j in 'lh'} + +class Instrs: + binopmap = { + '+': 'add', + } + stacksize = 0x10 # TODO + + @init_defaults + def __init__(self, *, name, ns, filename, nargs: int, freeregs: set): + self.name, self.ns, self.filename, self.nargs, self.freeregs = name, ns, filename, nargs, freeregs + self.instrs = list() + self.varoff = bidict.OrderedBidict() + self.varsize = dict() + self.regpool = list(registers) + self.clobbered = set() + + def compile(self): + saveregs = tuple(sorted(self.clobbered - self.freeregs)) + instrs = [ + "push rbp", + "mov rbp, rsp", + *(f"push {i}" for i in saveregs), + f"sub rsp, {self.stacksize}", + '\n', + *self.instrs, + '\n', + *(f"pop {i}" for i in saveregs[::-1]), + "leave", + f"ret {self.nargs}" if (self.nargs > 0) else "ret", + ] + return f"global {self.name}\n{self.name}:\n\t"+'\n\t'.join('\t'.join(i.split(maxsplit=1)) for i in instrs)+'\n' + + def var(self, name): + if (self.varoff[name] == 0): return '[rbp]' + return "[rbp%+d]" % -self.varoff[name] + + @contextlib.contextmanager + def vars(self, *names): + yield tuple(map(self.var, names)) + + @contextlib.contextmanager + def regs(self, n): + regs = {self.regpool.pop(0) for _ in range(n)} + self.clobbered |= regs + try: yield regs + finally: self.regpool = [i for i in registers if i in {*self.regpool, *regs}] + + #def syscall(self, rax, rdi=None, rsi=None, rdx=None, r10=None, r8=None, r9=None): + # self.instrs.append(" + + @dispatch + def add(self, x: ASTRootNode): + self.add(x.code) + + @dispatch + def add(self, x: ASTCodeNode): + for i in x.nodes: + self.add(i) + + @dispatch + def add(self, x: ASTVardefNode): + name = x.name.identifier + try: + ln, lo = last(self.varoff.items()) + self.varoff[name] = lo+self.varsize[ln] + except StopIteration: self.varoff[name] = word_size + self.varsize[name] = self.sizeof(x.type) + if (x.value is not None): self.set(x.name, x.value) + + @dispatch + def add(self, x: ASTFunccallNode): + callarguments = CallArguments.build(x, self.ns) + fsig = Signature.build(x.callable, self.ns) + fcall = fsig.compatible_call(callarguments, self.ns) + if (fcall is None): raise TODO(fcall) + + n = int() + if (fcall[0] is not None): fname = f"{fsig.name}({CallArguments(args=fcall[0], ns=self.ns)})" + else: + if (isinstance(x.callable.value, ASTAttrgetNode)): + ofsig = Signature.build(x.callable.value.value, self.ns) + if (isinstance(fsig, stdlib.Builtin)): + fname = x.callable + else: raise NotImplementedError(ofsig) + else: raise NotImplementedError(x.callable.value) + + for i in x.callargs.callargs: + self.push(i) + n += 1 + + if (x.callargs.starargs or x.callkwargs.callkwargs or x.callkwargs.starkwargs): raise NotImplementedError() + + self.instrs.append(f"call {fname}") + + @dispatch + def load(self, x: ASTValueNode, *reg): + return self.load(x.value, *reg) + + @dispatch + def load(self, x: ASTLiteralNode): + return x.literal + + @dispatch + def load(self, x): + with self.regs(1) as (reg,): + self.load(x, reg) + return reg + #reg = self.regpool.pop(0) + #self.clobbered.add(reg) + #self.load(x, reg) + #return reg + + @dispatch + def load(self, x: ASTIdentifierNode, reg): + self.instrs.append(f"mov {reg}, {self.var(x.identifier)}") + + @dispatch + def load(self, x: ASTBinaryExprNode, reg): + self.load(x.lvalue, reg) + self.instrs.append(f"{self.binopmap[x.operator.operator]} {reg}, {self.load(x.rvalue)}") + + @dispatch + def load(self, x: str, reg): + self.instrs.append(f"mov {reg}, {x}") + + @dispatch + def set(self, name: ASTIdentifierNode, value): + self.set(name.identifier, value) + + @dispatch + def set(self, name: str, value: ASTValueNode): + self.set(name, value.value) + + @dispatch + def set(self, name: str, value: ASTLiteralNode): + self.set(name, self.load(value)) + + @dispatch + def set(self, name: str, value): + with self.vars(name) as (var,): + self.instrs.append(f"mov {var}, {self.load(value)}") + + def push(self, x): + self.instrs.append(f"push {self.load(x)}") + + @dispatch + def sizeof(self, x: ASTTypedefNode): + return self.sizeof(Signature.build(x, self.ns)) + + @dispatch + def sizeof(self, x: lambda x: hasattr(x, 'fmt')): + return struct.calcsize(x.fmt) + +class BuiltinInstrs(Instrs): + @init_defaults + def __init__(self, *, freeregs: set): + self.freeregs = freeregs + +@singleton +class builtin_stdio_println(BuiltinInstrs): + name = 'stdio.println' + instrs = [ + "mov al, [rbp+0x10]", + "add al, '0'", + "mov [rbp-2], al", + "mov al, 10", + "mov [rbp-1], al", + + "mov rax, 1 ; sys_write", + "mov rdi, 1 ; stdout", + "lea rsi, [rbp-2] ; buf", + "mov rdx, 2 ; count", + "syscall", + ] + nargs = 1 + stacksize = 2 + clobbered = {'rax', 'rcx', 'rdx', 'rdi', 'rsi', 'r11'} + +class x86_64Compiler(NativeCompiler): + ext = '.o' + + header = """ + section .text + + global _start + _start: + call main + mov rax, 60 ; sys_exit + mov rdi, 0 ; error_code + syscall + hlt + """ + + @classmethod + def compile_ast(cls, ast, ns, *, filename): + instrs = Instrs(name='main', ns=ns, filename=filename) + instrs.add(ast) + src = f"# {filename}\n{S(cls.header).unindent().strip()}\n" + src += '\n'+builtin_stdio_println.compile() + src += '\n'+instrs.compile() + + sname = os.path.splitext(filename)[0]+'.s' + sfd = open(sname, 'w') + try: + sfd.write(src) + sfd.close() + log("Src:\n"+src) + + ofd, oname = tempfile.mkstemp(prefix=f"slc-{os.path.splitext(filename)[0]}-", suffix='.o') + try: + subprocess.run(('yasm', '-felf64', '-o', oname, sname), stderr=subprocess.PIPE, text=True, check=True) + return os.fdopen(ofd, 'rb').read() + except subprocess.CalledProcessError as ex: raise SlCompilationError('\n'+S(ex.stderr).indent(), ast, scope=instrs.ns) from ex + finally: + try: os.remove(oname) + except OSError: pass + finally: + sfd.close() + try: os.remove(sname) + except OSError: pass + +compiler = x86_64Compiler + +# by Sdore, 2021 diff --git a/compilers/pyssembly/.gitignore b/compilers/pyssembly/.gitignore new file mode 100644 index 0000000..925e0ed --- /dev/null +++ b/compilers/pyssembly/.gitignore @@ -0,0 +1 @@ +stdlib/ \ No newline at end of file diff --git a/compilers/pyssembly/__init__.py b/compilers/pyssembly/__init__.py index f0447ef..be8c2a8 100644 --- a/compilers/pyssembly/__init__.py +++ b/compilers/pyssembly/__init__.py @@ -38,7 +38,7 @@ class Instrs: def _class_init(self, *args, **kwargs): getattr(self, '')() getattr(self, f"")(*args, **kwargs) - _class_init = _class_init.__code__.replace(co_name='') + _class_init = code_with(_class_init.__code__, co_name='') @init_defaults def __init__(self, *, name, ns, filename, argdefs=(), lastnodens: lambda: [None, None], firstlineno=0): @@ -52,7 +52,7 @@ class Instrs: self.cellvars = self.argnames.copy() self.srclnotab = list() self.lastln = self.firstlineno - self._class_init = self._class_init.replace(co_filename=self.filename, co_consts=tuple(i.replace(co_filename=self.filename) if (isinstance(i, CodeType)) else i for i in self._class_init.co_consts)) + self._class_init = code_with(self._class_init, co_filename=self.filename, co_consts=tuple(code_with(i, co_filename=self.filename) if (isinstance(i, CodeType)) else i for i in self._class_init.co_consts)) def compile(self): return pyssembly.Code('\n'.join(self.instrs), name=self.name, filename=self.filename.strip('"'), srclnotab=self.srclnotab, firstlineno=self.firstlineno, consts=self.consts, argnames=self.argnames) @@ -146,7 +146,7 @@ class Instrs: @dispatch def add(self, x: ASTFuncdefNode): code_ns = self.ns.derive(x.name.identifier) - name = f"{x.name.identifier}({CallArguments(args=tuple(Signature.build(i, code_ns) for i in x.argdefs))})" + name = f"{x.name.identifier}({CallArguments(args=x.argdefs, ns=code_ns)})" self.lastnodens[1] = code_ns fname = f"{self.name}.<{x.__fsig__()}>" f_instrs = Instrs(name=fname, ns=code_ns, filename=self.filename, argdefs=x.argdefs, lastnodens=self.lastnodens, firstlineno=x.lineno) @@ -214,8 +214,9 @@ class Instrs: src = open(filename, 'r').read() tl = parse_string(src) ast = build_ast(tl, filename) - optimize_ast(ast, validate_ast(ast)) - instrs = Instrs(name='', ns=validate_ast(ast), filename=filename) + if (x.flags.optimized): optimize_ast(ast, validate_ast(ast)) + ns = validate_ast(ast) + instrs = Instrs(name=filename, ns=ns, filename=filename) instrs.add(ast) code = instrs.compile().to_code() # TODO @@ -359,36 +360,54 @@ class Instrs: @dispatch def load(self, x: ASTFunccallNode): - if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures and not isinstance(self.ns.signatures[x.callable.value.identifier], Class)): - self.load(f"{x.callable.value.identifier}({CallArguments.build(x, self.ns)})") - else: self.load(x.callable) + #if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures and not isinstance(self.ns.signatures[x.callable.value.identifier], Class)): + callarguments = CallArguments.build(x, self.ns) + fsig = Signature.build(x.callable, self.ns) + fcall = fsig.compatible_call(callarguments, self.ns) + if (fcall is None): raise TODO(fcall) n = int() + if (fcall[0] is not None): self.load(f"{fsig.name}({CallArguments(args=fcall[0], ns=self.ns)})") + else: + if (isinstance(x.callable.value, ASTAttrgetNode)): + ofsig = Signature.build(x.callable.value.value, self.ns) + if (type(ofsig) is Function): + ofcall = ofsig.compatible_call(callarguments, self.ns) + assert (ofcall is not None) + self.load(fsig.name) + self.load(f"{ofsig.name}({CallArguments(args=ofcall[0], ns=self.ns)})") + n += 1 + elif (type(ofsig) is stdlib.list): + f = ofsig.attrops[x.callable.value.optype.special, x.callable.value.attr.identifier] + self.load(f.name) + self.load(x.callable.value.value) + n += 1 + elif (isinstance(fsig, stdlib.Builtin)): + self.load(x.callable) + else: raise NotImplementedError(ofsig) + else: raise NotImplementedError(x.callable.value) for i in x.callargs.callargs: self.load(i) n += 1 if (x.callargs.starargs): - if (n): - self.instrs.append(f"BUILD_TUPLE {n}") - n = 1 + if (n or len(x.callargs.starargs) > 1): self.instrs.append(f"BUILD_LIST {n}") for i in x.callargs.starargs: self.load(i) - n += 1 - self.instrs.append(f"BUILD_TUPLE_UNPACK_WITH_CALL {n}") + if (n or len(x.callargs.starargs) > 1): self.instrs.append("LIST_EXTEND 1") + if (n or len(x.callargs.starargs) > 1): self.instrs.append("LIST_TO_TUPLE") + n = 0 + elif (x.callkwargs.starkwargs): + self.instrs.append("BUILD_TUPLE {n}") n = 0 - for i in x.callkwargs.callkwargs: self.load(f"'{i[0]}'") self.load(i[1]) n += 1 - if (n and (x.callargs.starargs or x.callkwargs.starkwargs)): - self.instrs.append(f"BUILD_MAP {n}") - n = 1 if (x.callkwargs.starkwargs): + self.instrs.append(f"BUILD_MAP {n}") for i in x.callkwargs.starkwargs: self.load(i) - n += 1 - self.instrs.append(f"BUILD_MAP_UNPACK_WITH_CALL {n}") + self.instrs.append("DICT_MERGE 1") n = 1 self.instrs.append(f"CALL{'EX' if (x.callargs.starargs or x.callkwargs.starkwargs) else 'KW' if (x.callkwargs.callkwargs) else ''} {n}") @@ -453,18 +472,18 @@ class PyssemblyCompiler(Compiler): @classmethod def compile_ast(cls, ast, ns, *, filename): - instrs = Instrs(name='', ns=ns, filename=filename) + instrs = Instrs(name=filename, ns=ns, filename=filename) - instrs.consts.append(compile(open(std.__file__).read(), '', 'exec')) + instrs.consts.append(code_with(compile(open(std.__file__).read(), '', 'exec'), name='stdlib')) instrs.instrs += [ f"LOAD {len(instrs.consts)-1}", - "LOAD ('')", + "LOAD ('')", "MKFUNC 0", # TODO? "CALL", ] try: instrs.add(ast) - except Exception as ex: raise SlCompilationError('Compilation error', instrs.lastnodens[0], scope=instrs.lastnodens[1].scope) from ex + except Exception as ex: raise SlCompilationError('Compilation error', instrs.lastnodens[0], scope=instrs.lastnodens[1].scope if (instrs.lastnodens[1] is not None) else '') from ex #dlog("Instrs:\n"+'\n'.join(instrs.instrs)+'\n') @@ -483,4 +502,6 @@ class PyssemblyCompiler(Compiler): return code +compiler = PyssemblyCompiler + # by Sdore, 2020 diff --git a/compilers/pyssembly/genstdlib.py b/compilers/pyssembly/genstdlib.py new file mode 100755 index 0000000..289cc2e --- /dev/null +++ b/compilers/pyssembly/genstdlib.py @@ -0,0 +1,181 @@ +#!/usr/bin/python3 + +from utils.nolog import * + +builtin_types = {i for i in itertools.chain(builtins.__dict__.values(), types.__dict__.values()) if isinstance(i, type)} + +@dispatch +def gen(x, name: NoneType, *, scope: dict): + return gen(x, '', scope=scope).rstrip(' ') + +@dispatch +def gen(x: lambda x: x is NoneType, name: str, *, scope: dict): + return f"void {name}" + +@dispatch +def gen(x: lambda x: isinstance(x, type) and (getattr(x, '__module__', None) == '__main__' or x in builtin_types), name: str, *, scope: dict): + return f"{x.__name__} {name}" + +@dispatch +def gen(x: lambda x: typing_inspect.get_origin(x) is not None, name: str, *, scope: dict): + o = typing_inspect.get_origin(x) + if (o is typing.Literal): + return gen(type(x.__args__[0]), name, scope=scope) + elif (o is typing.Union): + if (x.__args__[1] is NoneType): return f"{gen(x.__args__[0], name, scope=scope).rstrip()}?" + else: return f"{'|'.join(gen(i, None, scope=scope) for i in x.__args__)} {name}" + elif (o in (tuple, list, dict, set, frozenset)): + return f"{o.__name__}{', '.join(gen(i, None, scope=scope) for i in x.__args__ if i is not ...).join('[]') if (x.__args__ and x.__args__ != (typing.Any,) and x.__args__ != (typing.Any, ...)) else ''} {name}" # TODO args + + # TODO FIXME: + elif (o is collections.abc.Iterable): + return f"iterable {name}" + elif (o is collections.abc.Iterator): + return f"iterator {name}" + elif (o is collections.abc.Sequence): + return f"sequence {name}" + elif (o is collections.abc.Mapping): + return f"mapping[{gen(x.__args__[0], None, scope=scope)}, {gen(x.__args__[1], None, scope=scope)}] {name}" + elif (o is collections.abc.ItemsView): + return f"items {name}" + elif (o is collections.abc.KeysView): + return f"keys {name}" + elif (o is collections.abc.ValuesView): + return f"values {name}" + elif (o is collections.abc.Set): + return f"set {name}" + elif (o is collections.abc.Callable): + return f"callable {name}" + + elif (o is type): + return f"{x.__args__[0].__name__} {name}" + else: raise NotImplementedError(x, o) + +@dispatch +def gen(x: lambda x: x is typing.Any, name: str, *, scope: dict): # TODO FIXME + return f"object {name}" + +@dispatch +def gen(x: typing.TypeVar, name: str, *, scope: dict): + return f"{x.__name__} {name}" + +@dispatch +def gen(x: lambda x: isinstance(x, type) and issubclass(x, typing.Protocol), name: str, *, scope: dict): + if (x is typing.SupportsInt): + t = int + elif (x is typing.SupportsFloat): + t = float + else: raise NotImplementedError(x) + return gen(t, name, scope=scope) + +@dispatch +def gen(x: function, name: str, *, scope: dict): + fsig = inspect.signature(x) + return f"{gen(typing.get_type_hints(x, scope)['return'], None, scope=scope)} {name}({', '.join(gen(v, x, k, scope=scope) for ii, (k, v) in enumerate(fsig.parameters.items()) if not (ii == 0 and k == 'self'))})" + +@dispatch +def gen(x: inspect.Parameter, f, name: str, *, scope: dict): + t = gen(typing.get_type_hints(f, scope)[x.name], name, scope=scope) + if (x.default is not inspect._empty): + if (x.default is ...): + if (t[-1:] != '?'): t += '?' + else: raise NotImplementedError(x.default) + return t + +@dispatch +def gen(x: property, name: str, *, scope: dict): + return gen(typing.get_type_hints(x.fget, scope)['return'], name, scope=scope) + +@dispatch +def gen(x: lambda x: isinstance(x, type) and x.__module__ == '__main__', name: str, *, scope: dict): + return f"{x.__name__} {name}" + +#@dispatch +#def gen(x: type, name: str, *, scope: dict): +# return f"{type.__name__} {name}" + +@dispatch +def gen(x: lambda x: isinstance(x, type) and x.__module__ == '__main__', *, scope: dict): + r = [] + + for k, v in typing.get_type_hints(x, scope).items(): + r.append(f"{gen(v, k, scope=scope)};".lstrip(';')) + + for k, v in x.__dict__.items(): + if (k in ('__module__', '__doc__')): continue + if (not isinstance(v, property) and (not isinstance(v, function) or getattr(v, '__module__', None) != '__main__')): continue + r.append(f"{gen(v, k, scope=scope)};".lstrip(';')) + + if (not r): return '' + return f"class {x.__name__} {{\n\t"+'\n\t'.join(r)+'\n}' + +@dispatch +def gen(x: CodeType, *, package): + r = {'__name__': '__main__', '__package__': package} + exec(x, r) + return '\n\n'.join(Slist(gen(i, scope=r) for i in r.values() if isinstance(i, type) and i.__module__ == '__main__').strip('')) + +class AnnotationsFileLoader(importlib.machinery.SourceFileLoader): + header = "from __future__ import annotations" + + def get_data(self, path): + dlog(path) + data = super().get_data(path) + if (not path.endswith('.pyi')): return data + return (self.header+'\n\n').encode() + data + +def path_hook_factory(f): + def path_hook(path): + finder = f(path) + finder._loaders.insert(0, ('.pyi', AnnotationsFileLoader)) + return path_hook + +@apmain +@aparg('typeshed', metavar='') +@aparg('output', metavar='') +def main(cargs): + if (sys.version_info < (3,8)): raise NotImplementedError("Currently only Python 3.8+ is supported.") + + dirs = ('stdlib', 'stubs') + + import __future__ + + sys.dont_write_bytecode = True + sys.path = [__future__.__file__] + [os.path.join(cargs.typeshed, i) for i in dirs] + [os.path.join(cargs.typeshed, 'stubs', i, j) for i in os.listdir(os.path.join(cargs.typeshed, 'stubs')) for j in os.listdir(os.path.join(cargs.typeshed, 'stubs', i))] + #sys.path_hooks[-1] = path_hook_factory(sys.path_hooks[-1]) + #sys.path_hooks[-1] = importlib.machinery.FileFinder.path_hook((AnnotationsFileLoader, ['.pyi']+[i for i in importlib.machinery.all_suffixes() if i != '.pyc'])) + sys.path_hooks = [importlib.machinery.FileFinder.path_hook((AnnotationsFileLoader, ['.pyi']))] + sys.meta_path = [sys.meta_path[2]] + + skipped = int() + for d in dirs: + for v in sorted(os.listdir(os.path.join(cargs.typeshed, d))): + if (v in ('@python2', '_typeshed')): continue + for p, _, n in os.walk(os.path.join(cargs.typeshed, d, v)): + if ('@python2' in p): continue + o = os.path.join(cargs.output, p.partition(os.path.join(cargs.typeshed, d, v, ''))[2]) + os.makedirs(o, exist_ok=True) + for i in sorted(n, key=lambda x: 'builtins.pyi' not in x): + if (not i.endswith('.pyi')): continue + filename = os.path.join(p, i) + log(filename) + code = compile(b"from __future__ import annotations\n\n"+open(filename, 'rb').read(), filename, 'exec') + try: r = gen(code, package=os.path.splitext(filename)[0].replace('/', '.')) + except Exception as ex: + if ('builtins.pyi' in i): raise + logexception(ex) + skipped += 1 + raise # XXX + else: + if (not r): continue + if ('builtins.pyi' in i): r = 'import sld:std:*;\n\n'+r + else: r = 'import py:builtins:*;\n\n'+r + open(os.path.join(o, os.path.splitext(i)[0]+'.sld'), 'w').write(r+'\n') + + if (skipped): print(f"\033[93m{skipped} errors caught ({skipped} files skipped).\033[0m") + print("\033[1mSuccess!\033[0m") + +if (__name__ == '__main__'): exit(main(nolog=True)) +else: logimported() + +# by Sdore, 2021 diff --git a/compilers/pyssembly/requirements.txt b/compilers/pyssembly/requirements.txt new file mode 100644 index 0000000..e1861a6 --- /dev/null +++ b/compilers/pyssembly/requirements.txt @@ -0,0 +1 @@ +typing_inspect \ No newline at end of file diff --git a/compilers/pyssembly/std.py b/compilers/pyssembly/std.py index b666fdc..0048d69 100644 --- a/compilers/pyssembly/std.py +++ b/compilers/pyssembly/std.py @@ -1,2 +1,5 @@ class stdio: println = print + +def _map(f, l): return list(map(f, l)) +def _each(l, f): return list(map(f, l)) diff --git a/compilers/pyssembly/typeshed b/compilers/pyssembly/typeshed index b44cd29..e2fd852 160000 --- a/compilers/pyssembly/typeshed +++ b/compilers/pyssembly/typeshed @@ -1 +1 @@ -Subproject commit b44cd294c4f6cdb66fdd6c13aebabb10855e7dc2 +Subproject commit e2fd852952ff807c60819f8840e8b23664cc7593 diff --git a/compilers/sbc.py b/compilers/sbc.py deleted file mode 100644 index ba341eb..0000000 --- a/compilers/sbc.py +++ /dev/null @@ -1,351 +0,0 @@ -#!/usr/bin/python3 -# Slang Bytecode (SBC) compiler target - -from . import * -from Slang.ast import * -from utils import * - -NOP = 0x00 -END = 0x01 -POP = 0x02 -RET = 0x03 -BLTIN = 0x04 -CODE = 0x05 - -POS = 0x10 -NEG = 0x11 -NOT = 0x12 -INV = 0x13 -ATOI = 0x14 -ITOA = 0x15 -ITOF = 0x16 -CEIL = 0x17 -FLR = 0x18 -RND = 0x19 -CTOS = 0x1a - -ADD = 0x20 -SUB = 0x21 -MUL = 0x22 -DIV = 0x23 -IDIV = 0x24 -MOD = 0x25 -POW = 0x26 -LSH = 0x27 -RSH = 0x28 -AND = 0x29 -OR = 0x2a -XOR = 0x2b - -EQ = 0x30 -NE = 0x31 -LT = 0x32 -GT = 0x33 -LE = 0x34 -GE = 0x35 -IS = 0x36 -ISNOT = 0x37 - -IF = 0x40 -ELSE = 0x41 -EXEC = 0x42 - -ALLOC = 0xa0 -EXTEND = 0xa1 -CONST = 0xa2 -JUMPF = 0xa3 -JUMPB = 0xa4 -SCPGET = 0xa5 -SCPSET = 0xa6 -CALL = 0xa7 - -HASARG = 0xa0 - -def readVarInt(s): - r = int() - i = int() - while (True): - b = s.recv(1)[0] - r |= (b & (1 << 7)-1) << (7*i) - if (not b & (1 << 7)): break - i += 1 - return r - -def writeVarInt(v): - assert v >= 0 - r = bytearray() - while (True): - c = v & (1 << 7)-1 - v >>= 7 - if (v): c |= (1 << 7) - r.append(c) - if (not v): break - return bytes(r) - -class Instrs: - unops = '+-!~' - binops = (*'+-*/%', '**', '<<', '>>', '&', '|', '^') - unopmap = { - '+': POS, - '-': NEG, - '!': NOT, - '~': INV, - 'not': NOT, - } - binopmap = { - '+': ADD, - '-': SUB, - '*': MUL, - '/': DIV, - '//': IDIV, - '%': MOD, - '**': POW, - '<<': LSH, - '>>': RSH, - '&': AND, - '|': OR, - '^': XOR, - - '==': EQ, - '!=': NE, - '<': LT, - '>': GT, - '<=': LE, - '>=': GE, - 'is': IS, - 'is not': ISNOT, - } - - @init_defaults - def __init__(self, *, name, ns, filename, scpcells: indexset): - self.name, self.ns, self.filename, self.scpcells = name, ns, filename, scpcells - self.instrs = bytearray() - - def compile(self): - return bytes(self.instrs) - - @dispatch - def add(self, opcode: lambda x: isinstance(x, int) and x < HASARG): - self.instrs.append(opcode) - - @dispatch - def add(self, opcode: lambda x: isinstance(x, int) and x >= HASARG, oparg: int): - self.instrs.append(opcode) - self.instrs.append(oparg) - - @dispatch - def add(self, x: ASTRootNode): - self.add(x.code) - - @dispatch - def add(self, x: ASTCodeNode): - for i in x.nodes: - self.add(i) - - @dispatch - def add(self, x: ASTValueNode): - self.add(x.value) - - @dispatch - def add(self, x: ASTVardefNode): - if (x.value is not None): - self.load(x.value) - self.store(x.name) - - @dispatch - def add(self, x: ASTAssignmentNode): - if (x.inplace_operator is not None): raise NotImplementedError() - self.load(x.value) - self.store(x.name) - - @dispatch - def add(self, x: ASTFunccallNode): - self.load(x) - self.add(POP) - - @dispatch - def add(self, x: ASTBlockNode): - self.add(x.code) - - @dispatch - def add(self, x: ASTFuncdefNode): - code_ns = self.ns.derive(x.name.identifier) - name = f"{x.name.identifier}__{self.ns.signatures[x.name.identifier].call.index(CallArguments(args=tuple(Signature.build(i, code_ns) for i in x.argdefs)))}" - fname = f"{self.name}.<{x.__fsig__()}>" - self.scpcells[name] - f_instrs = Instrs(name=fname, ns=code_ns, filename=self.filename, scpcells=self.scpcells.copy()) - for i in x.argdefs: - f_instrs.scpcells[i.name.identifier] - f_instrs.add(x.code) - self.add(CODE) - self.instrs += f_instrs.instrs - self.add(END) - self.store(name+'.len') - self.store(name) - - @dispatch - def add(self, x: ASTKeywordExprNode): - if (x.keyword.keyword == 'import'): - ns, _, name = x.value.identifier.partition('::') - raise TODO - self.store(name) - elif (x.keyword.keyword == 'return'): - self.load(x.value) - self.add(RET) - else: raise NotImplementedError(x.keyword) - - @dispatch - def add(self, x: ASTKeywordDefNode): - if (x.keyword.keyword == 'main'): - name = '
' - code_ns = self.ns.derive(name) - f_instrs = Instrs(name=name, ns=code_ns, filename=self.filename) - f_instrs.add(x.code) - self.add(CODE) - self.instrs += f_instrs.instrs - self.add(END) - self.add(EXEC) - self.add(POP) - else: raise NotImplementedError(x.keyword) - - @dispatch - def add(self, x: ASTConditionalNode): - self.load(x.condition) - self.add(IF) - self.add(x.code) - self.add(END) - - #@dispatch - def add_(self, x: ASTForLoopNode): - self.load(x.iterable) - #self.cellvars.append(x.name.identifier) # TODO FIXME - self.instrs += [ - "SETUP_LOOP :end", - "ITER", - ":for", - "FOR :else", - ] - self.store(x.name) - self.add(x.code) - self.instrs += [ - "JUMPA :for", - ":else", - "POP_BLOCK", - ":end", - ] - - #@dispatch - def add_(self, x: ASTWhileLoopNode): - self.instrs += [ - "SETUP_LOOP :end", - ":while", - ] - self.load(x.condition) - self.instrs.append("JPOPF :else") - self.add(x.code) - self.instrs += [ - "JUMPA :while", - ":else", - "POP_BLOCK", - ":end", - ] - - #@dispatch - def add_(self, x: ASTElseClauseNode): - assert (self.instrs[-1] == ":end") - popped = [self.instrs.pop()] - if (self.instrs[-1] == "POP_BLOCK"): popped.append(self.instrs.pop()) - self.add(x.code) - self.instrs += reversed(popped) - - @dispatch - def load(self, x: ASTLiteralNode): - sig = Signature.build(x, ns=self.ns) - if (hasattr(sig, 'fmt')): - v = struct.pack(sig.fmt, int(x.literal)) - elif (isinstance(sig, stdlib.int)): - v = writeVarInt(int(x.literal)) - elif (isinstance(sig, stdlib.str)): - v = x.literal.encode('utf8') - else: raise NotImplementedError(sig) - self.add(CONST, len(v)) - self.instrs += v - - @dispatch - def load(self, x: ASTIdentifierNode): - self.load(x.identifier) - - @dispatch - def load(self, x: ASTValueNode): - self.load(x.value) - - @dispatch - def load(self, x: ASTFunccallNode): - nargs = int() - - for i in x.callargs.callargs: - self.load(i) - nargs += 1 - if (x.callargs.starargs): raise NotImplementedError() - - if (x.callkwargs.callkwargs): raise NotImplementedError() - if (x.callkwargs.starkwargs): raise NotImplementedError() - - if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures): - name = f"{x.callable.value.identifier}__{self.ns.signatures[x.callable.value.identifier].call.index(CallArguments.build(x, self.ns))}" - self.load(name) - self.load(name+'.len') - self.add(EXEC) - else: # builtin - #self.add(ITOA) # TODO FIXME cast - self.add(BLTIN) - self.instrs += x.callable.value.identifier.encode('ascii')+b'\0' - self.add(CALL, nargs) - - #@dispatch - def load_(self, x: ASTAttrgetNode): - self.load(x.value) - assert x.optype.special == '.' # TODO - self.instrs.append(f"GETATTR ({x.attr})") - - @dispatch - def load(self, x: ASTUnaryExprNode): - self.load(x.value) - self.add(self.unopmap[x.operator.operator]) - - @dispatch - def load(self, x: ASTBinaryExprNode): - self.load(x.lvalue) - self.load(x.rvalue) - if (x.operator.operator == 'to'): raise NotImplementedError() - else: self.add(self.binopmap[x.operator.operator]) - - #@dispatch - def load_(self, x: ASTItemgetNode): - self.load(x.value) - self.load(x.key) - self.instrs.append("SUBSCR") - - @dispatch - def load(self, x: str): - self.add(SCPGET, self.scpcells[x]) - - @dispatch - def store(self, x: ASTIdentifierNode): - self.store(x.identifier) - - @dispatch - def store(self, x: str): - self.add(SCPSET, self.scpcells[x]) - -class SBCCompiler(Compiler): - ext = '.sbc' - - @classmethod - def compile_ast(cls, ast, ns, *, filename): - instrs = Instrs(name='', ns=ns, filename=filename) - instrs.add(ast) - code = instrs.compile() - return code - -# by Sdore, 2019 diff --git a/compilers/sbc/__init__.py b/compilers/sbc/__init__.py new file mode 100644 index 0000000..0aff4bd --- /dev/null +++ b/compilers/sbc/__init__.py @@ -0,0 +1,289 @@ +#!/usr/bin/python3 +# Slang Bytecode (SBC) compiler target + +from . import * +from .. import * +from ...ast import * +from utils import * + +NOP = 0x00 +POP = 0x01 +DUP = 0x02 +RET = 0x03 +CODE = 0x04 +IF = 0x05 +LOOP = 0x06 +ELSE = 0x07 +END = 0x08 +CALL = 0x09 +ASGN = 0x0A +BLTIN = 0x0B +CONST = 0x0C +SGET = 0x0D +SSET = 0x0E + +def readVarInt(s): + r = int() + i = int() + while (True): + b = s.recv(1)[0] + r |= (b & (1 << 7)-1) << (7*i) + if (not b & (1 << 7)): break + i += 1 + return r + +def writeVarInt(v): + assert v >= 0 + r = bytearray() + while (True): + c = v & (1 << 7)-1 + v >>= 7 + if (v): c |= (1 << 7) + r.append(c) + if (not v): break + return bytes(r) + +class Instrs: + unopmap = { + '!': 'not', + '+': 'abs', + '-': 'neg', + '~': 'inv', + '++': 'inc', + '--': 'dec', + '**': 'sqr', + 'not': 'not', + } + binopmap = { + '+': 'add', + '-': 'sub', + '*': 'mul', + '/': 'div', + '//': 'idiv', + '%': 'mod', + '**': 'pow', + '<<': 'ls', + '>>': 'rs', + '&': 'and', + '^': 'xor', + '|': 'or', + + '&&': 'and', + '^^': 'xor', + '||': 'or', + '==': 'eq', + '!=': 'ne', + '<': 'lt', + '>': 'gt', + '<=': 'le', + '>=': 'ge', + + 'is': 'is', + 'is not': 'isnot', + 'in': 'in', + 'not in': 'notin', + 'isof': 'isof', + 'and': 'and', + 'but': 'and', + 'xor': 'xor', + 'or': 'or', + 'to': 'range', + } + + @init_defaults + def __init__(self, *, name, ns, filename, scpcells: indexset): + self.name, self.ns, self.filename, self.scpcells = name, ns, filename, scpcells + self.instrs = bytearray() + self.opmap = dict() + + def compile(self): + return bytes(self.instrs) + + @dispatch + def add(self, opcode: int, *args: int): + self.instrs.append(opcode) + if (args): self.instrs += bytes(args) + + @dispatch + def add(self, x: ASTRootNode): + self.add(x.code) + + @dispatch + def add(self, x: ASTBlockNode): + self.add(x.code) + + @dispatch + def add(self, x: ASTCodeNode): + for i in x.nodes: + self.add(i) + + @dispatch + def add(self, x: ASTValueNode): + self.add(x.value) + + @dispatch + def add(self, x: ASTVardefNode): + if (x.value is not None): + self.load(x.value, sig=Signature.build(x.type, ns=self.ns)) + self.store(x.name) + + @dispatch + def add(self, x: ASTFunccallNode): + self.load(x) + self.add(POP) + + @dispatch + def add(self, x: ASTKeywordDefNode): + if (x.keyword.keyword == 'main'): + name = '
' + code_ns = self.ns.derive(name) + f_instrs = Instrs(name=name, ns=code_ns, filename=self.filename) + f_instrs.add(x.code) + self.add(CODE) + self.instrs += f_instrs.instrs + self.add(END) + self.add(CALL, 0) + self.add(POP) + else: raise NotImplementedError(x.keyword) + + @dispatch + def add(self, x: ASTConditionalNode): + self.load(x.condition) + self.add(IF) + self.add(x.code) + self.add(END) + + @dispatch + def add(self, x: ASTForLoopNode): + self.load(x.iterable) + self.builtin('iter', 1) + self.store(x.name) + self.add(DUP, 0) + self.add(LOOP) + self.add(x.code) + self.builtin('iter', 1) + self.store(x.name) + self.add(DUP, 0) + self.add(END) + + @dispatch + def add(self, x: ASTWhileLoopNode): + self.load(x.condition) + self.add(LOOP) + self.add(x.code) + self.load(x.condition) + self.add(END) + + @dispatch + def add(self, x: ASTElseClauseNode): + assert (self.instrs[-1] == END) + end = self.instrs.pop() + self.add(ELSE) + self.add(x.code) + self.add(end) + + @dispatch + def load(self, x: ASTLiteralNode, *, sig=None): + if (sig is None): sig = Signature.build(x, ns=self.ns) + + if (hasattr(sig, 'fmt')): + t, v = sig.__class__.__name__, struct.pack(sig.fmt, int(x.literal)) + elif (isinstance(sig, stdlib.int)): + t, v = 'i', writeVarInt(eval_literal(x)) + elif (isinstance(sig, stdlib.str)): + t, v = 's', eval_literal(x).encode('utf-8') + elif (isinstance(sig, stdlib.char)): + t, v = 'c', eval_literal(x).encode('utf-8') # TODO + else: raise NotImplementedError(sig) + + self.add(CONST) + self.instrs += t.encode('utf-8')+b'\0' + writeVarInt(len(v)) + v + + @dispatch + def load(self, x: ASTIdentifierNode, **kwargs): + self.load(x.identifier, **kwargs) + + @dispatch + def load(self, x: ASTValueNode, **kwargs): + self.load(x.value, **kwargs) + + @dispatch + def load(self, x: ASTFunccallNode): + nargs = int() + + for i in x.callargs.callargs: + self.load(i) + nargs += 1 + if (x.callargs.starargs): raise NotImplementedError() + + if (x.callkwargs.callkwargs): raise NotImplementedError() + if (x.callkwargs.starkwargs): raise NotImplementedError() + + if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures): + raise NotImplementedError() + #name = f"{x.callable.value.identifier}({CallArguments.build(x, self.ns)})" + #self.load(name) + #self.load(name+'.len') + #self.add(EXEC) + else: + self.load(x.callable) + self.add(CALL, nargs) + + @dispatch + def load(self, x: ASTAttrgetNode): + if (str(x.value) != 'stdio'): raise NotImplementedError(x) + if (x.optype.special != '.'): raise NotImplementedError(x) + self.builtin(str(x.attr)) + + @dispatch + def load(self, x: ASTBinaryExprNode, **kwargs): + self.load(x.lvalue, **kwargs) + self.load(x.rvalue, **kwargs) + self.builtin(self.binopmap[x.operator.operator], 2) + + @dispatch + def load(self, x: str, **kwargs): + self.add(SGET, self.scpcells[x]) + + @dispatch + def store(self, x: ASTIdentifierNode): + self.store(x.identifier) + + @dispatch + def store(self, x: str): + self.add(SSET, self.scpcells[x]) + + @dispatch + def assign(self, builtin: str, + nargs_code: lambda nargs_code: isinstance(nargs_code, int) and 0 <= nargs_code < 4, + nargs_stack: lambda nargs_stack: isinstance(nargs_stack, int) and 0 <= nargs_stack < 64, + opcode: int = None): + self.builtin(builtin) + if (opcode is None): opcode = first(sorted(set(range(0x10, 0x100)) - set(self.opmap.values()))) + self.add(ASGN, opcode, (nargs_code << 6) | nargs_stack) + self.opmap[builtin, nargs_code, nargs_stack] = opcode + + @dispatch + def builtin(self, builtin: str): + self.add(BLTIN) + self.instrs += builtin.encode('ascii')+b'\0' + + @dispatch + def builtin(self, builtin: str, nargs: int): + if (nargs is not None and len(self.opmap) < 0xf0): self.assign(builtin, 0, nargs) + if ((builtin, 0, nargs) in self.opmap): self.add(self.opmap[builtin, 0, nargs]) + else: self.builtin(builtin); self.add(CALL, nargs) + +class SBCCompiler(Compiler): + ext = '.sbc' + + @classmethod + def compile_ast(cls, ast, ns, *, filename): + instrs = Instrs(name='', ns=ns, filename=filename) + instrs.add(ast) + code = instrs.compile() + return code + +compiler = SBCCompiler + +# by Sdore, 2020 diff --git a/lexer.py b/lexer.py index 589f34e..bcc7a3a 100644 --- a/lexer.py +++ b/lexer.py @@ -7,13 +7,13 @@ def read_token(src, *, lineno, offset, lineoff): (l, src), line = lstripcount(src[offset:], whitespace), src offset += l if (src[:1] in '\n;'): return (offset, None) - length = int() + err = (0, 0) for ii, i in enumerate(Token.types): - r = globals()['find_'+i.casefold()](src) or 0 - if (isinstance(r, int) and r <= 0): length = max(length, -r); continue - n, s = r if (isinstance(r, tuple)) else (r, src[:r]) + r = globals()['find_'+i.casefold()](src) + n, s = r if (isinstance(r, tuple)) else (r, src[:r]) if (isinstance(r, int) and r > 0) else (0, None) + if (isinstance(n, int) and n <= 0): err = max(err, (-n, s if (isinstance(s, int)) else 0)); continue return (offset+n, Token(ii, s, lineno=lineno, offset=offset+lineoff)) - else: raise SlSyntaxError("Invalid token", line, lineno=lineno, offset=offset+lineoff, length=length+l) + else: raise SlSyntaxError("Invalid token", [None]*(lineno-1)+line.split('\n'), lineno=lineno, offset=offset+lineoff, length=err[0]+l, char=err[1]) def parse_expr(src, *, lineno=1, lineoff=0): r = list() diff --git a/repl.py b/repl.py index 91c8aeb..394667b 100644 --- a/repl.py +++ b/repl.py @@ -6,21 +6,28 @@ from .ast import * from .lexer import * from utils.nolog import * +def get_node_value(node, ns): + while (isinstance(node, ASTNode)): + node = execute_node(node, ns) + if (isiterablenostr(node)): + try: node = type(node)(get_node_value(i, ns) for i in node) + except Exception: pass + return node + @dispatch def execute_node(node: ASTCodeNode, ns): r = None for i in node.nodes: if (isinstance(i, ASTElseClauseNode) and r is not None): continue r = execute_node(i, ns) - if (r not in (None, ...)): + if (ns.flags.interactive and r not in (None, ...)): ns.values['_'] = r print(repr(r)) - else: return return r @dispatch def execute_node(node: ASTVardefNode, ns): - ns.values[node.name] = node.value + if (node.value is not None): ns.values[node.name] = get_node_value(node.value, ns) @dispatch def execute_node(node: ASTBlockNode, ns): @@ -28,19 +35,52 @@ def execute_node(node: ASTBlockNode, ns): @dispatch def execute_node(node: ASTFuncdefNode, ns): - ns.values[node.name.identifier] = (node.argdefs, node.code) + ns.values[node.name.identifier] = node @dispatch def execute_node(node: ASTAssignmentNode, ns): - ns.values[node.name] = execute_node(ASTBinaryExprNode(node.name, node.inplace_operator, node.value, lineno=node.lineno, offset=node.offset), ns) if (node.inplace_operator is not None) else node.value + ns.values[node.name] = execute_node(ASTBinaryExprNode(node.name, node.inplace_operator, node.value, lineno=node.value.lineno, offset=node.value.offset), ns) if (node.inplace_operator is not None) else get_node_value(node.value, ns) + +@dispatch +def execute_node(node: ASTUnaryOperationNode, ns): + def _op(): execute_node(ASTAssignmentNode(node.name, node.isattr, ASTSpecialNode('=', lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), ASTOperatorNode(node.unary_operator.operator[0], lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), ASTLiteralNode(1 if (node.unary_operator.operator[0] in '+-') else get_node_value(node.name, ns), lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), lineno=node.lineno, offset=node.offset), ns) + if (isinstance(node, ASTUnaryPreOperationNode)): _op() + res = ns.values[node.name] + if (isinstance(node, ASTUnaryPostOperationNode)): _op() + return res + +@dispatch +def execute_node(node: ASTItemgetNode, ns): + return get_node_value(node.value, ns)[get_node_value(node.key, ns)] + +@dispatch +def execute_node(node: ASTAttrgetNode, ns): + if (node.optype.special == '.'): + if (isinstance(node.value.value, ASTIdentifierNode) and node.value.value.identifier == 'stdio'): + if (node.attr.identifier == 'println'): return stdlib.stdio.println + elif (node.attr.identifier == 'map'): return stdlib._map + elif (node.attr.identifier == 'each'): return stdlib._each + raise NotImplementedError(node) @dispatch def execute_node(node: ASTFunccallNode, ns): - code_ns = ns.derive(node.callable.value.identifier) - argdefs, func = ns.values[node.callable] + func = execute_node(node.callable, ns) + + if (isinstance(func, type) and issubclass(func, stdlib.Builtin)): + if (func is stdlib.stdio.println): f = print + elif (func is stdlib._map): f = lambda l: [execute_node(ASTFunccallNode(node.callable.value.value, ASTCallargsNode([i], [], lineno=node.callargs.lineno, offset=node.callargs.offset), ASTCallkwargsNode([], [], lineno=node.callkwargs.lineno, offset=node.callkwargs.offset), lineno=node.callable.lineno, offset=node.callable.offset), ns) for i in l] + elif (func is stdlib._each): f = lambda _: [execute_node(ASTFunccallNode(node.callargs.callargs[0].value, ASTCallargsNode([i], [], lineno=node.callargs.lineno, offset=node.callargs.offset), ASTCallkwargsNode([], [], lineno=node.callkwargs.lineno, offset=node.callkwargs.offset), lineno=node.callable.lineno, offset=node.callable.offset), ns) for i in get_node_value(node.callable.value.value, ns)] + else: raise NotImplementedError(func) + + callarguments = CallArguments.build(node, ns) + assert (func.compatible_call(callarguments, ns) is not None) + return f(*(get_node_value(i, ns) for i in node.callargs.callargs), + *(get_node_value(j, ns) for i in node.callargs.starargs for j in get_node_value(i, ns))) + + code_ns = ns.derive(str(node.callable), append=False) for ii, i in enumerate(node.callargs.callargs): - code_ns.values[argdefs[ii].name] = i - return execute_node(func, code_ns) + code_ns.values[func.argdefs[ii].name] = get_node_value(i, ns) + return execute_node(func.code, code_ns) @dispatch def execute_node(node: ASTValueNode, ns): @@ -48,24 +88,35 @@ def execute_node(node: ASTValueNode, ns): @dispatch def execute_node(node: ASTIdentifierNode, ns): - try: return ns.values[node] - except KeyError: raise SlValidationError(f"{node} is not initialized", node, scope=ns.scope) + if (ns.values.get(node) is None): raise SlValidationError(f"{node} is not initialized", node, scope=ns.scope) + return ns.values[node] @dispatch def execute_node(node: ASTLiteralNode, ns): - return eval(node.literal) if (isinstance(node.literal, str)) else node.literal + if (isinstance(node.literal, str)): + try: return eval(node.literal) + except Exception as ex: raise SlReplError(ex, node, scope=ns.scope) + else: return node.literal @dispatch def execute_node(node: ASTListNode, ns): - return node.values + return list(node.values) + +@dispatch +def execute_node(node: ASTTupleNode, ns): + return tuple(node.values) @dispatch def execute_node(node: ASTKeywordExprNode, ns): - #if (node.keyword.keyword == 'return'): return execute_node(node.value, ns) # TODO FIXME??? - #el - if (node.keyword.keyword == 'delete'): ns.delete(node.value) + if (node.keyword.keyword == 'return'): return execute_node(node.value, ns) + elif (node.keyword.keyword == 'delete'): ns.delete(node.value) else: raise NotImplementedError(node.keyword) +@dispatch +def execute_node(node: ASTKeywordDefNode, ns): + if (node.keyword.keyword == 'main'): + execute_node(node.code, ns) + @dispatch def execute_node(node: ASTConditionalNode, ns): if (execute_node(node.condition, ns)): @@ -75,23 +126,44 @@ def execute_node(node: ASTConditionalNode, ns): @dispatch def execute_node(node: ASTForLoopNode, ns): + ns.define(node.name, Signature.build(node.iterable, ns).valtype) + ns.weaken(node.name) for i in execute_node(node.iterable, ns): + ns.values[node.name] = get_node_value(i, ns) execute_node(node.code, ns) else: return return ... +@dispatch +def execute_node(node: ASTWhileLoopNode, ns): + while (get_node_value(execute_node(node.condition, ns), ns)): + execute_node(node.code, ns) + else: return + return ... + +@dispatch +def execute_node(node: ASTElseClauseNode, ns): + execute_node(node.code, ns) + @dispatch def execute_node(node: ASTUnaryExprNode, ns): - value = execute_node(node.value, ns) - return eval(f"{node.operator.operator} value") + value = get_node_value(node.value, ns) + try: return eval(f"{node.operator.operator} value") + except Exception as ex: raise SlReplError(ex, node, scope=ns.scope) @dispatch def execute_node(node: ASTBinaryExprNode, ns): - lvalue = execute_node(node.lvalue, ns) - rvalue = execute_node(node.rvalue, ns) - if (node.operator.operator == 'xor'): return eval("(lvalue and not rvalue) or (rvalue and not lvalue)") - elif (node.operator.operator == 'to'): return range(lvalue, rvalue) - else: return eval(f"lvalue {node.operator.operator} rvalue") + lvalue = get_node_value(node.lvalue, ns) + rvalue = get_node_value(node.rvalue, ns) + if (node.operator.operator == 'xor'): + try: return eval("(lvalue and not rvalue) or (rvalue and not lvalue)") + except Exception as ex: raise SlReplError(ex, node, scope=ns.scope) + elif (node.operator.operator == 'to'): + try: return range(lvalue, rvalue) + except Exception as ex: raise SlReplError(ex, node, scope=ns.scope) + else: + try: return eval(f"lvalue {node.operator.operator} rvalue") + except Exception as ex: raise SlReplError(ex, node, scope=ns.scope) class Completer: def __init__(self, namespace): @@ -155,9 +227,23 @@ class Completer: matches.sort() return matches -def repl(): +class SlReplError(SlNodeException): + ex: ... + + def __init__(self, ex, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ex = ex + + def __exline__(self): + return "Repl error" + + def __exsubline__(self): + return f"\n\033[1;91mException\033[0m:\n "+'\n '.join(traceback.format_exception_only(type(self.ex), self.ex)) + +def repl(*, optimize=0): ns = Namespace('') - ns.define(ASTIdentifierNode('_', lineno=None, offset=None), stdlib.Any()) + ns.define(ASTIdentifierNode('_', lineno=None, offset=None), stdlib.Any) + ns.flags.interactive = True completer = Completer(ns) histfile = os.path.expanduser('~/.sli_history') @@ -187,8 +273,8 @@ def repl(): #if (len(tl) >= 2 and tl[-2][-1].token == '\\'): tl[-1] = tl[-2][:-1]+tl.pop() # TODO FIXME?: [['a', '+', '\\'], 'b'] --> [['a', '+', 'b']] if (tl[0][-1].token == '{' and tl[-1][-1].token != '}'): continue ast = build_ast(tl, interactive=True) + if (optimize): optimize_ast(ast, validate_ast(ast), optimize) validate_ast(ast, ns) - optimize_ast(ast, ns) execute_node(ast.code, ns) except KeyboardInterrupt: buf = readline.get_line_buffer() @@ -196,13 +282,33 @@ def repl(): except EOFError: print(end='\r\033[K') break - except (SlSyntaxError, SlValidationError) as ex: - ex.line = l[ex.lineno-1] + except (SlSyntaxException, SlNodeException) as ex: + if (not ex.srclines): ex.srclines = l print(ex) tl.clear() l.clear() finally: readline.write_history_file(histfile) -if (__name__ == '__main__'): repl() +def run_file(file, *, optimize=0): + src = file.read() + + try: + tl = parse_string(src) + ast = build_ast(tl, file.name.join('""')) + if (optimize): optimize_ast(ast, validate_ast(ast), optimize) + ns = validate_ast(ast) + execute_node(ast.code, ns) + except (SlSyntaxException, SlNodeException) as ex: + if (not ex.srclines): ex.srclines = src.split('\n') + sys.exit(str(ex)) + +@apmain +@aparg('file', metavar='file.sl', nargs='?', type=argparse.FileType('r')) +@aparg('-O', metavar='level', help='Code optimization level', type=int, default=DEFAULT_OLEVEL) +def main(cargs): + if (cargs.file is not None): run_file(cargs.file, optimize=cargs.O) + else: repl(optimize=cargs.O) + +if (__name__ == '__main__'): main(nolog=True) # by Sdore, 2020 diff --git a/sl/Slang/Slang.sl b/sl/Slang/Slang.sl index 3341fe5..f98fef3 100644 --- a/sl/Slang/Slang.sl +++ b/sl/Slang/Slang.sl @@ -14,4 +14,5 @@ main { stdio.println("\n") } -# by Sdore, 2020 +# by Sdore, 2021 +# slang.sdore.me diff --git a/sl/Slang/lexer.sl b/sl/Slang/lexer.sl index 4d0659f..4022702 100644 --- a/sl/Slang/lexer.sl +++ b/sl/Slang/lexer.sl @@ -3,7 +3,8 @@ import tokens:* tuple read_token(str src, int lineno, int offset, int lineoff) { - tuple c = lstripcount(src#|[offset:]|#, whitespace) + tuple c = lstripcount(src[offset:], whitespace) + int l = c[0] src = c[1] str line = src @@ -11,39 +12,42 @@ tuple read_token(str src, int lineno, int offset, int lineoff) { if not src or src[0] in '\n;' { return (offset, None) } - int length = 0 - int ii = -1 + + int length = 0, ii = -1 + for i in Token.types { ii += 1 auto r = globals['find_'+i.casefold()](src) or 0 + if r isof int and r <= 0 { length = max(length, -r) continue } + tuple c - if r isof tuple { - c = r - } - else { - c = (r, src[:r]) - } + + if r isof tuple: c = r + else: c = (r, src[:r]) + int n = c[0] str s = c[1] + return (offset+n, Token(ii, s, lineno=lineno, offset=offset+lineoff)) - } #else raise SlSyntaxError("Invalid token", line, lineno=lineno, offset=offset+lineoff, length=length) + } else: raise SlSyntaxError("Invalid token", line, lineno=lineno, offset=offset+lineoff, length=length) + return (0, Token()) } tuple parse_expr(str src, int lineno = 1, int lineoff = 0) { list r = [Token] int offset - while 1 { + + while true { offset, tok = read_token(src, lineno=lineno, offset=offset, lineoff=lineoff) - if tok is None { - break - } + if tok is None: break r.append(tok) } + return (offset, r) } @@ -52,23 +56,25 @@ list parse_string(str src) { list tl = [Token] int lines = src.count('\n') int lineoff = 0 + while src { tuple c = parse_expr(src, lineno=lines-src.count('\n')+1, lineoff=lineoff) int offset = c[0] list r = c[1] + lineoff += offset + if offset < src.len { - if src[offset] == '\n' { - lineoff = 0 - } - else { - lineoff += 1 - } + if src[offset] == '\n': lineoff = 0 + else: lineoff += 1 } + src = src[offset+1:] tl.append(r) } + return tl } -# by Sdore, 2020 +# by Sdore, 2021 +# slang.sdore.me diff --git a/sl/Slang/tokens.sl b/sl/Slang/tokens.sl index 36e5abe..ab06a19 100644 --- a/sl/Slang/tokens.sl +++ b/sl/Slang/tokens.sl @@ -1,49 +1,35 @@ # Slang tokens -str whitespace = ' \t\r\v\f' +const str whitespace = " \t\r\v\f" tuple lstripcount(str s, str chars) { int ii = -1 char i + for i in s { ii += 1 - if i not in chars { - break - } - } - else { - ii = 0 - } - return (ii, s#|[ii:]|#) + if i not in chars: break + } else: ii = 0 + + return (ii, s[ii:]) } class Token { - int type + const tuple types = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order is also resolution order + + int type, lineno, offset str token - int lineno - int offset - tuple types# = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order is also resolution order + constr (int .type, str .token, int .lineno, int .offset); - constr(int type, str token, int lineno, int offset) { - #.type, .token, .lineno, .offset = type, token, lineno, offset - } + repr = f"" - #|repr { - return "" - }|# + eq = (super == x or .token == x) - #|eq { - #return super() == x or .token == x - }|# + property typename = .types[.type] - #|property typename { - #return .types[.type] - } - - property length { - #return .token.length - }|# + property length = .token.length } -# by Sdore, 2020 +# by Sdore, 2021 +# slang.sdore.me diff --git a/slang.lang b/slang.lang index fa2a74d..18ec99c 100644 --- a/slang.lang +++ b/slang.lang @@ -47,6 +47,7 @@ + #\| \|# @@ -54,7 +55,8 @@ - + + \|#(?!#\|) @@ -206,6 +208,7 @@ (?<![\w\.]) + (\.\w+)? stdio globals diff --git a/sld.py b/sld.py index af2fdc3..3859d34 100644 --- a/sld.py +++ b/sld.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # Slang .sld parser -# .sld files are used for definition of built-in, implementation-provided, external and native components. +# .sld files are used for definition of built-in, implementation-provided, external and native components, and documentation. from .tokens import * from utils import * diff --git a/stdlib.py b/stdlib.py index 4acca73..9eb1e95 100644 --- a/stdlib.py +++ b/stdlib.py @@ -1,14 +1,11 @@ #!/usr/bin/python3 # Slang stdlib -from .ast import Signature, Function, Object, Collection, CallArguments +from .ast import Signature, Function, Object, Collection, CallArguments, MultiCollection from .tokens import * from utils import * class Builtin(Signature): - def __init__(self): - pass - @classitemget def attrops(cls, optype, attr): if (optype == '.'): @@ -25,18 +22,12 @@ class Builtin(Signature): return self.__class__.__name__ class BuiltinFunction(Builtin, Function): - @property - def name(self): - return self.__class__.__name__ + def __init__(self): + super().__init__(name=self.__class__.__name__) class BuiltinObject(Builtin, Object): pass class BuiltinType(Builtin): - @init_defaults - @autocast - def __init__(self, *, modifiers: paramset): - self.modifiers = modifiers - def __eq__(self, x): return (super().__eq__(x) or issubclass(x.__class__, self.__class__) or issubclass(self.__class__, x.__class__)) @@ -51,10 +42,13 @@ class BuiltinType(Builtin): if (op in operators[10]+operators[11]+operators[12]): return type(valsig) # binary `and'. `xor', `or' raise KeyError() +@singleton class Any(BuiltinType): def __eq__(self, x): return True +class auto(BuiltinType): pass + class void(BuiltinType): @staticitemget def operators(op, valsig=None): @@ -81,7 +75,7 @@ class int(BuiltinType): if (op in map(UnaryOperator, '+-~')): return int if (op == UnaryOperator('!')): return bool if (isinstance(valsig, (int, float))): - if (op in map(BinaryOperator, ('**', *'+-*'))): return valsig + if (op in map(BinaryOperator, ('**', *'+-*%'))): return valsig if (op in map(BinaryOperator, ('//', '<<', '>>', *'&^|'))): return int if (op == BinaryOperator('/')): return float if (isinstance(valsig, int)): @@ -98,7 +92,7 @@ class float(BuiltinType): if (op in map(UnaryOperator, '+-')): return float if (op == UnaryOperator('!')): return bool if (isinstance(valsig, (int, float))): - if (op in map(BinaryOperator, ('**', *'+-*'))): return float + if (op in map(BinaryOperator, ('**', *'+-*%'))): return float if (op == BinaryOperator('/')): return float if (op == BinaryOperator('//')): return int raise KeyError() @@ -121,22 +115,22 @@ class str(BuiltinType): raise KeyError() class rstrip(BuiltinFunction): - callargssigstr = "rstrip(char)" + callargssigstr: "rstrip(char)" - @staticitemget + @staticmethod @instantiate - def call(callargssig): - if (callargssig.kwargs or callargssig.starkwargs): raise KeyError() - return str + def compatible_call(callarguments, ns): + if (callarguments.kwargs or callarguments.starkwargs): return None + return (None, str()) class count(BuiltinFunction): - callargssig = "count(char)" + callargssig: "count(char)" - @staticitemget + @staticmethod @instantiate - def call(callargssig): - if (callargssig.kwargs or callargssig.starkwargs): raise KeyError() - return int + def compatible_call(callarguments, ns): + if (callarguments.kwargs or callarguments.starkwargs): return None + return (None, int()) class char(BuiltinType): @staticitemget @@ -150,58 +144,80 @@ class char(BuiltinType): if (isinstance(valsig, (char, int)) and op in map(BinaryOperator, '+-')): return char raise KeyError() -class i8(int): fmt = 'b' -class u8(int): fmt = 'B' -class i16(int): fmt = 'h' -class u16(int): fmt = 'H' -class i32(int): fmt = 'i' -class u32(int): fmt = 'I' -class i64(int): fmt = 'q' -class u64(int): fmt = 'Q' -#class i128(int): fmt = ' -#class u128(int): fmt = ' +class i8(int): fmt: 'b' +class u8(int): fmt: 'B' +class i16(int): fmt: 'h' +class u16(int): fmt: 'H' +class i32(int): fmt: 'i' +class u32(int): fmt: 'I' +class i64(int): fmt: 'q' +class u64(int): fmt: 'Q' +#class i128(int): fmt: ' +#class u128(int): fmt: ' -#class f8(float): fmt = ' -#class f16(float): fmt = 'e' -#class f32(float): fmt = 'f' -#class f64(float): fmt = 'd' -#class f128(float): fmt = ' +#class f8(float): fmt: ' +#class f16(float): fmt: 'e' +#class f32(float): fmt: 'f' +#class f64(float): fmt: 'd' +#class f128(float): fmt: ' #uf8 = uf16 = uf32 = uf64 = uf128 = float class range(BuiltinType): - keytype = int - valtype = int + keytype: int + valtype: int @staticitemget def operators(op, valsig=None): raise KeyError() -class iterable(BuiltinType, Collection): pass +class iterable(Collection, BuiltinType): pass class list(iterable): - keytype = int() - valtype = Any() + keytype: int -class tuple(iterable): - keytype = int() - valtype = Any() + def __init__(self, *, valtype=Any): + super().__init__(keytype=self.keytype, valtype=valtype) + + @staticitemget + def attrops(optype, attr): + if (optype == '.'): + if (attr == 'each'): return _each() + raise KeyError() + +class tuple(iterable, MultiCollection): + keytype: int + + def __init__(self, *, valtypes=()): + super().__init__(keytype=self.keytype, valtypes=valtypes) class stdio(BuiltinObject): class println(BuiltinFunction): - callargssigstr = "println(...)" + callargssigstr: "println(...)" - @staticitemget - @instantiate - def call(callargssig): - if (callargssig.kwargs or callargssig.starkwargs): raise KeyError() - return void + @staticmethod + def compatible_call(callarguments, ns): + if (callarguments.kwargs or callarguments.starkwargs): return None + return (None, void()) class _map(BuiltinFunction): - @staticitemget - @instantiate - def call(callargssig): - if (callargssig.kwargs or callargssig.starkwargs): raise KeyError() - return list + @staticmethod + def compatible_call(callarguments, ns): + if (len(callarguments.args) != 1 or + callarguments.starargs or + callarguments.kwargs or + callarguments.starkwargs): return None + return (None, list(valtype=Any)) + + +class _each(BuiltinFunction): + @staticmethod + def compatible_call(callarguments, ns): + if (len(callarguments.args) != 1 or + callarguments.starargs or + callarguments.kwargs or + callarguments.starkwargs): return None + #fsig = Signature.build(callarguments.args[0], ns) + return (None, list(valtype=Any)) builtin_names = {k: v for i in map(allsubclassdict, Builtin.__subclasses__()) for k, v in i.items()} builtin_names.update({i: globals()[i] for i in (i+j for j in map(builtins.str, (8, 16, 32, 64, 128)) for i in (*'iuf', 'uf') if i+j in globals())}) diff --git a/stdlib/std.sld b/stdlib/std.sld index cde3626..e28e542 100644 --- a/stdlib/std.sld +++ b/stdlib/std.sld @@ -19,13 +19,14 @@ class int { int operator *int; int operator //int; int operator **int; + int operator %int; int operator <>int; int operator ∫ int operator ^int; int operator |int; - range operator to int; + range operator 'to' int; int popcount(); int length(int base=2); @@ -42,6 +43,7 @@ class float { float operator /float; int operator //float; float operator **float; + float operator %float; int round(); bool isint(); diff --git a/tests/class.sl b/tests/class.sl index f059159..2fd210f 100644 --- a/tests/class.sl +++ b/tests/class.sl @@ -1,3 +1,5 @@ +#| Slang `class' test. |# + class Test { int a = 1 @@ -9,29 +11,27 @@ class Test { .a = 5 } - constr (int a) { - .a = a - } + constr (int .a); } main { - stdio.println(Test.a) + stdio.println(Test.a) # 1 Test t - stdio.println(t.a) + stdio.println(t.a) # 3 t.a = 2 - stdio.println(t.a) + stdio.println(t.a) # 2 delete t Test t = Test() - stdio.println(t.a) + stdio.println(t.a) # 5 delete t Test t = Test(7) - stdio.println(t.a) + stdio.println(t.a) # 7 delete t #Test t(10) - #stdio.println(t.a) + #stdio.println(t.a) # 10 #delete t } diff --git a/tests/colonkw.sl b/tests/colonkw.sl new file mode 100644 index 0000000..d80b8e3 --- /dev/null +++ b/tests/colonkw.sl @@ -0,0 +1,7 @@ +#| Slang `colonkw' test. |# + +int a(int y) = y + +main { + a(y: 3) +} diff --git a/tests/divzero.sl b/tests/divzero.sl new file mode 100644 index 0000000..af0087f --- /dev/null +++ b/tests/divzero.sl @@ -0,0 +1,7 @@ +#| Slang `divzero' test. |# + +main { + u8 a = 1 + u8 b = 0 + stdio.println(a/b) +} diff --git a/tests/each.sl b/tests/each.sl new file mode 100644 index 0000000..c17df53 --- /dev/null +++ b/tests/each.sl @@ -0,0 +1,8 @@ +#| Slang `each' test. |# + +main { + list l = [int: 1, 2, 3] + l.each(stdio.println) + + [int: 4, 5].each(stdio.println) +} diff --git a/tests/example.sl b/tests/example.sl index cdf44d9..40f07b5 100644 --- a/tests/example.sl +++ b/tests/example.sl @@ -2,24 +2,24 @@ #| and that is a multiline one. |# -const u32 n = 123123 # n is of type const u32 -const i64 m = 10**18 # m is of type const i64 -const int z = 2**128 # z is of type const int (unsized) -const auto q = 2**256 # q is of type const int +const u32 n = 123123 # n: const u32 (unsigned 32 bit) +const i64 m = 10**18 # m: const i64 (signed 64 bit) +const int z = 2**128 # z: const int (signed unsized) +const auto q = 2**256 # q: const int (signed unsized) -char f(str x) { # f() is of type char, x is of type str - auto c = x[1] # c is of type char - return c +char f(str x) { # f(): char, x: str + auto c = x[1] # c: char + return c # char } -auto g(str x) { # g() is of type char, x is of type str - return x[0] # retval is of type char +auto g(str x) { # g(): char, x: str + return x[0] # char } -int h(int x) = x+1 # h() is of type int, x is of type int +int h(int x) = x+1 # h(): int, x: int main { stdio.println(h(n), \ # comment here too f('123asd') + g('32') + 1) #--> «123124 f» - stdio.println(q/z/2**96) #--> «4294967296.0» + stdio.println(q/z/2**96) #--> «4294967296.0» } diff --git a/tests/fib.sl b/tests/fib.sl index 8f1c422..e912f2a 100644 --- a/tests/fib.sl +++ b/tests/fib.sl @@ -1,3 +1,5 @@ +#| Slang `fib' test. |# + int fib(int n) { if (n < 2) {return 1} return fib(n-1) + fib(n-2) diff --git a/tests/for.sl b/tests/for.sl index fb837f5..76cae86 100644 --- a/tests/for.sl +++ b/tests/for.sl @@ -1,3 +1,5 @@ +#| Slang `for' test. |# + main { for i in (0 to 5) { stdio.println(i) diff --git a/tests/funcdef.sl b/tests/funcdef.sl index 6873cf9..4d0f31f 100644 --- a/tests/funcdef.sl +++ b/tests/funcdef.sl @@ -1,2 +1,4 @@ +#| Slang `funcdef' test. |# + void f() {} void f(int a?) {} diff --git a/tests/list.sl b/tests/list.sl index 2e5c1a9..717244d 100644 --- a/tests/list.sl +++ b/tests/list.sl @@ -1,3 +1,5 @@ +#| Slang `list' test. |# + list l = [int: 1, 2, 3] main { @@ -7,4 +9,6 @@ main { stdio.println(i, l[i]) #i -=- 1 } + for i in l stdio.println(i) + stdio.println(l, *l) } diff --git a/tests/fmap.sl b/tests/map.sl similarity index 72% rename from tests/fmap.sl rename to tests/map.sl index 9ec63b1..ed635e0 100644 --- a/tests/fmap.sl +++ b/tests/map.sl @@ -1,3 +1,5 @@ +#| Slang `map' test. |# + void f(int x) = stdio.println(x) main { diff --git a/tests/nestedcomm.sl b/tests/nestedcomm.sl new file mode 100644 index 0000000..d9c38c5 --- /dev/null +++ b/tests/nestedcomm.sl @@ -0,0 +1,5 @@ +#| Slang `nestedcomm' test. |# + +#| test #| a |#|# +main { #a +} diff --git a/tests/nl.sl b/tests/nl.sl index 7cb249b..7eee666 100644 --- a/tests/nl.sl +++ b/tests/nl.sl @@ -1,4 +1,6 @@ +#| Slang `nl' test. |# + main { stdio.println(1, #| |# 2) -} \ No newline at end of file +} diff --git a/tests/opti.sl b/tests/opti.sl index e27c191..f11a278 100644 --- a/tests/opti.sl +++ b/tests/opti.sl @@ -1,5 +1,16 @@ +#| Slang `opti' test. |# + int a = 3 +void f() {a = 5} +void g() {a += 2+2} + main { - stdio.println(2**a) + stdio.println(a) + a = 4 + stdio.println(a) + f() + stdio.println(a) + g() + stdio.println(a) } diff --git a/tests/overload.sl b/tests/overload.sl index 0a9f865..9863ac6 100644 --- a/tests/overload.sl +++ b/tests/overload.sl @@ -1,7 +1,10 @@ +#| Slang `overload' test. |# + int f(int x) = x+1 int f(int x, int y) = x+y+1 main { stdio.println(f(1)) stdio.println(f(1, 2)) + f() } diff --git a/tests/println.sl b/tests/println.sl index 2e9e0f4..99eb3ba 100644 --- a/tests/println.sl +++ b/tests/println.sl @@ -1,3 +1,8 @@ +#| Slang `println' test. |# + main { - stdio.println('test') + stdio.println(3) } + +stdio.println(5) +stdio.println(5) diff --git a/tests/range.sl b/tests/range.sl new file mode 100644 index 0000000..262e424 --- /dev/null +++ b/tests/range.sl @@ -0,0 +1,5 @@ +#| Slang `range' test. |# + +main { + stdio.println(1 to 3) +} diff --git a/tests/redefine.sl b/tests/redefine.sl index ca50b3f..87f4e23 100644 --- a/tests/redefine.sl +++ b/tests/redefine.sl @@ -1,6 +1,8 @@ +#| Slang `redefine' test. |# + main { int a a = 3 a, b := (int a, char 'b') stdio.println(a, b) -} \ No newline at end of file +} diff --git a/tests/scope.sl b/tests/scope.sl new file mode 100644 index 0000000..61467af --- /dev/null +++ b/tests/scope.sl @@ -0,0 +1,16 @@ +#| Slang `scope' test. |# + +int a = 3 + +int g() = 5 + +int f() { + int a = 4 + int g = g() + return a+g +} + +main { + int a = f() + stdio.println(a) +} diff --git a/tests/strcat.sl b/tests/strcat.sl index e3cbe38..cca083e 100644 --- a/tests/strcat.sl +++ b/tests/strcat.sl @@ -1,3 +1,5 @@ +#| Slang `strcat' test. |# + str a = "a" str b = "B" str c = a+b diff --git a/tests/sum.sl b/tests/sum.sl index f7612c8..dd7911c 100644 --- a/tests/sum.sl +++ b/tests/sum.sl @@ -1,4 +1,6 @@ -i8 a = 3 -i8 b = 5 -i8 c = a+b +#| Slang `sum' test. |# + +i64 a = 97 +i64 b = 5 +i64 c = a+b stdio.println(c) diff --git a/tests/test.sl b/tests/test.sl index 81ee32f..3f9a721 100644 --- a/tests/test.sl +++ b/tests/test.sl @@ -1,5 +1,9 @@ +#| Slang `test' test. |# + const int a = 3 -int b = 2; int c = 0; int x = 9 +int b = 2 +int c = 0 +int x = 9 int sum(int a, int z) = a + z stdio.println(sum(a, b & 3)) @@ -8,7 +12,7 @@ stdio.println(1, 2) stdio.println('a') stdio.println(2*(3)+-2*(5+1)/2) stdio.println(-2*x**(2+2)*a*b+10*c) -stdio.println(*'a'+'b'*2) +stdio.println(*"a"+"b"*2) stdio.println(-2-2-2-2-2-2-2-2) main { @@ -20,9 +24,10 @@ main { int test() = 2*2 stdio.println(sum(b, test())) - for i in 0 to 5 stdio.println(i) - else stdio.println(0) + for i in (0 to 5): stdio.println(i) + else: stdio.println(0) int i = 5 - while i {stdio.println(i); i -= 1;} else stdio.println('ВСЁ!') + while i: stdio.println(i); i -= 1 + else: stdio.println('ВСЁ!') } diff --git a/tests/tuple.sl b/tests/tuple.sl new file mode 100644 index 0000000..89be2b6 --- /dev/null +++ b/tests/tuple.sl @@ -0,0 +1,6 @@ +#| Slang `tuple' test. |# + +main { + tuple a = (int 1, str "test") + stdio.println(a, *a) +} diff --git a/tests/underscore.sl b/tests/underscore.sl index 7cde44f..3eed0bf 100644 --- a/tests/underscore.sl +++ b/tests/underscore.sl @@ -1 +1,3 @@ -int _a_b_ \ No newline at end of file +#| Slang `underscore' test. |# + +int _a_b_ diff --git a/tests/wrongnl.sl b/tests/wrongnl.sl index 6757fc7..e5ff200 100644 --- a/tests/wrongnl.sl +++ b/tests/wrongnl.sl @@ -1,4 +1,6 @@ +#| Slang `wrongnl' test. |# + main { stdio.println(1, \ 2, 3) -} \ No newline at end of file +} diff --git a/tokens.py b/tokens.py index 482ac61..ed199ed 100644 --- a/tokens.py +++ b/tokens.py @@ -15,6 +15,9 @@ class Modifier(Keyword): pass class ReservedKeyword(Keyword): pass class Operator(str): pass + #def __eq__(self, x): + # if (isinstance(x, Operator)): return (type(x) is type(self) and x == self) + # return super().__eq__(x) class UnaryOperator(Operator): pass class BinaryOperator(Operator): pass class BothOperator(UnaryOperator, BinaryOperator): pass @@ -51,10 +54,13 @@ keywords = ( ReservedKeyword('raise'), ReservedKeyword('with'), ReservedKeyword('yield'), + ReservedKeyword('include'), + ReservedKeyword('using'), + ReservedKeyword('default'), ) operators = (*(tuple(map(*i)) for i in ( # ordered by priority - (UnaryOperator, '!+-~'), + (UnaryOperator, (*'!+-~', '++', '--', '**')), (BinaryOperator, ('**',)), (BinaryOperator, ('//', *'*/%')), (BinaryOperator, '+-'), @@ -69,7 +75,8 @@ operators = (*(tuple(map(*i)) for i in ( # ordered by priority (BinaryOperator, ('||', 'or')), (BinaryOperator, ('to',)), )),) -bothoperators = '+-' +unaryops = ('++', '--', '**') +bothoperators = (*'+-', *unaryops) attrops = ('->', '@.', *'@.:') logical_operators = ('is', 'is not', 'in', 'not in', 'not', 'and', 'but', 'xor', 'or', 'isof') @@ -83,6 +90,7 @@ def find_identifier(s): if (not s[i].isalnum() and s[i] != '_'): break else: i += 1 if (s[:i].isidentifier()): return i + return (0, i) def find_keyword(s): if (not s): return @@ -98,7 +106,9 @@ def find_literal(s): for i in range(1, len(s)): if (esc): esc = False; continue if (s[i] == '\\'): esc = True; continue + if (s[i] == '\n'): break if (s[i] == s[0]): return i+1 + return (0, i) if (s[0].isdigit() or s[0] == '.'): i = int() digits = '0123456789abcdef' @@ -108,7 +118,7 @@ def find_literal(s): for i in range(1, len(s)): if (i == 1 and s[0] == '0'): if (s[1] not in 'box'): - if (s[1].isalnum()): return + if (s[1].isalnum()): break return 1 else: radix = (2, 8, 16)['box'.index(s[1])] @@ -117,29 +127,38 @@ def find_literal(s): if (s[i].casefold() not in digits[:radix]): if (s[i] == '_'): continue if (s[i] == '.' and not dp): dp = True; continue - if (not digit or s[i].isalpha()): return + if (not digit or s[i].isalnum()): break return i digit = True - if (s[i].casefold() in digits[:radix] or s[i] == '.' and dp): return i+1 + else: + if (s[i].casefold() in digits[:radix] or s[i] == '.' and dp): return i+1 + c = i + while (i < len(s) and (s[i].isalnum() or s[i] == '.')): i += 1 + return (-i+1, c) def find_operator(s): if (not s): return for i in sorted(itertools.chain(*operators), key=len, reverse=True): if (s.startswith(i)): l = len(i) - if (not (i[-1].isalpha() and s[l:l+1].isidentifier())): return (len(i), BothOperator(i) if (i in bothoperators) else i) + if (not (i[-1].isalpha() and s[l:l+1].isidentifier())): return (l, BothOperator(i) if (i in bothoperators) else i) def find_special(s): if (not s): return if (s[0] == '.' and s[1:2].isdigit()): return if (s[:2] == '#|'): - l = s.find('|#', 2) - if (l == -1): return -2 - return l+2 + o = 2 + l = 1 + while (s[o:] and l > 0): + #if (s[o:o+3] in ('#|#', '|#|')): o += 2; break + if (s[o:o+2] == '#|'): l += 1; o += 2 + elif (s[o:o+2] == '|#'): l -= 1; o += 2 + else: o += 1 + if (l > 0): return (0, o) + return o if (s[0] == '#'): - l = s.find('\n', 1) - if (l == -1): return len(s) - return l + s = s.partition('\n')[0] + return len(s) if (s[1:] and not s[1:].isspace()) else (0, 1) if (s[0] == '\\'): return 1 for i in sorted(specials, key=len, reverse=True): if (s[:len(i)] == i): @@ -148,12 +167,15 @@ def find_special(s): def operator_precedence(op): for ii, i in enumerate(operators): - if (op in i): return ii - #else: return len(operators) + if (any(op == j and type(op) is type(j) for j in i)): return ii + else: raise WTFException(op) -class Token: - __slots__ = ('type', 'token', 'lineno', 'offset') - types = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order is also resolution order +class Token(Slots): + types = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order in tuple is resolution order + type: ... + token: ... + lineno: ... + offset: ... def __init__(self, type, token, *, lineno, offset): self.type, self.token, self.lineno, self.offset = type, token, lineno, offset @@ -162,7 +184,7 @@ class Token: return f"" def __eq__(self, x): - return super() == x or self.token == x + return (self.token == x) @property def typename(self): @@ -172,43 +194,38 @@ class Token: def length(self): return len(self.token) -def lstripcount(s, chars): - for ii, i in enumerate(s): - if (i not in chars): break - else: ii = 0 - return (ii, s[ii:]) - -class SlSyntaxException(Exception): pass +class SlSyntaxException(Exception, Slots): pass class SlSyntaxNoToken(SlSyntaxException): pass class SlSyntaxEmpty(SlSyntaxNoToken): pass class SlSyntaxError(SlSyntaxException): - __slots__ = ('desc', 'line', 'lineno', 'offset', 'length', 'usage') + desc: ... + srclines: ... + lineno: ... + offset: ... + length: ... + char: ... + usage: None - #@dispatch - def __init__(self, desc='Syntax error', line='', *, lineno, offset, length, scope=None): - self.desc, self.line, self.lineno, self.offset, self.length = (f'\033[2m(in {scope})\033[0m ' if (scope is not None) else '')+desc, line, lineno, offset, length - self.usage = None - - #@dispatch - #def __init__(self, desc='Syntax error', *, token): - # self.desc, self.line, self.lineno, self.offset, self.length = desc, '', token.lineno, token.offset, token.length + def __init__(self, desc='Syntax error', srclines=(), *, lineno, offset, length, char=0, scope=None): + self.desc, self.srclines, self.lineno, self.offset, self.length, self.char = (f'\033[2m(in {scope})\033[0m ' if (scope is not None) else '')+desc, srclines, lineno, offset, length, char def __str__(self): - l, line = lstripcount(self.line.partition('\n')[0].replace('\t', ' '), ' \t') + l, line = lstripcount(self.srclines[self.lineno-1].partition('\n')[0]) if (self.srclines) else (0, '') offset = (self.offset-l) if (self.offset >= 0) else (len(line)+self.offset+1) - #Syntax error: - return f"{self.desc}{self.at}"+(':\n'+\ - ' \033[1m'+line[:offset]+'\033[91m'*(self.offset >= 0)+line[offset:]+'\033[0m\n'+\ - ' '+' '*offset+'\033[95m^'+'~'*(self.length-1)+'\033[0m' if (line) else '') + \ - (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__}" if (self.__cause__ is not None) else '') + + return f"{self.desc} {self.at}"+(':\n'+\ + ' '*2+'\033[1m'+line[:offset]+'\033[91m'*(self.offset >= 0)+line[offset:]+'\033[0m\n'+\ + ' '*(2+offset)+'\033[95m'+'~'*self.char+'^'+'~'*(self.length-1-self.char)+'\033[0m' if (line) else '') + \ + (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__ if (isinstance(self.__cause__, (SlSyntaxError, SlValidationError, SlCompilationError))) else ' '+str().join(traceback.format_exception(type(self.__cause__), self.__cause__, self.__cause__.__traceback__))}" if (self.__cause__ is not None) else '') @property def at(self): - return f" at line {self.lineno}, offset {self.offset}" + return f"at line {self.lineno}, offset {self.offset}" if (self.offset >= 0) else f"at the end of line {self.lineno}" class SlSyntaxExpectedError(SlSyntaxError): - __slots__ = ('expected', 'found') + expected: ... + found: ... def __init__(self, expected='nothing', found='nothing', *, lineno=None, offset=None, length=0, scope=None): assert (expected != found) @@ -227,14 +244,16 @@ class SlSyntaxExpectedMoreTokensError(SlSyntaxExpectedError): super().__init__(expected=f"More tokens for {for_}", offset=offset, **kwargs) class SlSyntaxMultiExpectedError(SlSyntaxExpectedError): - __slots__ = ('sl', 'errlist') + sl: ... + errlist: ... def __init__(self, expected, found, *, scope=None, errlist=None, **kwargs): self.errlist = errlist self.sl = len(scope)+6 if (scope is not None) else 0 super().__init__( - expected=S(',\n'+' '*(self.sl+9)).join(Stuple((i.expected+f" at offset {i.offset if (i.offset >= 0) else ''}{i.offset+1 if (i.offset < -1) else ''}" if (not isinstance(i, SlSyntaxMultiExpectedError)) else i.expected)+(f' (for {i.usage})' if (i.usage) else '') for i in expected).strip('nothing').uniquize(str.casefold), last=',\n'+' '*(self.sl+6)+'or ') or 'nothing', - found=S(',\n'+' '*(self.sl+6)).join(Stuple(i.found+f" at {f'offset {i.offset}' if (i.offset >= 0) else ''}{i.offset+1 if (i.offset < -1) else ''}" if (not isinstance(i, SlSyntaxMultiExpectedError)) else i.expected for i in found).strip('nothing').uniquize(str.casefold), last=',\n'+' '*(self.sl+2)+'and ') or 'nothing', + expected=S(',\n'+' '*(self.sl+9)).join(Stuple((i.expected+f" at {f'offset {i.offset}' if (i.offset >= 0) else 'the end of line'}" if (not isinstance(i, SlSyntaxMultiExpectedError)) else i.expected)+(f' (for {i.usage})' if (i.usage) else '') for i in expected).strip('nothing').uniquize(str.casefold), last=',\n'+' '*(self.sl+6)+'or ') or 'nothing', + found=S(',\n'+' '*(self.sl+6)).join(Stuple(i.found+f" at {f'offset {i.offset}' if (i.offset >= 0) else 'the end of line'}" if (not isinstance(i, SlSyntaxMultiExpectedError)) else i.expected for i in found).strip('nothing').uniquize(str.casefold), last=',\n'+' '*(self.sl+2)+'and ') or 'nothing', + #{i.offset+1 if (i.offset < -1) else ''} scope=scope, **kwargs ) @@ -253,11 +272,12 @@ class SlSyntaxMultiExpectedError(SlSyntaxExpectedError): i.expected = ' '*loff+S(i.expected).indent(sl+loff).lstrip() i.found = ' '*loff+S(i.found).indent(sl+loff).lstrip() + lineno = max(err, key=operator.attrgetter('lineno')).lineno return cls( expected=sorted(err, key=operator.attrgetter('expected'), reverse=True), found=sorted(err, key=operator.attrgetter('offset')), - lineno=max(err, key=operator.attrgetter('lineno')).lineno, - offset=max(err, key=lambda x: x.offset if (x.offset >= 0) else inf).offset, + lineno=lineno, + offset=max((i for i in err if i.lineno == lineno), key=lambda x: x.offset if (x.offset >= 0) else inf).offset, length=min(i.length for i in err if i.length) if (not any(i.offset < 0 for i in err)) else 0, scope=scope, errlist=list(err),