Initial commit

This commit is contained in:
egormanga 2019-08-28 21:24:54 +03:00
commit 7badd31e42
23 changed files with 2760 additions and 0 deletions

7
.gitignore vendored Normal file
View File

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

1
README.md Symbolic link
View File

@ -0,0 +1 @@
Slang.md

116
SBC/bytecode.md Normal file
View File

@ -0,0 +1,116 @@
# Slang Bytecode
## Standalone
### NOP
0x00
> Does nothing.
### RET
0x01
> Returns `TOS` to caller.
## 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`.
### FLRDIV
0x24
> Pushes `TOS1 // TOS`.
### MOD
0x25
> Pushes `TOS1 % TOS`.
### POW
0x26
> Pushes `TOS1 ** TOS`.
### SHL
0x27
> Pushes `TOS1 << TOS`.
### SHR
0x28
> Pushes `TOS1 >> TOS`.
### AND
0x29
> Pushes `TOS1 & TOS`.
### OR
0x2a
> Pushes `TOS1 | TOS`.
### XOR
0x2b
> Pushes `TOS1 ^ TOS`.
## 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
> Pops `nargs ^ (1 << 7)` TODO

BIN
SBC/tests/hw.sbc Normal file

Binary file not shown.

170
Slang.md Normal file
View File

