Huge work; i'm too tired to write commit message for all of it. Pleez let me sleep acouple of hours. Made most tests work. Started SBC.

This commit is contained in:
egormanga 2020-03-18 07:00:12 +03:00
parent 7badd31e42
commit 27d951227d
51 changed files with 3004 additions and 717 deletions

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
old.c
**.db
**.log
**.old
**.pyc
**.old
**.bak
**__pycache__
**_config.py

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "compilers/pyssembly/typeshed"]
path = compilers/pyssembly/typeshed
url = https://github.com/python/typeshed.git

50
SBC/builtins.c Normal file
View File

@ -0,0 +1,50 @@
#include <stdarg.h>
#include <string.h>
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;
}

View File

@ -6,9 +6,22 @@
### NOP
0x00
> Does nothing.
### RET
### 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
@ -44,7 +57,7 @@
0x19
> Pushes integer of smallest possible width equal to rounded `TOS`.
### CTOS
0x1a
0x1A
> Pushes string consisting of char `TOS` and a null byte.
@ -62,7 +75,7 @@
### DIV
0x23
> Pushes `TOS1 / TOS`.
### FLRDIV
### IDIV
0x24
> Pushes `TOS1 // TOS`.
### MOD
@ -71,46 +84,88 @@
### POW
0x26
> Pushes `TOS1 ** TOS`.
### SHL
### LSH
0x27
> Pushes `TOS1 << TOS`.
### SHR
### RSH
0x28
> Pushes `TOS1 >> TOS`.
### AND
0x29
> Pushes `TOS1 & TOS`.
### OR
0x2a
0x2A
> Pushes `TOS1 | TOS`.
### XOR
0x2b
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
0xA0
> Pushes reference to `calloc(1, bytes)`.
### EXTEND*(bytes)*
0xa1
0xA1
> Extends integer `TOS` width to `bytes` bytes if narrower.
### CONST*(bytes)*
0xa2
0xA2
> Reads next `bytes` bytes of bytecode and pushes a reference to a copy of them.
### JUMPF*(offset)*
0xa3
0xA3
> Jumps `offset` bytes of bytecode forward.
### JUMPB*(offset)*
0xa4
0xA4
> Jumps `offset` bytes of bytecode backward.
### SCPGET*(cell)*
0xa5
0xA5
> Pushes the value of cell `cell` of local scope variables array.
### SCPSET*(cell)*
0xa6
0xA6
> Sets the value of cell `cell` of local scope variables array to `TOS`.
### CALL*(nargs)*
0xa7
> Pops `nargs ^ (1 << 7)` TODO
0xA7
> Calls `TOS` with `nargs` arguments popped from stack (below the callable).

50
SBC/dissbc.py Executable file
View File

@ -0,0 +1,50 @@
#!/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='<file.sbc>')
@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

BIN
SBC/sbc Executable file

Binary file not shown.

217
SBC/sbc.c Normal file
View File

@ -0,0 +1,217 @@
// SBC
#define _GNU_SOURCE
#include <ctype.h>
#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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 <file.sbc>\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

53
SBC/stack.c Normal file
View File