@ -0,0 +1,170 @@
# Slang
<!-- <font size=0>(June, 30 12:04 AM draft)</font> -->
### Example code
<!-- TODO: Slang syntax highlighting -->
```c
# this is a comment
#| 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
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 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), \
f('123asd') + g('32') + 1) #--> «123124 f»
print(q/z/2**96) #--> «4294967296.0»
}
main()
```
## Tokens
* [Keywords](#Keywords)
* [Identifiers](#Identifiers)
* [Literals](#Literals)
* [Operators](#Operators)
* [Specials](#Specials)
#### Token resolution order
1. Special
2. Operator
3. Literal
4. Keyword
5. Identifier
## Syntax structures
_Note: `*` after syntax unit means any number of them._
### Abstract
* `{[<\n | ;>]* <<expr> <\n | ;>>* [<\n | ;>]*}``code`
* `<<expr> | <code>>``block`
### Primitive
* `<<literal> | <funccall> | <attrget> | <itemget> | <identifier> | <lambda>>``value`
* `<value>\[<value>\]``itemget`
* `<(<expr>) | <value> | <operator> <expr> | <expr> <operator> <expr>>``expr` (`f(x+3)` is an instance of `expr`, also `f`, `x+3` and `x` are `expr`s too)
### Non-final
* `[modifier]* <type>``typedef` (left-hand type definition)
* `[typedef] <identifier> [? | + | * | ** | =<expr>]``argdef` (argument definition)
> `?` — if present then argument value, `none` else.<br>
> `+` — tuple with at least one argument.<br>
> `*` — tuple with any number of arguments.<br>
> `**` — object with keyword arguments.<br>
> `=` — default value if argument not specified.
* `([<argdef>[, <argdef>]*]) -> <typedef> = <expr>``lambda` (lambda function)
* `<<expr>[, <expr>]*[, *<expr>] | *<expr>>``callargs`
* `<<identifier>=<expr>[, <identifier>=<expr>]*[, **<expr>] | **<expr>>``callkwargs`
### Final (ordered by resolution order)
* `<typedef> <identifier>([<argdef>[, <argdef>]*]) <<code> | = <expr>>``funcdef` (function definition)
* `<exprkeyword> [expr]``keywordexpr` (keyword expression)
* `<typedef> <identifier> [= <value>]``vardef` (variable definition)
* `<identifier> = <value>``assignment`
* `<value>([<callargs> | <callkwargs> | <callargs>, <callkwargs>])``funccall` (function call)
* `<expr>` — expr evaluation (only in REPL)
* `if (<expr>) <block>``conditional`
* `for (<identifier> in <expr>) <block>``forloop`
* `while (<expr>) <block>``whileloop`
* `else <block>``elseclause`
## Keywords
* `return [expr]` — return from function
### Modifiers
* `const` — immutable/constant variable
### Reserved keywords
* `def`
## Identifiers
Non-empty sequence of alphanumeric characters plus underscore («_»), not starting with a digit character.
Regex: `[_\w]+[_\w\d]*`
### Data types
* `i8`, `i16`, `i32`, `i64`, `i128` — fixed size integer
* `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
* `int` — unsized («big») integer
* `uint` — unsized unsigned integer
* `float` — unsized floating point number
* `ufloat` — unsized unsigned floating point
* `bool` — logical (boolean) value
* `byte` — single byte
* `char` — UTF-8 character
* `str` — UTF-8 string
* `void` — nothing (or anything...)
* `auto` — compile-time type deduction based on value
## Literals
_Note: `*` after syntax unit here means any number of them, `+` means at least one._
* `<<0<b | o | x>><digit+> | <digit+>.<digit*> | <digit*>.<digit+>>` — number
* `<"<character*>" | '<character*>'>` — string
## Operators
* `<operator> <operand>` — unary operators usage
* `<operand> <operator> <operand>` — binary operators usage
* `<operand> <operator>= <operand>` — in-place operator usage
### Character operators
A set of pre-defined character operators:
* `!$%&*+-:^~` — unary charset
* `%&*+-/<=>^|` — binary charset
* `==`, `**`, `//`, `<<`, `>>` — double-char binary operators
### Keyword operators
A set of pre-defined keyword operators:
* `not` — unary keyword operator
* `and`, `or`, `xor`, `is`, `is not`, `to` — binary keyword operators
## Specials
* `#`, `#|`, `|#` — comment specials
* `;` — expr separator special
* `->`, `@.`, `@`, `.`, `:` — attrget optype specials
* `[`, `]` — itemget specials
* `\,?=(){}` — other specials charset
# Footnotes
All character class checks are performed in current locale.
---
_by Sdore, 2019_

60
Slang.py Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/python3
# 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>'):
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: {repr(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")
code = PyssemblyCompiler.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]
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
def main(cargs):
if (cargs.o is None and not cargs.file.name.rpartition('.')[0]):
argparser.add_argument('-o', metavar='<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))
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))
else: logimported()
# by Sdore, 2019

1
TODO.md Normal file
View File

@ -0,0 +1 @@
- arrays

1
__main__.py Symbolic link
View File

@ -0,0 +1 @@
Slang.py

1283
ast.py Normal file

File diff suppressed because it is too large Load Diff

38
compilers/__init__.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/python3
# Slang compilers
import abc
class Compiler(abc.ABC):
@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(Exception):
__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 __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 '')
@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

278
compilers/pyssembly.py Normal file
View File

@ -0,0 +1,278 @@
#!/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

288
compilers/sbc.py Normal file
View File

@ -0,0 +1,288 @@
#!/usr/bin/python3
# Slang Bytecode (SBC) compiler target
from . import *
from Slang.ast import *
from utils import *
NOP = 0x00
RET = 0x01
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
FLRDIV = 0x24
MOD = 0x25
POW = 0x26
SHL = 0x27
SHR = 0x28
AND = 0x29
OR = 0x2a
XOR = 0x2b
ALLOC = 0xa0
EXTEND = 0xa1
CONST = 0xa2
JUMPF = 0xa3
JUMPB = 0xa4
SCPGET = 0xa5
SCPSET = 0xa6
HASARG = 0xa0
class Instrs:
unops = '+-!~'
binops = (*'+-*/%', '**', '<<', '>>', '&', '|', '^')
@init_defaults
def __init__(self, *, name, ns, filename, argdefs=()):
self.ns = ns
self.instrs = bytearray()
def compile(self):
raise TODO
@dispatch
def add(self, opcode: int, oparg: int = None):
self.instrs.append(opcode)
if (opcode >= HASARG): self.instrs.append(oparg)
else: assert oparg is None
@dispatch
def add(self, x: ASTRootNode):
self.add(x.code)
@dispatch
def add(self, x: ASTCodeNode):
lastln = int()
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): 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

42
lexer.py Normal file
View File

@ -0,0 +1,42 @@
#!/usr/bin/python3
# Slang lexer
from .tokens import *
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()
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])
return (offset+n, Token(ii, s, lineno=lineno, offset=offset+lineoff))
else: raise SlSyntaxError("Invalid token", line, lineno=lineno, offset=offset+lineoff, length=length)
def parse_expr(src, *, lineno=1, lineoff=0):
r = list()
offset = int()
while (True):
offset, tok = read_token(src, lineno=lineno, offset=offset, lineoff=lineoff)
if (tok is None): break
r.append(tok)
return offset, r
def parse_string(src):
src = src.rstrip()
tl = list()
lines = src.count('\n')
lineoff = int()
while (src):
offset, r = parse_expr(src, lineno=lines-src.count('\n')+1, lineoff=lineoff)
lineoff += offset
if (offset < len(src)):
if (src[offset] == '\n'): lineoff = int()
else: lineoff += 1
src = src[offset+1:]
tl.append(r)
return tl
# by Sdore, 2019

89
repl.py Normal file
View File

@ -0,0 +1,89 @@
#!/usr/bin/python3
# Slang REPL
import readline
from .ast import *
from .lexer import *
from utils import *
@dispatch
def execute_node(node: ASTCodeNode, ns):
for i in node.nodes:
r = execute_node(i, ns)
if (r is not None):
ns.values['_'] = r
print(repr(r))
else: return
return r
@dispatch
def execute_node(node: ASTVardefNode, ns):
ns.values[node.name] = node.value
@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
@dispatch
def execute_node(node: ASTFunccallNode, ns):
code_ns = ns.derive(node.callable.value.identifier)
argdefs, func = ns.values[node.callable]
for ii, i in enumerate(node.callargs.callargs):
code_ns.values[argdefs[ii].name] = i
return execute_node(func, code_ns)
@dispatch
def execute_node(node: ASTValueNode, ns):
return execute_node(node.value, ns)
@dispatch
def execute_node(node: ASTIdentifierNode, ns):
return ns.values[node]
@dispatch
def execute_node(node: ASTLiteralNode, ns):
return eval(str(node.literal))
@dispatch
def execute_node(node: ASTKeywordExprNode, ns):
if (node.keyword.keyword == 'return'): return execute_node(node.value, ns)
raise NotImplementedError(node.keyword)
@dispatch
def execute_node(node: ASTUnaryExprNode, ns):
return eval(f"{node.operator.operator} {execute_node(node.value, ns)}")
@dispatch
def execute_node(node: ASTBinaryExprNode, ns):
return eval(f"{execute_node(node.lvalue, ns)} {node.operator.operator} {execute_node(node.rvalue, ns)}")
def repl():
ns = Namespace('<repl>')
#ns.values['print'] = print
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

110
stdlib.py Normal file
View File

@ -0,0 +1,110 @@
#!/usr/bin/python3
# Slang stdlib
from .ast import Signature, Function, Object
from .tokens import *
from utils import *
class Builtin(Signature):
def __init__(self):
pass
@property
def __reprname__(self):
return type(self).mro()[1].__name__
@property
def typename(self):
return type(self).__name__
class BuiltinFunction(Builtin, Function): pass
class BuiltinObject(Builtin, Object): pass
class BuiltinType(Builtin):
@init_defaults
@autocast
def __init__(self, *, modifiers: paramset):
self.modifiers = modifiers
class void(BuiltinType): pass
class bool(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
if (valsig is None):
if (op in map(UnaryOperator, '+-~')): return int
if (op in map(UnaryOperator, ('not', '!'))): return bool
raise KeyError()
class int(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
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
raise KeyError()
class float(BuiltinType):
@staticitemget
@instantiate
def operators(op, valsig=None):
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
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
raise KeyError()
@staticitemget
@instantiate
def itemget(keysig):
if (isinstance(keysig, int)): return char
raise KeyError()
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
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 print(BuiltinFunction):
callargssigstr = "print(...)"
@staticitemget
@instantiate
def call(callargssig):
if (callargssig.kwargs or callargssig.starkwargs): raise KeyError()
return void
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'))})
# by Sdore, 2019

27
tests/example.sl Normal file
View File

@ -0,0 +1,27 @@
# this is a comment
#| 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
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
}
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), \
f('123asd') + g('32') + 1) #--> «123124 f»
print(q/z/2**96) #--> «4294967296.0»
}
main()