@ -0,0 +1,53 @@
/*
#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

Binary file not shown.

View File

@ -18,19 +18,17 @@ char f(str x) { # f() is of type char, x is of type str
return c
}
char g(str x) { # g() is of type char, x is of type str
auto g(str x) { # g() is of type char, x is of type str
return x[0] # retval is of type char
}
int h(int x) = x+1 # h() is of type int, x is of type int
void main() {
print(h(n), \
main {
stdio.println(h(n), \ # comment here too
f('123asd') + g('32') + 1) #--> «123124 f»
print(q/z/2**96) #--> «4294967296.0»
stdio.println(q/z/2**96) #--> «4294967296.0»
}
main()
```
## Tokens
@ -83,6 +81,7 @@ _Note: `*` after syntax unit means any number of them._
* `<exprkeyword> [expr]``keywordexpr` (keyword expression)
* `<typedef> <identifier> [= <value>]``vardef` (variable definition)
* `<identifier> = <value>``assignment`
* `<identifier>[, <identifier>]* = <value>``unpackassignment`
* `<value>([<callargs> | <callkwargs> | <callargs>, <callkwargs>])``funccall` (function call)
* `<expr>` — expr evaluation (only in REPL)
* `if (<expr>) <block>``conditional`
@ -106,7 +105,7 @@ _Note: `*` after syntax unit means any number of them._
Non-empty sequence of alphanumeric characters plus underscore («_»), not starting with a digit character.
Regex: `[_\w]+[_\w\d]*`
Regex: `[_\w][_\w\d]*`
### Data types
@ -133,6 +132,12 @@ _Note: `*` after syntax unit here means any number of them, `+` means at least o
* `<<0<b | o | x>><digit+> | <digit+>.<digit*> | <digit*>.<digit+>>` — number
* `<"<character*>" | '<character*>'>` — string
### Literal structures
* `[ <value>[, <value>]* ]` — list
* `( [type] <value>[, [type] <value>]* )` — tuple
* `{ <<key>: <value>>* }` — map
## Operators
* `<operator> <operand>` — unary operators usage
@ -143,7 +148,7 @@ _Note: `*` after syntax unit here means any number of them, `+` means at least o
A set of pre-defined character operators:
* `!$%&*+-:^~` — unary charset
* `!+-~` — unary charset
* `%&*+-/<=>^|` — binary charset
* `==`, `**`, `//`, `<<`, `>>` — double-char binary operators
@ -167,4 +172,4 @@ A set of pre-defined keyword operators:
All character class checks are performed in current locale.
---
_by Sdore, 2019_
_by Sdore, 2020_

View File

@ -2,12 +2,10 @@
# Slang
from .ast import *
from .repl import *
from .compilers import *
from .compilers.pyssembly import *
from utils.nolog import *; logstart('Slang')
def debug_compile(src, filename='<string>'):
def compile(src, filename='<string>', *, compiler):
try:
#print(f"Source: {{\n{S(src).indent()}\n}}\n")
@ -15,46 +13,36 @@ def debug_compile(src, filename='<string>'):
#print(f"Tokens:\n{pformat(tl)}\n")
ast = build_ast(tl, filename)
#print(f"Code: {repr(ast.code)}\n")
#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: {repr(ast.code)}\n")
#print(f"Optimized: {ast.code}\n")
code = PyssemblyCompiler.compile_ast(ast, validate_ast(ast), filename=filename)
code = compiler.compile_ast(ast, validate_ast(ast), filename=filename)
#print("Compiled.\n")
#print("Running.\n")
#exec(code, {})
#print("\nFinished.\n")
except (SlSyntaxError, SlValidationError, SlCompilationError) as ex:
ex.line = src.split('\n')[ex.lineno-1]
if (not ex.line): ex.line = src.split('\n')[ex.lineno-1]
sys.exit(ex)
except pyssembly.PyssemblyError as ex:
print('Error:', ex)
try: code = ex.code.to_code()
except pyssembly.PyssemblyError: pass
else:
print("\nHere is full pyssembly code 'til the errorneous line:\n")
dis.dis(code)
sys.exit(1)
else: return code
return code
@apmain
@aparg('file', metavar='<file>', type=argparse.FileType('r'))
@aparg('-o', dest='output')
@aparg('-f', dest='compiler', required=True)
def main(cargs):
if (cargs.o is None and not cargs.file.name.rpartition('.')[0]):
argparser.add_argument('-o', metavar='<output>', required=True)
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
code = debug_compile(src, filename=filename.join('""'))
open(cargs.o or cargs.file.name.rpartition('.')[0]+'.pyc', 'wb').write(pyssembly.asm(code))
_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)
open(cargs.output or cargs.file.name.rpartition('.')[0]+compiler.ext, 'wb').write(code)
if (__name__ == '__main__'):
argparser.add_argument('file', metavar='<file>', type=argparse.FileType('r'))
argparser.add_argument('-o', metavar='<output>')
cargs = argparser.parse_args()
logstarted(); exit(main(cargs))
if (__name__ == '__main__'): exit(main())
else: logimported()
# by Sdore, 2019
# by Sdore, 2020

15
TODO.md
View File

@ -1 +1,14 @@
- arrays
- doc `import [[<namespace>:][<path>/]<pkg>:]<name>`
- allow `?` modifier only if ≥2 args ?
- 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
- 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

BIN
asm/hw Executable file

Binary file not shown.

16
asm/hw.asm Normal file
View File

@ -0,0 +1,16 @@
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

1
asm/hw.c Normal file
View File

@ -0,0 +1 @@
main() {puts("hw");}

BIN
asm/hw.o Normal file

Binary file not shown.

797
ast.py

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
#!/usr/bin/python3
# Slang compilers
import abc
from ..ast import SlSyntaxError, SlValidationError
import abc, traceback
class Compiler(abc.ABC):
@abc.abstractclassmethod
@ -23,9 +24,10 @@ class SlCompilationError(Exception):
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) if (line) else '')
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):

View File

@ -0,0 +1,135 @@
#!/usr/bin/python3
# Slang Esolang Gibberish compilation target (PoC)
# https://esolangs.org/wiki/Gibberish_(programming_language)
from .. import *
from Slang.ast import *
from utils import *
class Instrs:
binopmap = {
'+': b'ea',
'-': b'es',
'*': b'em',
'/': b'ed',
'<<': b'fl',
'>>': b'fr',
'&': b'ga',
}
@init_defaults
@autocast
def __init__(self, ns, stack: Slist, functions: dict):
self.ns, self.stack, self.functions = ns, stack, functions
self.code = bytearray()
@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):
if (x.value is not None):
self.load(x.value)
self.stack[-1] = Signature.build(x, self.ns)
@dispatch
def add(self, x: ASTAssignmentNode):
sig = Signature.build(x.name, self.ns)
for ii, i in enumerate(self.stack):
if (i == sig): self.stack[ii] = None
self.load(x.value)
self.stack[-1] = sig
@dispatch
def add(self, x: ASTFunccallNode):
self.load(x)
self.code += b'ev'
self.stack.pop()
@dispatch
def add(self, x: ASTFuncdefNode):
code_ns = self.ns.derive(x.name.identifier)
if (x.name.identifier == 'main'):
assert not x.argdefs
name = x.name.identifier
else: 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)))}"
f_instrs = Instrs(ns=code_ns, stack=(Signature.build(i, code_ns) for i in x.argdefs), functions=self.functions.copy())
f_instrs.add(x.code)
#dlog(f"{x.__fsig__()} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.functions[name] = f_instrs
@dispatch
def add(self, x: ASTKeywordExprNode):
if (x.keyword.keyword == 'return'):
self.load(x.value)
else: raise NotImplementedError(x.keyword)
@dispatch
def load(self, x: ASTLiteralNode):
s = str(eval(str(x.literal))).encode().split(b']')
if (s):
s[0] = b'['+s[0]
s[-1] += b']'
self.code += b'][93]eigtec['.join(s) + b'c'*(len(s)-1)
if (issubclass(literal_type(x.literal), (int, float))): self.code += b'ei'
self.stack.append(None)
@dispatch
def load(self, x: ASTIdentifierNode):
dlog(self.stack)
sig = Signature.build(x, self.ns)
i = self.stack.rindex(sig)
self.code += (b'[%d]eip' if (i >= 10) else b'%dep') % i
self.stack.append(sig)
@dispatch
def load(self, x: ASTValueNode):
self.load(x.value)
@dispatch
def load(self, x: ASTFunccallNode):
assert not (x.callargs.starargs or x.callkwargs.callkwargs or x.callkwargs.starkwargs)
if (x.callable.value.identifier == 'print'):
for i in x.callargs.callargs[:-1]:
self.load(i)
self.code += b'eq[32]eigteq'
self.stack.pop()
if (x.callargs.callargs):
self.load(x.callargs.callargs[-1])
self.stack.pop()
else: self.code += b'[]'
self.code += b'eo'
self.stack.append(None)
return
for i in x.callargs.callargs[::-1]:
self.load(i)
self.stack.pop()
self.code += self.functions[f"{x.callable.value.identifier}__{self.ns.signatures[x.callable.value.identifier].call.index(CallArguments.build(x, self.ns))}" if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures) else x.callable.value.identifier].code#.join((b'{', b'}'))
@dispatch
def load(self, x: ASTBinaryExprNode):
self.load(x.lvalue)
self.load(x.rvalue)
self.code += self.binopmap[x.operator.operator]
self.stack.pop()
self.stack.pop()
self.stack.append(None)
class GibberishCompiler(Compiler):
ext = '.gib'
@classmethod
def compile_ast(cls, ast, ns, *, filename):
instrs = Instrs(ns=ns)
instrs.add(ast)
code = bytes(instrs.code)
dlog("Code:\n"+code.decode())
return code
# by Sdore, 2019

View File

@ -1,278 +0,0 @@
#!/usr/bin/python3
# Slang Pyssembly compiler target
import pyssembly
from . import *
from Slang.ast import *
from utils import *
class Instrs:
unopmap = {
'+': 'POS',
'-': 'NEG',
'!': 'NOT',
'~': 'INV',
'not': 'NOT',
}
binopmap = {
'+': 'ADD',
'-': 'SUB',
'*': 'MUL',
'/': 'DIV',
'//': 'FLOORDIV',
'%': 'MOD',
'**': 'POW',
'<<': 'LSHIFT',
'>>': 'RSHIFT',
'&': 'AND',
'|': 'OR',
'^': 'XOR',
'and': 'AND',
'or': 'OR',
}
@init_defaults
def __init__(self, *, name, ns, filename, argdefs=()):
self.name, self.ns, self.filename = name, ns, filename
self.instrs = list()
self.consts = list()
self.argnames = list()
for i in argdefs:
if (i.modifier is not None): raise SlCompilationError("argument modifiers are not supported yet", i.modifier)
self.argnames.append(i.name.identifier)
self.cellvars = self.argnames.copy()
self.srclnotab = list()
def compile(self):
return pyssembly.Code('\n'.join(self.instrs), name=self.name, filename=self.filename.strip('"'), srclnotab=self.srclnotab, consts=self.consts, argnames=self.argnames)
@dispatch
def add(self, x: ASTRootNode):
self.add(x.code)
@dispatch
def add(self, x: ASTCodeNode):
lastln = int()
for i in x.nodes:
self.srclnotab.append(i.lineno-lastln)
lastln = i.lineno
l = len(self.instrs)
self.add(i)
self.srclnotab += [0]*(len(self.instrs)-l-1)
@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): self.instrs.append(f"LOAD ({x.name})")
self.load(x.value)
if (x.inplace_operator is not None): self.instrs.append(f"INP_{self.binopmap[x.inplace_operator.operator]}")
self.store(x.name)
@dispatch
def add(self, x: ASTFunccallNode):
self.load(x)
self.instrs.append("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)))}"
f_instrs = Instrs(name=f"{self.name}.{name}", ns=code_ns, filename=self.filename, argdefs=x.argdefs)
f_instrs.add(x.code)
#dlog(f"{x.__fsig__()} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('{self.name}.{name}')",
f"MAKE_FUNCTION 0", # TODO: flags
]
self.store(name)
@dispatch
def add(self, x: ASTKeywordExprNode):
if (x.keyword.keyword == 'import'):
ns, _, name = x.value.identifier.partition('::')
assert ns == 'py'
self.instrs += [
"LOAD (0)", # TODO
"LOAD (None)", # TODO
]
self.instrs.append(f"IMPORT_NAME ({name})")
self.store(name)
elif (x.keyword.keyword == 'return'):
self.load(x.value)
self.instrs.append("RET")
else: raise NotImplementedError(x.keyword)
@dispatch
def add(self, x: ASTConditionalNode):
self.load(x.condition)
self.instrs.append("JPOPF :else")
self.add(x.code)
self.instrs += [
"JUMPF :end",
":else",
":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):
self.instrs.append(f"LOAD ({x.literal})")
@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):
if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures):
self.load(f"{x.callable.value.identifier}__{self.ns.signatures[x.callable.value.identifier].call.index(CallArguments.build(x, self.ns))}")
else: self.load(x.callable)
n = int()
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
for i in x.callargs.starargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_TUPLE_UNPACK_WITH_CALL {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):
for i in x.callkwargs.starkwargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_MAP_UNPACK_WITH_CALL {n}")
n = 1
self.instrs.append(f"CALL{'EX' if (x.callargs.starargs or x.callkwargs.starkwargs) else 'KW' if (x.callkwargs.callkwargs) else ''} {n}")
@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.instrs.append(self.unopmap[x.operator.operator])
@dispatch
def load(self, x: ASTBinaryExprNode):
self.load(x.lvalue)
char = isinstance(Signature.build(x.lvalue, self.ns), stdlib.char)
if (char): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
self.load(x.rvalue)
if (char and isinstance(Signature.build(x.rvalue, self.ns), stdlib.char)): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (x.operator.operator == 'to'): self.instrs.append("CALL (range) 2")
else: self.instrs.append(self.binopmap[x.operator.operator])
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (char and x.operator.operator not in keyword_operators): self.instrs.append("CALL (chr) 1")
@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.instrs.append(f"LOAD {f'${x}' if (x in self.cellvars) else f'({x})'}")
@dispatch
def store(self, x: ASTIdentifierNode):
self.store(x.identifier)
@dispatch
def store(self, x: str):
self.instrs.append(f"STORE {f'${x}' if (x in self.cellvars) else f'({x})'}")
class PyssemblyCompiler(Compiler):
@classmethod
def compile_ast(cls, ast, ns, *, filename):
instrs = Instrs(name='<module>', ns=ns, filename=filename)
instrs.add(ast)
#dlog("Instrs:\n"+'\n'.join(instrs.instrs)+'\n')
code = instrs.compile().to_code()
#dis.show_code(code)
#dis.dis(code)
#print()
return code
# by Sdore, 2019

View File

@ -0,0 +1,486 @@
#!/usr/bin/python3
# Slang Pyssembly compiler target
import pyssembly
from . import std
from .. import *
from ...ast import *
from utils import *
class Instrs:
unopmap = {
'+': 'POS',
'-': 'NEG',
'!': 'NOT',
'~': 'INV',
'not': 'NOT',
}
binopmap = {
'+': 'ADD',
'-': 'SUB',
'*': 'MUL',
'/': 'DIV',
'//': 'IDIV',
'%': 'MOD',
'**': 'POW',
'<<': 'LSHIFT',
'>>': 'RSHIFT',
'&': 'AND',
'|': 'OR',
'^': 'XOR',
'and': 'AND',
'or': 'OR',
}
_self_argdef = ASTArgdefNode(Class, ASTIdentifierNode('<self>', lineno=None, offset=None), None, None, lineno=None, offset=None)
def _class_init(self, *args, **kwargs):
getattr(self, '<init>')()
getattr(self, f"<constructor ({', '.join(type(i).__name__ for i in (*args, *kwargs.values()))})>")(*args, **kwargs)
_class_init = _class_init.__code__.replace(co_name='<new>')
@init_defaults
def __init__(self, *, name, ns, filename, argdefs=(), lastnodens: lambda: [None, None], firstlineno=0):
self.name, self.ns, self.filename, self.lastnodens, self.firstlineno = name, ns, filename, lastnodens, firstlineno
self.instrs = list()
self.consts = list()
self.argnames = list()
for i in argdefs:
if (i.modifier is not None): raise NotImplementedError("argument modifiers are not supported yet")
self.argnames.append(i.name.identifier)
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))
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)
@dispatch
def add(self, x: ASTRootNode):
self.add(x.code)
@dispatch
def add(self, x: ASTCodeNode):
for ii, i in enumerate(x.nodes):
assert (i.lineno >= self.lastln)
if (i.lineno != self.lastln): self.instrs.append(f"#line {i.lineno}")
self.lastln = i.lineno
self.lastnodens[0] = i
self.add(i)
@dispatch
def add(self, x: ASTValueNode):
self.add(x.value)
@dispatch
def add(self, x: ASTVardefNode):
typesig = Signature.build(x.type, self.ns)
if (x.value is not None):
self.load(x.value)
self.store(x.name)
elif (isinstance(typesig, Class)):
self.instrs += [
"LOAD (object)",
"GETATTR (__new__)",
]
self.load(x.type.type)
self.instrs += [
"CALL 1",
"DUP",
"GETATTR <<init>>",
"CALL",
"POP",
]
self.store(x.name)
@dispatch
def add(self, x: ASTAssignmentNode):
if (x.inplace_operator is not None): self.instrs.append(f"LOAD ({x.name})")
self.load(x.value)
if (x.inplace_operator is not None): self.instrs.append(f"IP{self.binopmap[x.inplace_operator.operator]}")
if (x.isattr):
self.instrs += [
"LOAD $<self>",
f"SETATTR ({x.name})",
]
else: self.store(x.name)
@dispatch
def add(self, x: ASTUnpackAssignmentNode):
assert (x.inplace_operator is None) # TODO
self.load(x.value)
self.instrs.append(f"UNPACK {len(x.names)}")
for name in x.names:
self.store(name)
@dispatch
def add(self, x: ASTAttrsetNode):
assert (x.assignment.isattr)
if (x.assignment.inplace_operator is not None):
self.load(x.value)
self.instrs += [
"DUP",
"GETATTR ({x.assignment.name})",
]
self.load(x.assignment.value)
self.instrs += [
f"IP{self.binopmap[x.assignment.inplace_operator.operator]}",
"ROT",
]
else:
self.load(x.assignment.value)
self.load(x.value)
self.instrs.append(f"SETATTR ({x.assignment.name})")
@dispatch
def add(self, x: ASTFunccallNode):
self.load(x)
self.instrs.append("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}({CallArguments(args=tuple(Signature.build(i, code_ns) for i in x.argdefs))})"
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)
f_instrs.add(x.code)
#dlog(f"{fname} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.lastnodens[1] = self.ns
self.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('{fname}')",
"MKFUNC 0", # TODO: flags
]
self.store(name)
@dispatch
def add(self, x: ASTClassdefNode):
code_ns = self.ns.derive(x.name.identifier)
name = x.name.identifier
self.lastnodens[1] = code_ns
cname = str(name)
c_instrs = Instrs(name=cname, ns=code_ns, filename=self.filename, lastnodens=self.lastnodens, firstlineno=x.lineno)
c_instrs.consts.append(self._class_init)
c_instrs.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('<new>')",
"MKFUNC 0", # TODO: flags
]
c_instrs.store('__init__')
c_instrs.add(x.code)
self.consts.append(c_instrs.compile().to_code())
self.lastnodens[1] = self.ns
self.instrs += [
"LOAD_BUILD_CLASS",
f"LOAD {len(self.consts)-1}",
f"LOAD ('{cname}')",
"MKFUNC 0", # TODO: flags?
f"LOAD ('{cname}')",
"CALL 2",
]
self.store(name)
@dispatch
def add(self, x: ASTKeywordExprNode):
if (x.keyword.keyword == 'import'):
m = re.fullmatch(r'(?:(?:(\w+):)?(?:([\w./]+)/)?([\w.]+):)?([\w*]+)', x.value.identifier)
assert (m is not None)
namespace, path, pkg, name = m.groups()
if (namespace is None): namespace = 'sl'
if (path is None): path = '.'
if (pkg is None): pkg = name
if (namespace == 'py'):
assert (path == '.')
self.instrs += [
"LOAD (0)", # TODO
"LOAD (())", # TODO
f"IMPORT ({pkg})",
]
if (name == '*'): self.instrs.append("IMPALL")
else:
if (name != pkg): self.instrs.append("IMPFROM")
self.store(name)
elif (namespace == 'sl'):
pkg = pkg.replace('.', '/')
filename = f"{os.path.join(path, pkg)}.sl"
src = open(filename, 'r').read()
tl = parse_string(src)
ast = build_ast(tl, filename)
optimize_ast(ast, validate_ast(ast))
instrs = Instrs(name='<module>', ns=validate_ast(ast), filename=filename)
instrs.add(ast)
code = instrs.compile().to_code()
# TODO
else: raise WTFException(namespace)
elif (x.keyword.keyword == 'return'):
self.load(x.value)
self.instrs.append("RET")
elif (x.keyword.keyword == 'delete'):
self.delete(x.value)
elif (x.keyword.keyword == 'break'):
self.instrs.append("JF :end")
else: raise NotImplementedError(x.keyword)
@dispatch
def add(self, x: ASTKeywordDefNode):
name = x.name.identifier
if (x.keyword.keyword == 'main'):
code_ns = self.ns.derive(name)
self.lastnodens[1] = code_ns
f_instrs = Instrs(name=name, ns=code_ns, filename=self.filename, lastnodens=self.lastnodens, firstlineno=x.lineno)
f_instrs.add(x.code)
#dlog(f"{name} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.lastnodens[1] = self.ns
self.instrs += [
"LOAD (__name__)",
"LOAD ('__main__')",
"CMP (==)",
"JFP :nomain",
f"LOAD {len(self.consts)-1}",
f"LOAD ('{name}')",
"MKFUNC 0",
"CALL 0",
"POP",
":nomain",
]
elif (x.keyword.keyword == 'init'):
code_ns = self.ns.derive(name)
self.lastnodens[1] = code_ns
f_instrs = Instrs(name=name, argdefs=(self._self_argdef,), ns=code_ns, filename=self.filename, lastnodens=self.lastnodens, firstlineno=x.lineno)
f_instrs.add(x.code)
#dlog(f"{name} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.lastnodens[1] = self.ns
self.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('{name}')",
"MKFUNC 0",
]
self.store(name)
elif (x.keyword.keyword == 'constr'):
code_ns = self.ns.derive(name)
self.ns.define(x, redefine=True)
self.lastnodens[1] = code_ns
name = f"<constructor ({S(', ').join(i.type for i in x.argdefs)})>"
f_instrs = Instrs(name=name, ns=code_ns, filename=self.filename, argdefs=(self._self_argdef, *x.argdefs), lastnodens=self.lastnodens, firstlineno=x.lineno)
f_instrs.add(x.code)
#dlog(f"{name} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.lastnodens[1] = self.ns
self.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('{name}')",
"MKFUNC 0", # TODO: flags
]
self.store(name)
else: raise NotImplementedError(x.keyword)
@dispatch
def add(self, x: ASTConditionalNode):
self.load(x.condition)
self.instrs.append("JFP :else")
self.add(x.code)
self.instrs += [
"JF :end",
":else",
":end",
]
@dispatch
def add(self, x: ASTForLoopNode):
self.load(x.iterable)
#self.cellvars.append(x.name.identifier) # TODO FIXME
self.instrs += [
"ITER",
":for",
"FOR :else",
]
self.store(x.name)
self.add(x.code) # TODO: stack effect (py38)
self.instrs += [
"JA :for",
":else",
":end",
]
@dispatch
def add(self, x: ASTWhileLoopNode):
self.instrs += [
":while",
]
self.load(x.condition)
self.instrs.append("JFP :else")
self.add(x.code) # TODO: stack effect (py38)
self.instrs += [
"JA :while",
":else",
":end",
]
@dispatch
def add(self, x: ASTElseClauseNode):
ii = -1 - self.instrs[-1].startswith('#line')
assert (self.instrs.pop(ii) == ":end")
self.add(x.code) # TODO: stack effect (py38)
self.instrs.append(":end")
@dispatch
def load(self, x: ASTLiteralNode):
self.instrs.append(f"LOAD ({x.literal})")
@dispatch
def load(self, x: ASTIdentifierNode):
self.load(x.identifier)
@dispatch
def load(self, x: ASTListNode):
for i in x.values:
self.load(i)
self.instrs.append(f"BUILD_LIST {len(x.values)}")
@dispatch
def load(self, x: ASTTupleNode):
for i in x.values:
self.load(i)
self.instrs.append(f"BUILD_TUPLE {len(x.values)}")
@dispatch
def load(self, x: ASTValueNode):
self.load(x.value)
@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)
n = int()
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
for i in x.callargs.starargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_TUPLE_UNPACK_WITH_CALL {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):
for i in x.callkwargs.starkwargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_MAP_UNPACK_WITH_CALL {n}")
n = 1
self.instrs.append(f"CALL{'EX' if (x.callargs.starargs or x.callkwargs.starkwargs) else 'KW' if (x.callkwargs.callkwargs) else ''} {n}")
@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.instrs.append(self.unopmap[x.operator.operator])
@dispatch
def load(self, x: ASTBinaryExprNode):
self.load(x.lvalue)
char = isinstance(Signature.build(x.lvalue, self.ns), stdlib.char)
if (char): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
self.load(x.rvalue)
if (char and isinstance(Signature.build(x.rvalue, self.ns), stdlib.char)): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (x.operator.operator == 'to'): self.instrs.append("CALL (range) 2")
else: self.instrs.append(f"CMP ({x.operator.operator})" if (x.operator.operator in dis.cmp_op) else self.binopmap[x.operator.operator])
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (char and x.operator.operator not in logical_operators): self.instrs.append("CALL (chr) 1")
@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.instrs.append(f"LOAD {f'${x}' if (x in self.cellvars) else f'<{x}>'}")
@dispatch
def load(self, x):
self.instrs.append(f"LOAD_CONST ({repr(x)})")
@dispatch
def store(self, x: ASTIdentifierNode):
self.store(x.identifier)
@dispatch
def store(self, x: str):
self.instrs.append(f"STORE {f'${x}' if (x in self.cellvars) else f'<{x}>'}")
@dispatch
def delete(self, x: ASTIdentifierNode):
self.delete(x.identifier)
@dispatch
def delete(self, x: str):
self.instrs.append(f"DELETE {f'${x}' if (x in self.cellvars) else f'<{x}>'}")
class PyssemblyCompiler(Compiler):
ext = '.pyc'
@classmethod
def compile_ast(cls, ast, ns, *, filename):
instrs = Instrs(name='<module>', ns=ns, filename=filename)
instrs.consts.append(compile(open(std.__file__).read(), '<std>', 'exec'))
instrs.instrs += [
f"LOAD {len(instrs.consts)-1}",
"LOAD ('<std>')",
"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
#dlog("Instrs:\n"+'\n'.join(instrs.instrs)+'\n')
try:
code = instrs.compile().to_code()
#dis.show_code(code); dis.dis(code); print()
code = pyssembly.asm(code)
except pyssembly.PyssemblyError as ex:
print('Error:', ex)
try: code = ex.code.to_code()
except pyssembly.PyssemblyError: pass
else:
print("\nHere is full pyssembly code 'til the errorneous line:\n")
dis.dis(code)
raise SlCompilationError('Pyssembly error', ast, scope=instrs.ns) from ex
return code
# by Sdore, 2020

View File

@ -0,0 +1,2 @@
class stdio:
println = print

@ -0,0 +1 @@
Subproject commit b44cd294c4f6cdb66fdd6c13aebabb10855e7dc2

View File

@ -6,7 +6,11 @@ from Slang.ast import *
from utils import *
NOP = 0x00
RET = 0x01
END = 0x01
POP = 0x02
RET = 0x03
BLTIN = 0x04
CODE = 0x05
POS = 0x10
NEG = 0x11
@ -24,15 +28,28 @@ ADD = 0x20
SUB = 0x21
MUL = 0x22
DIV = 0x23
FLRDIV = 0x24
IDIV = 0x24
MOD = 0x25
POW = 0x26
SHL = 0x27
SHR = 0x28
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
@ -40,26 +57,81 @@ 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, argdefs=()):
self.ns = ns
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):
raise TODO
return bytes(self.instrs)
@dispatch
def add(self, opcode: int, oparg: int = None):
def add(self, opcode: lambda x: isinstance(x, int) and x < HASARG):
self.instrs.append(opcode)
if (opcode >= HASARG): self.instrs.append(oparg)
else: assert oparg is None
@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):
@ -67,7 +139,6 @@ class Instrs:
@dispatch
def add(self, x: ASTCodeNode):
lastln = int()
for i in x.nodes:
self.add(i)
@ -83,15 +154,14 @@ class Instrs:
@dispatch
def add(self, x: ASTAssignmentNode):
if (x.inplace_operator is not None): self.instrs.append(f"LOAD ({x.name})")
if (x.inplace_operator is not None): raise NotImplementedError()
self.load(x.value)
if (x.inplace_operator is not None): self.instrs.append(f"INP_{self.binopmap[x.inplace_operator.operator]}")
self.store(x.name)
@dispatch
def add(self, x: ASTFunccallNode):
self.load(x)
self.instrs.append("POP")
self.add(POP)
@dispatch
def add(self, x: ASTBlockNode):
@ -101,46 +171,52 @@ class Instrs:
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)))}"
f_instrs = Instrs(name=f"{self.name}.{name}", ns=code_ns, filename=self.filename, argdefs=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)
#dlog(f"{x.__fsig__()} instrs:\n"+'\n'.join(f_instrs.instrs)+'\n')
self.consts.append(f_instrs.compile().to_code())
self.instrs += [
f"LOAD {len(self.consts)-1}",
f"LOAD ('{self.name}.{name}')",
f"MAKE_FUNCTION 0", # TODO: flags
]
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('::')
assert ns == 'py'
self.instrs += [
"LOAD (0)", # TODO
"LOAD (None)", # TODO
]
self.instrs.append(f"IMPORT_NAME ({name})")
raise TODO
self.store(name)
elif (x.keyword.keyword == 'return'):
self.load(x.value)
self.instrs.append("RET")
self.add(RET)
else: raise NotImplementedError(x.keyword)
@dispatch
def add(self, x: ASTKeywordDefNode):
if (x.keyword.keyword == 'main'):
name = '<main>'
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.instrs.append("JPOPF :else")
self.add(IF)
self.add(x.code)
self.instrs += [
"JUMPF :end",
":else",
":end",
]
self.add(END)
@dispatch
def add(self, x: ASTForLoopNode):
#@dispatch
def add_(self, x: ASTForLoopNode):
self.load(x.iterable)
#self.cellvars.append(x.name.identifier) # TODO FIXME
self.instrs += [
@ -158,8 +234,8 @@ class Instrs:
":end",
]
@dispatch
def add(self, x: ASTWhileLoopNode):
#@dispatch
def add_(self, x: ASTWhileLoopNode):
self.instrs += [
"SETUP_LOOP :end",
":while",
@ -174,8 +250,8 @@ class Instrs:
":end",
]
@dispatch
def add(self, x: ASTElseClauseNode):
#@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())
@ -184,7 +260,16 @@ class Instrs:
@dispatch
def load(self, x: ASTLiteralNode):
self.instrs.append(f"LOAD ({x.literal})")
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):
@ -196,42 +281,29 @@ 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):
self.load(f"{x.callable.value.identifier}__{self.ns.signatures[x.callable.value.identifier].call.index(CallArguments.build(x, self.ns))}")
else: self.load(x.callable)
n = int()
nargs = int()
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
for i in x.callargs.starargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_TUPLE_UNPACK_WITH_CALL {n}")
n = 0
nargs += 1
if (x.callargs.starargs): raise NotImplementedError()
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):
for i in x.callkwargs.starkwargs:
self.load(i)
n += 1
self.instrs.append(f"BUILD_MAP_UNPACK_WITH_CALL {n}")
n = 1
if (x.callkwargs.callkwargs): raise NotImplementedError()
if (x.callkwargs.starkwargs): raise NotImplementedError()
self.instrs.append(f"CALL{'EX' if (x.callargs.starargs or x.callkwargs.starkwargs) else 'KW' if (x.callkwargs.callkwargs) else ''} {n}")
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):
#@dispatch
def load_(self, x: ASTAttrgetNode):
self.load(x.value)
assert x.optype.special == '.' # TODO
self.instrs.append(f"GETATTR ({x.attr})")
@ -239,31 +311,24 @@ class Instrs:
@dispatch
def load(self, x: ASTUnaryExprNode):
self.load(x.value)
self.instrs.append(self.unopmap[x.operator.operator])
self.add(self.unopmap[x.operator.operator])
@dispatch
def load(self, x: ASTBinaryExprNode):
self.load(x.lvalue)
char = isinstance(Signature.build(x.lvalue, self.ns), stdlib.char)
if (char): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
self.load(x.rvalue)
if (char and isinstance(Signature.build(x.rvalue, self.ns), stdlib.char)): self.instrs.append("CALL (ord) 1")
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (x.operator.operator == 'to'): self.instrs.append("CALL (range) 2")
else: self.instrs.append(self.binopmap[x.operator.operator])
if (x.operator.operator == 'xor'): self.instrs.append("BOOL")
if (char and x.operator.operator not in keyword_operators): self.instrs.append("CALL (chr) 1")
if (x.operator.operator == 'to'): raise NotImplementedError()
else: self.add(self.binopmap[x.operator.operator])
@dispatch
def load(self, x: ASTItemgetNode):
#@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.instrs.append(f"LOAD {f'${x}' if (x in self.cellvars) else f'({x})'}")
self.add(SCPGET, self.scpcells[x])
@dispatch
def store(self, x: ASTIdentifierNode):
@ -271,18 +336,16 @@ class Instrs:
@dispatch
def store(self, x: str):
self.instrs.append(f"STORE {f'${x}' if (x in self.cellvars) else f'({x})'}")
self.add(SCPSET, self.scpcells[x])
class SBCCompiler(Compiler):
ext = '.sbc'
class PyssemblyCompiler(Compiler):
@classmethod
def compile_ast(cls, ast, ns, *, filename):
instrs = Instrs(name='<module>', ns=ns, filename=filename)
instrs.add(ast)
#dlog("Instrs:\n"+'\n'.join(instrs.instrs)+'\n')
code = instrs.compile().to_code()
#dis.show_code(code)
#dis.dis(code)
#print()
code = instrs.compile()
return code
# by Sdore, 2019

View File

@ -13,21 +13,30 @@ def read_token(src, *, lineno, offset, lineoff):
if (isinstance(r, int) and r <= 0): length = max(length, -r); continue
n, s = r if (isinstance(r, tuple)) else (r, src[:r])
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+l)
def parse_expr(src, *, lineno=1, lineoff=0):
r = list()
lines = src.count('\n')
offset = int()
continueln = False
while (True):
offset, tok = read_token(src, lineno=lineno, offset=offset, lineoff=lineoff)
if (tok is None): break
offset, tok = read_token(src, lineno=lines-src[offset:].count('\n')+lineno, offset=offset, lineoff=lineoff)
if (tok is None):
if (not continueln): break
continueln = False
offset += 1
lineoff = -offset
continue
elif (continueln and tok.token[0] != '#'): raise SlSyntaxError("Expected newline or comment after line continuation", src, lineno=lines-src[offset:].count('\n')+lineno, offset=tok.offset, length=tok.length)
r.append(tok)
if (tok.token[0] != '#'): continueln = (tok.token == '\\' and tok.offset)
return offset, r
def parse_string(src):
def parse_string(src, lnooff=0):
src = src.rstrip()
tl = list()
lines = src.count('\n')
lines = src.count('\n')+lnooff
lineoff = int()
while (src):
offset, r = parse_expr(src, lineno=lines-src.count('\n')+1, lineoff=lineoff)
@ -39,4 +48,4 @@ def parse_string(src):
tl.append(r)
return tl
# by Sdore, 2019
# by Sdore, 2020

179
repl.py
View File

@ -4,13 +4,15 @@
import readline
from .ast import *
from .lexer import *
from utils import *
from utils.nolog import *
@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 is not None):
if (r not in (None, ...)):
ns.values['_'] = r
print(repr(r))
else: return
@ -20,13 +22,17 @@ def execute_node(node: ASTCodeNode, ns):
def execute_node(node: ASTVardefNode, ns):
ns.values[node.name] = node.value
@dispatch
def execute_node(node: ASTBlockNode, ns):
return execute_node(node.code, ns)
@dispatch
def execute_node(node: ASTFuncdefNode, ns):
ns.values[node.name.identifier] = (node.argdefs, node.code)
@dispatch
def execute_node(node: ASTAssignmentNode, ns):
ns.values[node.name] = node.value
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
@dispatch
def execute_node(node: ASTFunccallNode, ns):
@ -42,48 +48,161 @@ def execute_node(node: ASTValueNode, ns):
@dispatch
def execute_node(node: ASTIdentifierNode, ns):
return ns.values[node]
try: return ns.values[node]
except KeyError: raise SlValidationError(f"{node} is not initialized", node, scope=ns.scope)
@dispatch
def execute_node(node: ASTLiteralNode, ns):
return eval(str(node.literal))
return eval(node.literal) if (isinstance(node.literal, str)) else node.literal
@dispatch
def execute_node(node: ASTListNode, ns):
return node.values
@dispatch
def execute_node(node: ASTKeywordExprNode, ns):
if (node.keyword.keyword == 'return'): return execute_node(node.value, ns)
raise NotImplementedError(node.keyword)
#if (node.keyword.keyword == 'return'): return execute_node(node.value, ns) # TODO FIXME???
#el
if (node.keyword.keyword == 'delete'): ns.delete(node.value)
else: raise NotImplementedError(node.keyword)
@dispatch
def execute_node(node: ASTConditionalNode, ns):
if (execute_node(node.condition, ns)):
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTForLoopNode, ns):
for i in execute_node(node.iterable, ns):
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTUnaryExprNode, ns):
return eval(f"{node.operator.operator} {execute_node(node.value, ns)}")
value = execute_node(node.value, ns)
return eval(f"{node.operator.operator} value")
@dispatch
def execute_node(node: ASTBinaryExprNode, ns):
return eval(f"{execute_node(node.lvalue, ns)} {node.operator.operator} {execute_node(node.rvalue, 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")
class Completer:
def __init__(self, namespace):
self.namespace = namespace
def complete(self, text, state):
if (state == 0):
if ('.' in text): self.matches = self.attr_matches(text)
else: self.matches = self.global_matches(text)
try: return self.matches[state]
except IndexError: return None
def _callable_postfix(self, val, word):
if (isinstance(val, ASTCallableNode)): word += '('
return word
def global_matches(self, text):
matches = list()
seen = set()
n = len(text)
for word in keywords:
if (word[:n] != text): continue
seen.add(word)
matches.append(word+' ')
for word, val in self.namespace.values.items():
if (word[:n] != text or word in seen): continue
seen.add(word)
matches.append(self._callable_postfix(val, word))
return matches
def attr_matches(self, text):
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
if (m is None): return ()
expr, attr = m.group(1, 3)
try: obj = self.namespace.values[expr] # TODO FIXME
except KeyError: return ()
words = set() # TODO FIXME attrs
matches = list()
n = len(attr)
if (attr == ''): noprefix = '_'
elif (attr == '_'): noprefix = '__'
else: noprefix = None
while (True):
for word in words:
if (word[:n] != attr or (noprefix and word[:n+1] == noprefix)): continue
match = f"{expr}.{word}"
try: val = getattr(obj, word)
except Exception: pass # Include even if attribute not set
else: match = self._callable_postfix(val, match)
matches.append(match)
if (matches or not noprefix): break
if (noprefix == '_'): noprefix = '__'
else: noprefix = None
matches.sort()
return matches
def repl():
ns = Namespace('<repl>')
#ns.values['print'] = print
ns.define(ASTIdentifierNode('_', lineno=None, offset=None), stdlib.Any())
completer = Completer(ns)
histfile = os.path.expanduser('~/.sli_history')
try: readline.read_history_file(histfile)
except FileNotFoundError: pass
for i in (
'set colored-completion-prefix on',
'set enable-bracketed-paste on',
#'set horizontal-scroll-mode on',
'set skip-completed-text on',
'tab: complete',
): readline.parse_and_bind(i)
readline.set_completer(completer.complete)
#readline.set_completion_display_matches_hook(completer.display) # TODO
l = list()
tl = list()
while (True):
try:
l.append(input(f"\1\033[1;93m\2{'...' if (tl) else '>>>'}\1\033[0m\2 "))
tll = parse_string(l[-1])
if (not tll): continue
tl += tll
if (tl[-1][-1].token == '\\'): continue
if (len(tl) >= 2 and tl[-2][-1].token == '\\'): tl[-1] = tl[-2][:-1]+tl.pop()
if (tl[0][-1].token == '{' and tl[-1][-1].token != '}'): continue
ast = build_ast(tl, interactive=True)
validate_ast(ast, ns)
optimize_ast(ast, ns)
execute_node(ast.code, ns)
except (EOFError, KeyboardInterrupt): break
except (SlSyntaxError, SlValidationError) as ex:
ex.line = l[ex.lineno-1]
print(ex)
tl.clear()
l.clear()
# by Sdore, 2019
try:
while (True):
try:
l.append(input(f"\1\033[1;93m\2{'...' if (tl) else '>>>'}\1\033[0m\2 "))
tll = parse_string(l[-1], lnooff=len(l)-1)
if (not tll): l.pop(); continue
tl += tll
if (tl[-1][-1].token == '\\'): continue
#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)
validate_ast(ast, ns)
optimize_ast(ast, ns)
execute_node(ast.code, ns)
except KeyboardInterrupt:
buf = readline.get_line_buffer()
print(f"\r\033[2m^C{'> '+buf if (buf) else ' '}\033[0m")
except EOFError:
print(end='\r\033[K')
break
except (SlSyntaxError, SlValidationError) as ex:
ex.line = l[ex.lineno-1]
print(ex)
tl.clear()
l.clear()
finally: readline.write_history_file(histfile)
if (__name__ == '__main__'): repl()
# by Sdore, 2020

17
sl/Slang/Slang.sl Normal file
View File

@ -0,0 +1,17 @@
# Slang
import lexer:*
str src = "main {print('hello');}"
main {
stdio.println("Source: {"+src+"}\n")
list tl = parse_string(src)
stdio.println(tl)
stdio.println("Tokens:")
stdio.println(tl)
stdio.println("\n")
}
# by Sdore, 2020

74
sl/Slang/lexer.sl Normal file
View File

@ -0,0 +1,74 @@
# Slang lexer
import tokens:*
tuple read_token(str src, int lineno, int offset, int lineoff) {
tuple c = lstripcount(src#|[offset:]|#, whitespace)
int l = c[0]
src = c[1]
str line = src
offset += l
if not src or src[0] in '\n;' {
return (offset, None)
}
int length = 0
int 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])
}
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)
return (0, Token())
}
tuple parse_expr(str src, int lineno = 1, int lineoff = 0) {
list r = [Token]
int offset
while 1 {
offset, tok = read_token(src, lineno=lineno, offset=offset, lineoff=lineoff)
if tok is None {
break
}
r.append(tok)
}
return (offset, r)
}
list parse_string(str src) {
src = src.rstrip()
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
}
}
src = src[offset+1:]
tl.append(r)
}
return tl
}
# by Sdore, 2020

49
sl/Slang/tokens.sl Normal file
View File

@ -0,0 +1,49 @@
# Slang tokens
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:]|#)
}
class Token {
int type
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) {
#.type, .token, .lineno, .offset = type, token, lineno, offset
}
#|repr {
return "<Token {.types[.type]} «{repr(.token)[1:-1]}» at line {.lineno}, offset {.offset}>"
}|#
#|eq {
#return super() == x or .token == x
}|#
#|property typename {
#return .types[.type]
}
property length {
#return .token.length
}|#
}
# by Sdore, 2020

288
slang.lang Normal file
View File

@ -0,0 +1,288 @@
<?xml version="1.0" encoding="UTF-8"?>
<language id="slang" name="Slang" version="2.0" _section="Script">
<metadata>
<property name="mimetypes">text/x-slang;application/x-slang</property>
<property name="globs">*.sl</property>
<property name="line-comment-start">#</property>
<property name="block-comment-start">#|</property>
<property name="block-comment-start">|#</property>
</metadata>
<styles>
<style id="comment" name="Comment" map-to="def:comment"/>
<style id="keyword" name="Keyword" map-to="def:keyword"/>
<style id="reserved" name="Reserved Keyword" map-to="def:reserved"/>
<style id="string" name="String" map-to="def:string"/>
<style id="multiline-string" name="Multi-line String" map-to="def:string"/>
<style id="character" name="Character" map-to="def:character"/>
<style id="escaped-char" name="Escaped Character" map-to="def:special-char"/>
<style id="boolean" name="Boolean" map-to="def:boolean"/>
<style id="floating-point" name="Floating point number" map-to="def:floating-point"/>
<style id="decimal" name="Decimal number" map-to="def:decimal"/>
<style id="base-n-integer" name="Base-N number" map-to="def:base-n-integer"/>
<style id="complex" name="Complex number" map-to="def:complex"/>
<style id="builtin-constant" name="Builtin Constant" map-to="def:special-constant"/>
<style id="builtin-object" name="Builtin Object" map-to="def:function"/>
<style id="builtin-type" name="Builtin Type" map-to="def:type"/>
<style id="function-name" name="Function Name" map-to="def:function"/>
<style id="class-name" name="Class Name" map-to="def:function"/>
<style id="decorator" name="Decorator" map-to="def:preprocessor"/>
</styles>
<definitions>
<define-regex id="identifier">[_\w][_\w\d]*</define-regex>
<define-regex id="number">[1-9][0-9]*</define-regex>
<define-regex id="identifier-path" extended="true">
(\%{identifier}\.)*\%{identifier}
</define-regex>
<define-regex id="relative-path" extended="true">
(\.*\%{identifier-path}|\.+)
</define-regex>
<context id="comment" style-ref="comment" end-at-line-end="true" class-disabled="no-spell-check" class="comment">
<start>#</start>
<include>
<context ref="def:in-line-comment"/>
</include>
</context>
<context id="multiline-comment" style-ref="comment" class-disabled="no-spell-check" class="comment">
<start>#\|</start>
<end>\|#</end>
<include>
<context ref="def:in-comment"/>
</include>
</context>
<context id="close-comment-outside-comment" style-ref="error">
<match>\|#(?!#\|)</match>
</context>
<context id="escaped-char" style-ref="escaped-char" extend-parent="true">
<match extended="true">
\\( # leading backslash
[\\'"abfnrtv] | # single escaped char
N\{[0-9A-Z\ -]+\} | # named unicode character
u[0-9A-Fa-f]{4} | # xxxx - character with 16-bit hex value xxxx
U[0-9A-Fa-f]{8} | # xxxxxxxx - character with 32-bit hex value xxxxxxxx
x[0-9A-Fa-f]{1,2} | # \xhh - character with hex value hh
[0-7]{1,3} # \ooo - character with octal value ooo
)
</match>
</context>
<context id="multiline-double-quoted-string" style-ref="string" class="string" class-disabled="no-spell-check">
<start>"""</start>
<end>"""</end>
<include>
<context ref="escaped-char"/>
</include>
</context>
<context id="multiline-single-quoted-string" style-ref="multiline-string" class="string" class-disabled="no-spell-check">
<start>'''</start>
<end>'''</end>
<include>
<context ref="escaped-char"/>
</include>
</context>
<context id="double-quoted-string" style-ref="string" end-at-line-end="true" class="string" class-disabled="no-spell-check">
<start>"</start>
<end>"</end>
<include>
<context ref="escaped-char"/>
<context ref="def:line-continue"/>
</include>
</context>
<context id="single-quoted-string" style-ref="string" end-at-line-end="true" class="string" class-disabled="no-spell-check">
<start>'</start>
<end>'</end>
<include>
<context ref="escaped-char"/>
<context ref="def:line-continue"/>
</include>
</context>
<context id="char" style-ref="character" class="string" class-disabled="no-spell-check">
<match extended="true">'([^'])'</match>
</context>
<context id="boolean" style-ref="boolean">
<prefix>(?&lt;![\w\.])</prefix>
<keyword>false</keyword>
<keyword>true</keyword>
</context>
<define-regex id="float" extended="true">
( (\d+)?\.\d+ | \d+\. ) |
( (\d+|(\d+)?\.\d+|\d+\.)[eE][+-]?\d+ )
</define-regex>
<context id="complex" style-ref="complex">
<match>(?&lt;![\w\.])(\%{float}|\d+)[jJ]\b</match>
</context>
<context id="float" style-ref="floating-point">
<match>(?&lt;![\w\.])\%{float}(?![\w\.])</match>
</context>
<context id="function-definition">
</context>
<context id="class-definition">
<match extended="true">
(class)
\s+
(\%{identifier})
</match>
<include>
<context sub-pattern="1" style-ref="keyword"/>
<context sub-pattern="2" style-ref="class-name"/>
</include>
</context>
<context id="decorator" style-ref="decorator">
<match>@\%{identifier-path}</match>
</context>
<context id="keywords" style-ref="keyword">
<keyword>if</keyword>
<keyword>for</keyword>
<keyword>in</keyword>
<keyword>while</keyword>
<keyword>else</keyword>
<keyword>class</keyword>
<keyword>return</keyword>
<keyword>break</keyword>
<keyword>continue</keyword>
<keyword>import</keyword>
<keyword>delete</keyword>
<keyword>assert</keyword>
<keyword>breakpoint</keyword>
<keyword>main</keyword>
<keyword>exit</keyword>
<keyword>init</keyword>
<keyword>constr</keyword>
<keyword>repr</keyword>
<keyword>eq</keyword>
<keyword>const</keyword>
<keyword>static</keyword>
<keyword>volatile</keyword>
<keyword>property</keyword>
<keyword>is</keyword>
<keyword>in</keyword>
<keyword>not</keyword>
<keyword>and</keyword>
<keyword>but</keyword>
<keyword>xor</keyword>
<keyword>or</keyword>
<keyword>isof</keyword>
<keyword>to</keyword>
</context>
<context id="reserved-keywords" style-ref="reserved">
<keyword>def</keyword>
<keyword>try</keyword>
<keyword>catch</keyword>
<keyword>except</keyword>
<keyword>finally</keyword>
<keyword>raise</keyword>
<keyword>with</keyword>
<keyword>yield</keyword>
</context>
<context id="builtin-constants" style-ref="builtin-constant">
<prefix>(?&lt;![\w\.])</prefix>
<keyword>none</keyword>
<keyword>null</keyword>
</context>
<context id="builtin-objects" style-ref="builtin-object">
<prefix>(?&lt;![\w\.])</prefix>
<keyword>stdio</keyword>
<keyword>globals</keyword>
</context>
<context id="builtin-types" style-ref="builtin-type">
<prefix>(?&lt;![\w\.])</prefix>
<keyword>i8</keyword>
<keyword>i16</keyword>
<keyword>i32</keyword>
<keyword>i64</keyword>
<keyword>i128</keyword>
<keyword>u8</keyword>
<keyword>u16</keyword>
<keyword>u32</keyword>
<keyword>u64</keyword>
<keyword>u128</keyword>
<keyword>f8</keyword>
<keyword>f16</keyword>
<keyword>f32</keyword>
<keyword>f64</keyword>
<keyword>f128</keyword>
<keyword>uf8</keyword>
<keyword>uf16</keyword>
<keyword>uf32</keyword>
<keyword>uf64</keyword>
<keyword>uf128</keyword>
<keyword>int</keyword>
<keyword>uint</keyword>
<keyword>float</keyword>
<keyword>ufloat</keyword>
<keyword>bool</keyword>
<keyword>byte</keyword>
<keyword>char</keyword>
<keyword>str</keyword>
<keyword>void</keyword>
<keyword>auto</keyword>
<keyword>dict</keyword>
<keyword>list</keyword>
<keyword>map</keyword>
<keyword>object</keyword>
<keyword>range</keyword>
<keyword>set</keyword>
<keyword>tuple</keyword>
</context>
<context id="slang" class="no-spell-check">
<include>
<context ref="def:shebang"/>
<context ref="close-comment-outside-comment" style-ref="def:error"/>
<context ref="multiline-comment" style-ref="def:comment"/>
<context ref="comment" style-ref="def:comment"/>
<context ref="multiline-double-quoted-string"/>
<context ref="multiline-single-quoted-string"/>
<context ref="double-quoted-string"/>
<context ref="single-quoted-string"/>
<context ref="char"/>
<context ref="boolean"/>
<context ref="complex"/>
<context ref="float"/>
<context ref="def:decimal"/>
<context ref="def:octal"/>
<context ref="def:hexadecimal"/>
<context ref="function-definition"/>
<context ref="class-definition"/>
<context ref="decorator"/>
<context ref="keywords"/>
<context ref="reserved-keywords"/>
<context ref="builtin-constants"/>
<context ref="builtin-objects"/>
<context ref="builtin-types"/>
</include>
</context>
</definitions>
</language>

9
sld.py Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/python3
# Slang .sld parser
# .sld files are used for definition of built-in, implementation-provided, external and native components.
from .tokens import *
from utils import *
# by Sdore, 2020

171
stdlib.py
View File

@ -1,7 +1,7 @@
#!/usr/bin/python3
# Slang stdlib
from .ast import Signature, Function, Object
from .ast import Signature, Function, Object, Collection, CallArguments
from .tokens import *
from utils import *
@ -9,15 +9,25 @@ class Builtin(Signature):
def __init__(self):
pass
@classitemget
def attrops(cls, optype, attr):
if (optype == '.'):
try: return getattr(cls, attr)()
except AttributeError: raise KeyError()
raise KeyError()
@property
def __reprname__(self):
return type(self).mro()[1].__name__
return first(i.__name__ for i in self.__class__.mro() if i.__name__.startswith('Builtin'))
@property
def typename(self):
return type(self).__name__
return self.__class__.__name__
class BuiltinFunction(Builtin, Function): pass
class BuiltinFunction(Builtin, Function):
@property
def name(self):
return self.__class__.__name__
class BuiltinObject(Builtin, Object): pass
@ -27,84 +37,173 @@ class BuiltinType(Builtin):
def __init__(self, *, modifiers: paramset):
self.modifiers = modifiers
class void(BuiltinType): pass
def __eq__(self, x):
return (super().__eq__(x) or issubclass(x.__class__, self.__class__) or issubclass(self.__class__, x.__class__))
@staticitemget
@instantiate
def operators(op, valsig=None, selftype=None):
if (valsig is None):
if (op in operators[9]): return bool # unary `not'
else:
assert (selftype is not None)
if (isinstance(valsig, selftype) and op in operators[8]): return bool # comparisons
if (op in operators[10]+operators[11]+operators[12]): return type(valsig) # binary `and'. `xor', `or'
raise KeyError()
class Any(BuiltinType):
def __eq__(self, x):
return True
class void(BuiltinType):
@staticitemget
def operators(op, valsig=None):
raise KeyError()
class bool(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
try: return BuiltinType.operators(op, valsig=valsig, selftype=bool)
except KeyError: pass
if (valsig is None):
if (op in map(UnaryOperator, '+-~')): return int
if (op in map(UnaryOperator, ('not', '!'))): return bool
if (op == UnaryOperator('!')): return bool
raise KeyError()
class int(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
try: return BuiltinType.operators(op, valsig=valsig, selftype=int)
except KeyError: pass
if (valsig is None):
if (op in map(UnaryOperator, '+-~')): return int
if (op in map(UnaryOperator, ('not', '!'))): return bool
if (not isinstance(valsig, (int, float))): raise KeyError()
if (op in map(BinaryOperator, ('**', *'+-*'))): return valsig
if (op in map(BinaryOperator, ('//', '<<', '>>', *'&^|'))): return int
if (op == BinaryOperator('/')): return float
if (op == BinaryOperator('to')): 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 int
if (op == BinaryOperator('/')): return float
if (isinstance(valsig, int)):
if (op == BinaryOperator('to')): return range
raise KeyError()
class float(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
try: return BuiltinType.operators(op, valsig=valsig, selftype=float)
except KeyError: pass
if (valsig is None):
if (op in map(UnaryOperator, ('not', *'!+-'))): return float
if (not isinstance(valsig, (int, float))): raise KeyError()
if (op in map(BinaryOperator, ('**', *'+-*'))): return float
if (op == BinaryOperator('/')): return float
if (op == BinaryOperator('//')): return int
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 == BinaryOperator('/')): return float
if (op == BinaryOperator('//')): return int
raise KeyError()
class str(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
if (valsig is None): raise KeyError()
if (isinstance(valsig, str) and op == BinaryOperator('+')): return str
if (isinstance(valsig, int) and op == BinaryOperator('*')): return str
try: return BuiltinType.operators(op, valsig=valsig, selftype=str)
except KeyError: pass
if (valsig is not None):
if (isinstance(valsig, (char, str)) and op == BinaryOperator('+')): return str
if (isinstance(valsig, int) and op == BinaryOperator('*')): return str
raise KeyError()
@staticitemget
@instantiate
def itemget(keysig):
def itemget(keysig, key):
if (isinstance(keysig, int)): return char
raise KeyError()
class rstrip(BuiltinFunction):
callargssigstr = "rstrip(char)"
@staticitemget
@instantiate
def call(callargssig):
if (callargssig.kwargs or callargssig.starkwargs): raise KeyError()
return str
class count(BuiltinFunction):
callargssig = "count(char)"
@staticitemget
@instantiate
def call(callargssig):
if (callargssig.kwargs or callargssig.starkwargs): raise KeyError()
return int
class char(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
if (valsig is None): raise KeyError()
if (isinstance(valsig, str) and op == BinaryOperator('+')): return str
if (isinstance(valsig, int) and op == BinaryOperator('*')): return str
if (isinstance(valsig, (char, int)) and op in map(BinaryOperator, '+-')): return char
try: return BuiltinType.operators(op, valsig=valsig, selftype=char)
except KeyError: pass
if (valsig is not None):
if (isinstance(valsig, str) and op in map(BinaryOperator, ('+', 'in'))): return str
if (isinstance(valsig, int) and op == BinaryOperator('*')): return str
if (isinstance(valsig, (char, int)) and op in map(BinaryOperator, '+-')): return char
raise KeyError()
i8 = i16 = i32 = i64 = i128 = \
u8 = u16 = u32 = u64 = u128 = int
f8 = f16 = f32 = f64 = f128 = \
uf8 = uf16 = uf32 = uf64 = uf128 = float
# TODO: implement these types
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 print(BuiltinFunction):
callargssigstr = "print(...)"
#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
@staticitemget
def operators(op, valsig=None):
raise KeyError()
class iterable(BuiltinType, Collection): pass
class list(iterable):
keytype = int()
valtype = Any()
class tuple(iterable):
keytype = int()
valtype = Any()
class stdio(BuiltinObject):
class println(BuiltinFunction):
callargssigstr = "println(...)"
@staticitemget
@instantiate
def call(callargssig):
if (callargssig.kwargs or callargssig.starkwargs): raise KeyError()
return void
class _map(BuiltinFunction):
@staticitemget
@instantiate
def call(callargssig):
if (callargssig.kwargs or callargssig.starkwargs): raise KeyError()
return void
return list
builtin_names = {j.__name__: globals()[j.__name__] for i in map(operator.methodcaller('__subclasses__'), Builtin.__subclasses__()) for j in i}
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'))})
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())})
# by Sdore, 2019
# by Sdore, 2020

16
stdlib/io.sld Normal file
View File

@ -0,0 +1,16 @@
import sld:std:*;
class fd {
str read();
str read(int n);
int write(str data);
}
class stdio {
str readln();
void print(*args);
void println(*args);
}

117
stdlib/std.sld Normal file
View File

@ -0,0 +1,117 @@
class void {}
class bool {
castable to int;
bool operator !;
}
class int {
castable to bool;
castable to float;
int operator +;
int operator -;
int operator ~;
int operator +int;
int operator -int;
int operator *int;
int operator //int;
int operator **int;
int operator <<int;
int operator >>int;
int operator &int;
int operator ^int;
int operator |int;
range operator to int;
int popcount();
int length(int base=2);
}
class float {
float operator +;
float operator -;
bool operator !;
float operator +float;
float operator -float;
float operator *float;
float operator /float;
int operator //float;
float operator **float;
int round();
bool isint();
}
class char {
castable to str;
bool operator !;
char operator +char;
char operator +int;
char operator -char;
char operator -int;
char operator *int;
range operator 'to' char;
}
class str {
bool operator !;
iterable char;
char operator [int];
str operator +str;
str operator *int;
}
class range {
typename type;
iterable type;
type operator [int];
}
class list {
typename type;
iterable type;
type operator [int];
void append(type item);
void insert(int index, type item);
type pop();
type pop(int index);
void reverse();
void sort();
}
class tuple {
typename types[];
iterable types;
types operator [int];
}
class function {
typename argtypes[];
typename rettype;
call rettype (*argtypes);
rettype map(iterable);
}

37
tests/class.sl Normal file
View File

@ -0,0 +1,37 @@
class Test {
int a = 1
init {
.a = 3
}
constr () {
.a = 5
}
constr (int a) {
.a = a
}
}
main {
stdio.println(Test.a)
Test t
stdio.println(t.a)
t.a = 2
stdio.println(t.a)
delete t
Test t = Test()
stdio.println(t.a)
delete t
Test t = Test(7)
stdio.println(t.a)
delete t
#Test t(10)
#stdio.println(t.a)
#delete t
}

View File

@ -18,10 +18,8 @@ auto g(str x) { # g() is of type char, x is of type str
int h(int x) = x+1 # h() is of type int, x is of type int
void main() {
print(h(n), \
main {
stdio.println(h(n), \ # comment here too
f('123asd') + g('32') + 1) #--> «123124 f»
print(q/z/2**96) #--> «4294967296.0»
stdio.println(q/z/2**96) #--> «4294967296.0»
}
main()

8
tests/fib.sl Normal file
View File

@ -0,0 +1,8 @@
int fib(int n) {
if (n < 2) {return 1}
return fib(n-1) + fib(n-2)
}
main {
stdio.println(fib(3))
}

5
tests/fmap.sl Normal file
View File

@ -0,0 +1,5 @@
void f(int x) = stdio.println(x)
main {
f.map([int: 1, 2, 3])
}

6
tests/for.sl Normal file
View File

@ -0,0 +1,6 @@
main {
for i in (0 to 5) {
stdio.println(i)
stdio.println(i+2)
}
}

View File

@ -1,2 +1,2 @@
void f() {}
void f(int a?) {}
void f(int a?) {}

10
tests/list.sl Normal file
View File

@ -0,0 +1,10 @@
list l = [int: 1, 2, 3]
main {
#int i = 0
#while (i < 3) {
for i in (0 to 3) {
stdio.println(i, l[i])
#i -=- 1
}
}

4
tests/nl.sl Normal file
View File

@ -0,0 +1,4 @@
main {
stdio.println(1, #|
|# 2)
}

View File

@ -1,2 +1,5 @@
int a = 3
print(2**a)
main {
stdio.println(2**a)
}

View File

@ -1,5 +1,7 @@
int f(int x) = x+1
int f(int x, int y) = x+y+1
print(f(1))
print(f(1, 2))
main {
stdio.println(f(1))
stdio.println(f(1, 2))
}

3
tests/println.sl Normal file
View File

@ -0,0 +1,3 @@
main {
stdio.println('test')
}

6
tests/redefine.sl Normal file
View File

@ -0,0 +1,6 @@
main {
int a
a = 3
a, b := (int a, char 'b')
stdio.println(a, b)
}

4
tests/strcat.sl Normal file
View File

@ -0,0 +1,4 @@
str a = "a"
str b = "B"
str c = a+b
stdio.println(c)

View File

@ -1,3 +1,4 @@
int a = 3
int b = 5
print(a+b)
i8 a = 3
i8 b = 5
i8 c = a+b
stdio.println(c)

View File

@ -2,29 +2,27 @@ const int a = 3
int b = 2; int c = 0; int x = 9
int sum(int a, int z) = a + z
print(sum(a, b & 3))
print(1)
print(1, 2)
print('a')
print(2*(3)+-2*(5+1)/2)
print(-2*x**(2+2)*a*b+10*c)
print(*'a'+'b'*2)
print(-2-2-2-2-2-2-2-2)
stdio.println(sum(a, b & 3))
stdio.println(1)
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(-2-2-2-2-2-2-2-2)
void main() {
main {
int z = sum(3, 2)
b = 2**100
b *= 2
print(not a)
print(a, b, c)
stdio.println(not a)
stdio.println(a, b, c)
int test() = 2*2
print(sum(b, test()))
stdio.println(sum(b, test()))
for i in (0 to 5) print(i)
else print(0)
for i in 0 to 5 stdio.println(i)
else stdio.println(0)
int i = 5
while (i) {print(i); i -= 1;} else print('ВСЁ!')
while i {stdio.println(i); i -= 1;} else stdio.println('ВСЁ!')
}
main()

4
tests/wrongnl.sl Normal file
View File

@ -0,0 +1,4 @@
main {
stdio.println(1, \ 2,
3)
}

111
tokens.py
View File

@ -5,12 +5,19 @@ from utils import *
class Keyword(str): pass
class ExprKeyword(Keyword): pass
class DefKeyword(Keyword): pass
class DefNamedKeyword(Keyword): pass
class DefArgsKeyword(Keyword): pass
class ClassdefKeyword(DefKeyword): pass # TODO validate!
class ClassdefNamedKeyword(ClassdefKeyword, DefNamedKeyword): pass
class ClassdefArgsKeyword(ClassdefKeyword, DefArgsKeyword): pass
class Modifier(Keyword): pass
class ReservedKeyword(Keyword): pass
class Operator(str): pass
class UnaryOperator(Operator): pass
class BinaryOperator(Operator): pass
class BothOperator(UnaryOperator, BinaryOperator): pass
keywords = (
Keyword('if'),
@ -18,17 +25,36 @@ keywords = (
Keyword('in'),
Keyword('while'),
Keyword('else'),
Keyword('class'),
ExprKeyword('return'),
ExprKeyword('break'),
ExprKeyword('continue'),
ExprKeyword('import'),
ExprKeyword('delete'),
ExprKeyword('assert'),
ExprKeyword('breakpoint'),
DefKeyword('main'),
DefKeyword('exit'),
ClassdefKeyword('init'),
ClassdefArgsKeyword('constr'),
ClassdefNamedKeyword('property'),
ClassdefKeyword('repr'),
ClassdefKeyword('eq'),
Modifier('const'),
Modifier('static'),
Modifier('volatile'),
ReservedKeyword('def'),
ReservedKeyword('try'),
ReservedKeyword('catch'),
ReservedKeyword('except'),
ReservedKeyword('finally'),
ReservedKeyword('raise'),
ReservedKeyword('with'),
ReservedKeyword('yield'),
)
operators = (*(tuple(map(*i)) for i in ( # ordered by priority
(UnaryOperator, '!$:~'),
operators = (*(tuple(map(*i)) for i in ( # ordered by priority
(UnaryOperator, '!+-~'),
(BinaryOperator, ('**',)),
(BinaryOperator, ('//', *'*/%')),
(BinaryOperator, '+-'),
@ -36,19 +62,19 @@ operators = (*(tuple(map(*i)) for i in ( # ordered by priority
(BinaryOperator, '&'),
(BinaryOperator, '^'),
(BinaryOperator, '|'),
(BinaryOperator, ('<', '<=', '>', '>=', '==', '!=', 'is not', 'is')),
(BinaryOperator, ('<', '<=', '>', '>=', '==', '!=', 'is', 'is not', 'in', 'not in', 'isof')),
(UnaryOperator, ('not',)),
(BinaryOperator, ('&&', 'and')),
(BinaryOperator, ('&&', 'and', 'but')),
(BinaryOperator, ('^^', 'xor')),
(BinaryOperator, ('||', 'or')),
(BinaryOperator, ('to',)),
)),)
bothoperators = '%&*+-^'
bothoperators = '+-'
attrops = ('->', '@.', *'@.:')
keyword_operators = ('is not', 'is', 'not', 'and', 'xor', 'or', 'to')
logical_operators = ('is', 'is not', 'in', 'not in', 'not', 'and', 'but', 'xor', 'or', 'isof')
whitespace = ' \t\r\v\f'
specials = ('..', '->', '@.', *'#@.\\,;?=()[]{}')
specials = ('..', ':=', *attrops, *r'#\,;?=()[]{}')
def find_identifier(s):
if (not s or not s[0].isidentifier()): return
@ -63,7 +89,7 @@ def find_keyword(s):
for i in keywords:
if (s.startswith(i)):
l = len(i)
if (not s[l:l+1] or s[l:l+1].isspace()): return (l, i)
if (not s[l:l+1].isidentifier()): return (l, i)
def find_literal(s):
if (not s): return
@ -78,7 +104,7 @@ def find_literal(s):
digits = '0123456789abcdef'
radix = 10
digit = True
dp = s[0] == '.'
dp = (s[0] == '.')
for i in range(1, len(s)):
if (i == 1 and s[0] == '0'):
if (s[1] not in 'box'):
@ -94,14 +120,14 @@ def find_literal(s):
if (not digit or s[i].isalpha()): return
return i
digit = True
if (s[i].casefold() in digits[:radix] or s[i] == '.' and not dp): return i+1
if (s[i].casefold() in digits[:radix] or s[i] == '.' and dp): return i+1
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].isalnum())): return (len(i), i)
if (not (i[-1].isalpha() and s[l:l+1].isidentifier())): return (len(i), BothOperator(i) if (i in bothoperators) else i)
def find_special(s):
if (not s): return
@ -114,7 +140,7 @@ def find_special(s):
l = s.find('\n', 1)
if (l == -1): return len(s)
return l
if (s[:2] == '\\\n'): return 2
if (s[0] == '\\'): return 1
for i in sorted(specials, key=len, reverse=True):
if (s[:len(i)] == i):
if (i == '=' and s[:len(i)+1] == '=='): break
@ -127,7 +153,7 @@ def operator_precedence(op):
class Token:
__slots__ = ('type', 'token', 'lineno', 'offset')
types = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order is also resolution order
types = ('SPECIAL', 'OPERATOR', 'LITERAL', 'KEYWORD', 'IDENTIFIER') # order is also resolution order
def __init__(self, type, token, *, lineno, offset):
self.type, self.token, self.lineno, self.offset = type, token, lineno, offset
@ -157,11 +183,12 @@ class SlSyntaxNoToken(SlSyntaxException): pass
class SlSyntaxEmpty(SlSyntaxNoToken): pass
class SlSyntaxError(SlSyntaxException):
__slots__ = ('desc', 'line', 'lineno', 'offset', 'length')
__slots__ = ('desc', 'line', 'lineno', 'offset', 'length', 'usage')
#@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):
@ -169,10 +196,12 @@ class SlSyntaxError(SlSyntaxException):
def __str__(self):
l, line = lstripcount(self.line.partition('\n')[0].replace('\t', ' '), ' \t')
offset = (self.offset-l) if (self.offset != -1) else len(line)
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'+line[offset:]+'\033[0m\n'+\
' '+' '*offset+'\033[95m^'+'~'*(self.length-1) if (line) else '')
' \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 '')
@property
def at(self):
@ -182,25 +211,57 @@ class SlSyntaxExpectedError(SlSyntaxError):
__slots__ = ('expected', 'found')
def __init__(self, expected='nothing', found='nothing', *, lineno=None, offset=None, length=0, scope=None):
assert expected != found
assert (expected != found)
if (not isinstance(found, str)): lineno, offset, length, found = found.lineno, found.offset, found.length, found.typename if (hasattr(found, 'typename')) else found
assert lineno is not None and offset is not None
assert (lineno is not None and offset is not None)
super().__init__(f"Expected {expected.lower()},\n{' '*(len(scope)+6 if (scope is not None) else 3)}found {found.lower()}", lineno=lineno, offset=offset, length=length, scope=scope)
self.expected, self.found = expected, found
class SlSyntaxExpectedNothingError(SlSyntaxExpectedError):
def __init__(self, found='nothing', **kwargs):
def __init__(self, found, **kwargs):
super().__init__(found=found, **kwargs)
class SlSyntaxMultiExpectedError(SlSyntaxExpectedError):
__slots__ = ('sl',)
class SlSyntaxExpectedMoreTokensError(SlSyntaxExpectedError):
def __init__(self, for_, *, offset=-1, **kwargs):
assert (offset < 0)
super().__init__(expected=f"More tokens for {for_}", offset=offset, **kwargs)
def __init__(self, expected, found, *, scope=None, **kwargs):
class SlSyntaxMultiExpectedError(SlSyntaxExpectedError):
__slots__ = ('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__(S(',\n'+' '*(self.sl+9)).join(Stuple(expected).strip('nothing').uniquize(), last=',\n'+' '*(self.sl+6)+'or ') or 'nothing', S(',\n'+' '*(self.sl+6)).join(Stuple(f"{i.found} at offset {i.offset if (i.offset != -1) else '<end of line>'}" for i in found).strip('nothing').uniquize(), last=',\n'+' '*(self.sl+2)+'and ') or 'nothing', scope=scope, **kwargs)
super().__init__(
expected=S(',\n'+' '*(self.sl+9)).join(Stuple((i.expected+f" at offset {i.offset if (i.offset >= 0) else '<end of line>'}{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 '<end of line>'}{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',
scope=scope,
**kwargs
)
@property
def at(self):
return f"\n{' '*self.sl}at line {self.lineno}"
# by Sdore, 2019
@classmethod
def from_list(cls, err, scope=None, **kwargs):
sl = len(scope)+6 if (scope is not None) else 0
for i in err:
if (isinstance(i, SlSyntaxMultiExpectedError)):
loff = 0
i.expected = ' '*loff+S(i.expected).indent(sl+loff).lstrip()
i.found = ' '*loff+S(i.found).indent(sl+loff).lstrip()
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,
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),
**kwargs
)
# by Sdore, 2020