2
tests/funcdef.sl Normal file
View File

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

2
tests/opti.sl Normal file
View File

@ -0,0 +1,2 @@
int a = 3
print(2**a)

5
tests/overload.sl Normal file
View File

@ -0,0 +1,5 @@
int f(int x) = x+1
int f(int x, int y) = x+y+1
print(f(1))
print(f(1, 2))

3
tests/sum.sl Normal file
View File

@ -0,0 +1,3 @@
int a = 3
int b = 5
print(a+b)

30
tests/test.sl Normal file
View File

@ -0,0 +1,30 @@
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)
void main() {
int z = sum(3, 2)
b = 2**100
b *= 2
print(not a)
print(a, b, c)
int test() = 2*2
print(sum(b, test()))
for i in (0 to 5) print(i)
else print(0)
int i = 5
while (i) {print(i); i -= 1;} else print('ВСЁ!')
}
main()

1
tests/underscore.sl Normal file
View File

@ -0,0 +1 @@
int _a_b_

206
tokens.py Normal file
View File

@ -0,0 +1,206 @@
#!/usr/bin/python3
# Slang Tokens
from utils import *
class Keyword(str): pass
class ExprKeyword(Keyword): pass
class Modifier(Keyword): pass
class ReservedKeyword(Keyword): pass
class Operator(str): pass
class UnaryOperator(Operator): pass
class BinaryOperator(Operator): pass
keywords = (
Keyword('if'),
Keyword('for'),
Keyword('in'),
Keyword('while'),
Keyword('else'),
ExprKeyword('return'),
ExprKeyword('break'),
ExprKeyword('continue'),
ExprKeyword('import'),
Modifier('const'),
Modifier('volatile'),
ReservedKeyword('def'),
)
operators = (*(tuple(map(*i)) for i in ( # ordered by priority
(UnaryOperator, '!$:~'),
(BinaryOperator, ('**',)),
(BinaryOperator, ('//', *'*/%')),
(BinaryOperator, '+-'),
(BinaryOperator, ('<<', '>>')),
(BinaryOperator, '&'),
(BinaryOperator, '^'),
(BinaryOperator, '|'),
(BinaryOperator, ('<', '<=', '>', '>=', '==', '!=', 'is not', 'is')),
(UnaryOperator, ('not',)),
(BinaryOperator, ('&&', 'and')),
(BinaryOperator, ('^^', 'xor')),
(BinaryOperator, ('||', 'or')),
(BinaryOperator, ('to',)),
)),)
bothoperators = '%&*+-^'
attrops = ('->', '@.', *'@.:')
keyword_operators = ('is not', 'is', 'not', 'and', 'xor', 'or', 'to')
whitespace = ' \t\r\v\f'
specials = ('..', '->', '@.', *'#@.\\,;?=()[]{}')
def find_identifier(s):
if (not s or not s[0].isidentifier()): return
i = 1
for i in range(1, len(s)):
if (not s[i].isalnum() and s[i] != '_'): break
else: i += 1
if (s[:i].isidentifier()): return i
def find_keyword(s):
if (not s): return
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)
def find_literal(s):
if (not s): return
if (s[0] in '"\''):
esc = bool()
for i in range(1, len(s)):
if (esc): esc = False; continue
if (s[i] == '\\'): esc = True; continue
if (s[i] == s[0]): return i+1
if (s[0].isdigit() or s[0] == '.'):
i = int()
digits = '0123456789abcdef'
radix = 10
digit = True
dp = s[0] == '.'
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
return 1
else:
radix = (2, 8, 16)['box'.index(s[1])]
digit = False
continue
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
return i
digit = True
if (s[i].casefold() in digits[:radix] or s[i] == '.' and not 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)
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
if (s[0] == '#'):
l = s.find('\n', 1)
if (l == -1): return len(s)
return l
if (s[:2] == '\\\n'): return 2
for i in sorted(specials, key=len, reverse=True):
if (s[:len(i)] == i):
if (i == '=' and s[:len(i)+1] == '=='): break
return len(i)
def operator_precedence(op):
for ii, i in enumerate(operators):
if (op in i): return ii
#else: return len(operators)
class Token:
__slots__ = ('type', 'token', 'lineno', 'offset')
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
def __repr__(self):
return f"<Token {self.types[self.type]} «{repr(self.token)[1:-1]}» at line {self.lineno}, offset {self.offset}>"
def __eq__(self, x):
return super() == x or self.token == x
@property
def typename(self):
return self.types[self.type]
@property
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 SlSyntaxNoToken(SlSyntaxException): pass
class SlSyntaxEmpty(SlSyntaxNoToken): pass
class SlSyntaxError(SlSyntaxException):
__slots__ = ('desc', 'line', 'lineno', 'offset', 'length')
#@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
#@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 __str__(self):
l, line = lstripcount(self.line.partition('\n')[0].replace('\t', ' '), ' \t')
offset = (self.offset-l) if (self.offset != -1) else len(line)
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 '')
@property
def at(self):
return f" at line {self.lineno}, offset {self.offset}"
class SlSyntaxExpectedError(SlSyntaxError):
__slots__ = ('expected', 'found')
def __init__(self, expected='nothing', found='nothing', *, lineno=None, offset=None, length=0, scope=None):
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
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):
super().__init__(found=found, **kwargs)
class SlSyntaxMultiExpectedError(SlSyntaxExpectedError):
__slots__ = ('sl',)
def __init__(self, expected, found, *, scope=None, **kwargs):
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)
@property
def at(self):
return f"\n{' '*self.sl}at line {self.lineno}"
# by Sdore, 2019