├── .gitignore ├── Objects ├── baseBundle.nim ├── boolobject.nim ├── boolobjectImpl.nim ├── bundle.nim ├── cellobject.nim ├── codeobject.nim ├── descrobject.nim ├── dictobject.nim ├── dictproxyobject.nim ├── exceptions.nim ├── exceptionsImpl.nim ├── frameobject.nim ├── funcobject.nim ├── funcobjectImpl.nim ├── iterobject.nim ├── listobject.nim ├── methodobject.nim ├── moduleobject.nim ├── noneobject.nim ├── numobjects.nim ├── pyobject.nim ├── pyobjectBase.nim ├── rangeobject.nim ├── sliceobject.nim ├── stringobject.nim ├── stringobjectImpl.nim ├── tupleobject.nim └── typeobject.nim ├── Parser ├── Grammar ├── grammar.nim ├── lexer.nim ├── parser.nim └── token.nim ├── Python ├── asdl.nim ├── ast.nim ├── bltinmodule.nim ├── builtindict.nim ├── call.nim ├── compile.nim ├── coreconfig.nim ├── cpython.nim ├── jspython.nim ├── lifecycle.nim ├── neval.nim ├── opcode.nim ├── python.nim ├── symtable.nim └── traceback.nim ├── README.md ├── Utils ├── compat.nim └── utils.nim ├── npython.nimble └── tests ├── README.md ├── asserts ├── and.py ├── assert.py ├── bigint.py ├── breakcontinue.py ├── closure.py ├── comprehension.py ├── decorators.py ├── descriptors.py ├── factorial.py ├── fib.py ├── for.py ├── function.py ├── ifelse.py ├── import.py ├── in.py ├── insertsort.py ├── int.py ├── list.py ├── magic.py ├── none.py ├── property.py ├── quicksort.py ├── raise.py ├── simpleclass.py ├── simpleclass2.py ├── slice.py ├── tryexcept.py ├── tuple.py ├── unpack.py └── xfail.py ├── basics ├── basic.py ├── calc.py ├── everything.py ├── function.py ├── ifelse.py ├── import.py ├── loop.py └── setitem.py ├── benchmark ├── f_spin.py ├── fib.py ├── leak.py ├── mem.py ├── pass.py └── spin.py └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | trash/ 2 | 3 | .gdb_history 4 | *.txt 5 | # nimpretty backup file 6 | *.backup 7 | 8 | ./Objects/test.nim 9 | 10 | __pycache__/ 11 | -------------------------------------------------------------------------------- /Objects/baseBundle.nim: -------------------------------------------------------------------------------- 1 | import numobjects, exceptions, noneobject, stringobject, boolobject 2 | export numobjects, exceptions, noneobject, stringobject, boolobject 3 | -------------------------------------------------------------------------------- /Objects/boolobject.nim: -------------------------------------------------------------------------------- 1 | import pyobject 2 | 3 | declarePyType Bool(tpToken): 4 | b: bool 5 | 6 | proc newPyBool(b: bool): PyBoolObject = 7 | result = newPyBoolSimple() 8 | result.b = b 9 | 10 | 11 | let pyTrueObj* = newPyBool(true) 12 | let pyFalseObj* = newPyBool(false) 13 | -------------------------------------------------------------------------------- /Objects/boolobjectImpl.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | import hashes 3 | import macros 4 | 5 | import pyobject 6 | import exceptions 7 | import stringobject 8 | import boolobject 9 | import numobjects 10 | 11 | export boolobject 12 | 13 | method `$`*(obj: PyBoolObject): string = 14 | $obj.b 15 | 16 | methodMacroTmpl(Bool) 17 | 18 | implBoolMagic Not: 19 | if self == pyTrueObj: 20 | pyFalseObj 21 | else: 22 | pyTrueObj 23 | 24 | 25 | implBoolMagic bool: 26 | self 27 | 28 | 29 | implBoolMagic And: 30 | let otherBoolObj = other.callMagic(bool) 31 | errorIfNotBool(otherBoolObj, "__bool__") 32 | if self.b and PyBoolObject(otherBoolObj).b: 33 | return pyTrueObj 34 | else: 35 | return pyFalseObj 36 | 37 | implBoolMagic Xor: 38 | let otherBoolObj = other.callMagic(bool) 39 | errorIfNotBool(otherBoolObj, "__bool__") 40 | if self.b xor PyBoolObject(otherBoolObj).b: 41 | return pyTrueObj 42 | else: 43 | return pyFalseObj 44 | 45 | implBoolMagic Or: 46 | let otherBoolObj = other.callMagic(bool) 47 | errorIfNotBool(otherBoolObj, "__bool__") 48 | if self.b or PyBoolObject(otherBoolObj).b: 49 | return pyTrueObj 50 | else: 51 | return pyFalseObj 52 | 53 | implBoolMagic eq: 54 | let otherBoolObj = other.callMagic(bool) 55 | errorIfNotBool(otherBoolObj, "__bool__") 56 | let otherBool = PyBoolObject(otherBoolObj).b 57 | if self.b == otherBool: 58 | return pyTrueObj 59 | else: 60 | return pyFalseObj 61 | 62 | implBoolMagic repr: 63 | if self.b: 64 | return newPyString("True") 65 | else: 66 | return newPyString("False") 67 | 68 | implBoolMagic hash: 69 | newPyInt(Hash(self.b)) 70 | -------------------------------------------------------------------------------- /Objects/bundle.nim: -------------------------------------------------------------------------------- 1 | import baseBundle 2 | import codeobject, dictobject, frameobject, boolobjectImpl, 3 | listobject, moduleobject, methodobject, funcobject, 4 | pyobject, stringobjectImpl, rangeobject, exceptionsImpl, 5 | sliceobject, tupleobject, cellobject, methodobject 6 | export baseBundle 7 | export codeobject, dictobject, frameobject, boolobjectImpl, 8 | listobject, moduleobject, methodobject, funcobject, 9 | pyobject, stringobjectImpl, rangeobject, exceptionsImpl, 10 | sliceobject, tupleobject, cellobject, methodobject 11 | -------------------------------------------------------------------------------- /Objects/cellobject.nim: -------------------------------------------------------------------------------- 1 | import pyobject 2 | 3 | 4 | declarePyType Cell(tpToken): 5 | refObj: PyObject # might be nil 6 | 7 | proc newPyCell*(content: PyObject): PyCellObject = 8 | result = newPyCellSimple() 9 | result.refObj = content 10 | -------------------------------------------------------------------------------- /Objects/codeobject.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | import strutils 3 | 4 | import pyobject 5 | import stringobject 6 | import ../Python/[opcode, symtable] 7 | 8 | type 9 | OpArg* = int 10 | 11 | declarePyType Code(tpToken): 12 | # for convenient and not performance critical accessing 13 | code: seq[(OpCode, OpArg)] 14 | opCodes: ptr OpCode # array of opcodes with `length`, same with `code` 15 | opArgs: ptr OpArg # array of args with `length`, same with `code` 16 | lineNos: seq[int] 17 | constants: seq[PyObject] 18 | 19 | # store the strings for exception and debugging infomation 20 | names: seq[PyStrObject] 21 | localVars: seq[PyStrObject] 22 | cellVars: seq[PyStrObject] 23 | freeVars: seq[PyStrObject] 24 | 25 | argNames: seq[PyStrObject] 26 | argScopes: seq[(Scope, int)] 27 | 28 | # for tracebacks 29 | codeName: PyStrObject 30 | fileName: PyStrObject 31 | 32 | 33 | # most attrs of code objects are set in compile.nim 34 | proc newPyCode*(codeName, fileName: PyStrObject, length: int): PyCodeObject = 35 | when defined(js): 36 | result = newPyCodeSimple() 37 | else: 38 | proc finalizer(obj: PyCodeObject) = 39 | dealloc(obj.opCodes) 40 | dealloc(obj.opArgs) 41 | 42 | newPyCodeFinalizer(result, finalizer) 43 | result.opCodes = createU(OpCode, length) 44 | result.opArgs = createU(OpArg, length) 45 | result.codeName = codeName 46 | result.fileName = fileName 47 | 48 | proc len*(code: PyCodeObject): int {. inline .} = 49 | code.code.len 50 | 51 | template `[]`*(opCodes: ptr OpCode, idx: int): OpCode = 52 | cast[ptr OpCode](cast[int](opCodes) + idx * sizeof(OpCode))[] 53 | 54 | template `[]`*(opArgs: ptr OpArg, idx: int): OpArg = 55 | cast[ptr OpArg](cast[int](opArgs) + idx * sizeof(OpArg))[] 56 | 57 | template `[]=`(opCodes: ptr OpCode, idx: int, value: OpCode) = 58 | cast[ptr OpCode](cast[int](opCodes) + idx * sizeof(OpCode))[] = value 59 | 60 | template `[]=`(opArgs: ptr OpArg, idx: int, value: OpArg) = 61 | cast[ptr OpArg](cast[int](opArgs) + idx * sizeof(OpArg))[] = value 62 | 63 | proc addOpCode*(code: PyCodeObject, 64 | instr: tuple[opCode: OpCode, opArg: OpArg, lineNo: int]) = 65 | code.opCodes[code.len] = instr.opCode 66 | code.opArgs[code.len] = instr.opArg 67 | code.code.add((instr.opCode, instr.opArg)) 68 | code.lineNos.add(instr.lineNo) 69 | 70 | implCodeMagic repr: 71 | let codeName = self.codeName.str 72 | let fileName = self.fileName.str 73 | let msg = fmt("") 74 | newPyStr(msg) 75 | 76 | method `$`*(code: PyCodeObject): string = 77 | var s: seq[string] 78 | s.add("Names: " & $code.names) 79 | s.add("Local variables: " & $code.localVars) 80 | s.add("Cell variables: " & $code.cellVars) 81 | s.add("Free variables: " & $code.freeVars) 82 | # temperary workaround for code obj in the disassembly 83 | var otherCodes: seq[PyCodeObject] 84 | for idx, opArray in code.code: 85 | let opCode = OpCode(opArray[0]) 86 | let opArg = opArray[1] 87 | var line = fmt"{idx:>10} {opCode:<30}" 88 | if opCode in hasArgSet: 89 | line &= fmt"{opArg:<4}" 90 | case opCode 91 | of OpCode.LoadName, OpCode.StoreName, OpCode.LoadAttr, 92 | OpCode.LoadGlobal, OpCode.StoreGlobal: 93 | line &= fmt" ({code.names[opArg]})" 94 | of OpCode.LoadConst: 95 | let constObj = code.constants[opArg] 96 | if constObj.ofPyCodeObject: 97 | let otherCode = PyCodeObject(constObj) 98 | otherCodes.add(otherCode) 99 | let reprStr = tpMagic(Code, repr)(otherCode) 100 | line &= fmt" ({reprStr})" 101 | else: 102 | line &= fmt" ({code.constants[opArg]})" 103 | of OpCode.LoadFast, OpCode.StoreFast: 104 | line &= fmt" ({code.localVars[opArg]})" 105 | of OpCode.LoadDeref, OpCode.StoreDeref: 106 | if opArg < code.cellVars.len: 107 | line &= fmt" ({code.cellVars[opArg]})" 108 | else: 109 | line &= fmt" ({code.freeVars[opArg - code.cellVars.len]})" 110 | of OpCode.CallFunction, jumpSet, OpCode.BuildList, 111 | OpCode.BuildTuple, OpCode.UnpackSequence, OpCode.MakeFunction, 112 | OpCode.RaiseVarargs: 113 | discard 114 | else: 115 | line &= " (Unknown OpCode)" 116 | s.add(line) 117 | s.add("\n") 118 | result = s.join("\n") 119 | for otherCode in otherCodes: 120 | result &= $otherCode 121 | 122 | -------------------------------------------------------------------------------- /Objects/descrobject.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | 3 | import pyobject 4 | import noneobject 5 | import exceptions 6 | import stringobject 7 | import methodobject 8 | import funcobject 9 | import ../Python/call 10 | import ../Utils/utils 11 | 12 | # method descriptor 13 | 14 | declarePyType MethodDescr(): 15 | name: PyStrObject 16 | dType: PyTypeObject 17 | kind: NFunc 18 | meth: int # the method function pointer. Have to be int to make it generic. 19 | 20 | 21 | template newMethodDescrTmpl(FunType) = 22 | proc newPyMethodDescr*(t: PyTypeObject, 23 | meth: FunType, 24 | name: PyStrObject, 25 | ): PyMethodDescrObject = 26 | result = newPyMethodDescrSimple() 27 | result.dType = t 28 | result.kind = NFunc.FunType 29 | assert result.kind != NFunc.BltinFunc 30 | result.meth = cast[int](meth) 31 | result.name = name 32 | 33 | 34 | newMethodDescrTmpl(UnaryMethod) 35 | newMethodDescrTmpl(BinaryMethod) 36 | newMethodDescrTmpl(TernaryMethod) 37 | newMethodDescrTmpl(BltinMethod) 38 | # placeholder to fool compiler in typeobject.nim when initializing type dict 39 | proc newPyMethodDescr*(t: PyTypeObject, 40 | meth: BltinFunc, 41 | name: PyStrObject 42 | ): PyMethodDescrObject = 43 | unreachable("bltin function shouldn't be method. " & 44 | "This is a placeholder to fool the compiler") 45 | 46 | 47 | implMethodDescrMagic get: 48 | if other.pyType != self.dType: 49 | let msg = fmt"descriptor {self.name} for {self.dType.name} objects " & 50 | fmt"doesn't apply to {other.pyType.name} object" 51 | return newTypeError(msg) 52 | let owner = other 53 | case self.kind 54 | of NFunc.BltinFunc: 55 | return newPyNimFunc(cast[BltinFunc](self.meth), self.name) 56 | of NFunc.UnaryMethod: 57 | return newPyNimFunc(cast[UnaryMethod](self.meth), self.name, owner) 58 | of NFunc.BinaryMethod: 59 | return newPyNimFunc(cast[BinaryMethod](self.meth), self.name, owner) 60 | of NFunc.TernaryMethod: 61 | return newPyNimFunc(cast[TernaryMethod](self.meth), self.name, owner) 62 | of NFunc.BltinMethod: 63 | return newPyNimFunc(cast[BltinMethod](self.meth), self.name, owner) 64 | 65 | # get set descriptor 66 | # Nim level property decorator 67 | 68 | declarePyType GetSetDescr(): 69 | getter: UnaryMethod 70 | setter: BinaryMethod 71 | 72 | implGetSetDescrMagic get: 73 | self.getter(other) 74 | 75 | implGetSetDescrMagic set: 76 | self.setter(arg1, arg2) 77 | 78 | proc newPyGetSetDescr*(getter: UnaryMethod, setter: BinaryMethod): PyObject = 79 | let descr = newPyGetSetDescrSimple() 80 | descr.getter = getter 81 | descr.setter = setter 82 | descr 83 | 84 | 85 | # property decorator 86 | declarePyType Property(): 87 | getter: PyObject 88 | # setter, deleter not implemented 89 | 90 | implPropertyMagic init: 91 | # again currently only have getter 92 | checkArgNum(1) 93 | self.getter = args[0] 94 | pyNone 95 | 96 | implPropertyMagic get: 97 | fastCall(self.getter, @[other]) 98 | 99 | -------------------------------------------------------------------------------- /Objects/dictobject.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | import hashes 3 | import strutils 4 | import tables 5 | import macros 6 | 7 | import pyobject 8 | import listobject 9 | import baseBundle 10 | import ../Utils/utils 11 | 12 | 13 | # hash functions for py objects 14 | # raises an exception to indicate type error. Should fix this 15 | # when implementing custom dict 16 | proc hash*(obj: PyObject): Hash = 17 | let fun = obj.pyType.magicMethods.hash 18 | if fun.isNil: 19 | return hash(addr(obj[])) 20 | else: 21 | let retObj = fun(obj) 22 | if not retObj.ofPyIntObject: 23 | raise newException(DictError, retObj.pyType.name) 24 | return hash(PyIntObject(retObj)) 25 | 26 | 27 | proc `==`*(obj1, obj2: PyObject): bool {. inline, cdecl .} = 28 | let fun = obj1.pyType.magicMethods.eq 29 | if fun.isNil: 30 | return obj1.id == obj2.id 31 | else: 32 | let retObj = fun(obj1, obj2) 33 | if not retObj.ofPyBoolObject: 34 | raise newException(DictError, retObj.pyType.name) 35 | return PyBoolObject(retObj).b 36 | 37 | 38 | # currently not ordered 39 | # nim ordered table has O(n) delete time 40 | # todo: implement an ordered dict 41 | declarePyType dict(reprLock, mutable): 42 | table: Table[PyObject, PyObject] 43 | 44 | 45 | proc newPyDict* : PyDictObject = 46 | result = newPyDictSimple() 47 | result.table = initTable[PyObject, PyObject]() 48 | 49 | proc hasKey*(dict: PyDictObject, key: PyObject): bool = 50 | return dict.table.hasKey(key) 51 | 52 | proc `[]`*(dict: PyDictObject, key: PyObject): PyObject = 53 | return dict.table[key] 54 | 55 | 56 | proc `[]=`*(dict: PyDictObject, key, value: PyObject) = 57 | dict.table[key] = value 58 | 59 | 60 | template checkHashableTmpl(obj) = 61 | let hashFunc = obj.pyType.magicMethods.hash 62 | if hashFunc.isNil: 63 | let tpName = obj.pyType.name 64 | let msg = "unhashable type: " & tpName 65 | return newTypeError(msg) 66 | 67 | 68 | implDictMagic contains, [mutable: read]: 69 | checkHashableTmpl(other) 70 | try: 71 | result = self.table.getOrDefault(other, nil) 72 | except DictError: 73 | let msg = "__hash__ method doesn't return an integer or __eq__ method doesn't return a bool" 74 | return newTypeError(msg) 75 | if result.isNil: 76 | return pyFalseObj 77 | else: 78 | return pyTrueObj 79 | 80 | implDictMagic repr, [mutable: read, reprLock]: 81 | var ss: seq[string] 82 | for k, v in self.table.pairs: 83 | let kRepr = k.callMagic(repr) 84 | let vRepr = v.callMagic(repr) 85 | errorIfNotString(kRepr, "__str__") 86 | errorIfNotString(vRepr, "__str__") 87 | ss.add fmt"{PyStrObject(kRepr).str}: {PyStrObject(vRepr).str}" 88 | return newPyString("{" & ss.join(", ") & "}") 89 | 90 | 91 | implDictMagic len, [mutable: read]: 92 | newPyInt(self.table.len) 93 | 94 | implDictMagic New: 95 | newPyDict() 96 | 97 | 98 | implDictMagic getitem, [mutable: read]: 99 | checkHashableTmpl(other) 100 | try: 101 | result = self.table.getOrDefault(other, nil) 102 | except DictError: 103 | let msg = "__hash__ method doesn't return an integer or __eq__ method doesn't return a bool" 104 | return newTypeError(msg) 105 | if not (result.isNil): 106 | return result 107 | 108 | var msg: string 109 | let repr = other.pyType.magicMethods.repr(other) 110 | if repr.isThrownException: 111 | msg = "exception occured when generating key error msg calling repr" 112 | else: 113 | msg = PyStrObject(repr).str 114 | return newKeyError(msg) 115 | 116 | 117 | implDictMagic setitem, [mutable: write]: 118 | checkHashableTmpl(arg1) 119 | try: 120 | self.table[arg1] = arg2 121 | except DictError: 122 | let msg = "__hash__ method doesn't return an integer or __eq__ method doesn't return a bool" 123 | return newTypeError(msg) 124 | pyNone 125 | 126 | implDictMethod copy(), [mutable: read]: 127 | let newT = newPyDict() 128 | newT.table = self.table 129 | newT 130 | 131 | # in real python this would return a iterator 132 | # this function is used internally 133 | proc keys*(d: PyDictObject): PyListObject = 134 | result = newPyList() 135 | for key in d.table.keys: 136 | let rebObj = tpMethod(List, append)(result, @[key]) 137 | if rebObj.isThrownException: 138 | unreachable("No chance for append to thrown exception") 139 | 140 | 141 | proc update*(d1, d2: PyDictObject) = 142 | for k, v in d2.table.pairs: 143 | d1[k] = v 144 | -------------------------------------------------------------------------------- /Objects/dictproxyobject.nim: -------------------------------------------------------------------------------- 1 | import pyobject 2 | import baseBundle 3 | import dictobject 4 | 5 | # read only dict used for `__dict__` of types 6 | declarePyType DictProxy(): 7 | dict: PyObject 8 | 9 | implDictProxyMagic repr: 10 | # todo: add "dictproxy" or "mappingproxy" after string methods are implemented 11 | self.dict.callMagic(repr) 12 | 13 | implDictProxyMagic str: 14 | self.dict.callMagic(str) 15 | 16 | implDictProxyMagic getitem: 17 | self.dict.callMagic(getitem, other) 18 | 19 | implDictProxyMagic len: 20 | self.dict.callMagic(len) 21 | 22 | implDictProxyMagic init(mapping: PyObject): 23 | self.dict = mapping 24 | pyNone 25 | 26 | proc newPyDictProxy*(mapping: PyObject): PyObject {. cdecl .} = 27 | let d = newPyDictProxySimple() 28 | d.dict = mapping 29 | d 30 | 31 | -------------------------------------------------------------------------------- /Objects/exceptions.nim: -------------------------------------------------------------------------------- 1 | # we use a completely different approach for error handling 2 | # CPython relies on NULL as return value to inform the caller 3 | # that exception happens in that function. 4 | # Using NULL or nil as "expected" return value is bad idea 5 | # let alone using global variables so 6 | # we return exception object directly with a thrown flag inside 7 | # This might be a bit slower but we are not pursueing ultra performance anyway 8 | 9 | import strformat 10 | 11 | import pyobject 12 | import noneobject 13 | 14 | type ExceptionToken* {. pure .} = enum 15 | Base, 16 | Name, 17 | NotImplemented, 18 | Type, 19 | Attribute, 20 | Value, 21 | Index, 22 | StopIter, 23 | Lock, 24 | Import, 25 | UnboundLocal, 26 | Key, 27 | Assertion, 28 | ZeroDivision, 29 | Runtime, 30 | Syntax, 31 | Memory, 32 | KeyboardInterrupt, 33 | 34 | 35 | type TraceBack* = tuple 36 | fileName: PyObject # actually string 37 | funName: PyObject # actually string 38 | lineNo: int 39 | colNo: int # optional, for syntax error 40 | 41 | 42 | declarePyType BaseError(tpToken): 43 | tk: ExceptionToken 44 | thrown: bool 45 | msg: PyObject # could be nil 46 | context: PyBaseErrorObject # if the exception happens during handling another exception 47 | # used for tracebacks, set in neval.nim 48 | traceBacks: seq[TraceBack] 49 | 50 | 51 | type 52 | PyExceptionObject* = PyBaseErrorObject 53 | 54 | 55 | proc ofPyExceptionObject*(obj: PyObject): bool {. cdecl, inline .} = 56 | obj.ofPyBaseErrorObject 57 | 58 | 59 | macro declareErrors: untyped = 60 | result = newStmtList() 61 | var tokenStr: string 62 | for i in 1..int(ExceptionToken.high): 63 | let tokenStr = $ExceptionToken(i) 64 | 65 | let typeNode = nnkStmtList.newTree( 66 | nnkCommand.newTree( 67 | newIdentNode("declarePyType"), 68 | nnkCall.newTree( 69 | newIdentNode(tokenStr & "Error"), 70 | nnkCall.newTree( 71 | newIdentNode("base"), 72 | newIdentNode("BaseError") 73 | ) 74 | ), 75 | nnkStmtList.newTree( 76 | nnkDiscardStmt.newTree( 77 | newEmptyNode() 78 | ) 79 | ) 80 | ) 81 | ) 82 | 83 | result.add(typeNode) 84 | 85 | template addTpTmpl(name) = 86 | `py name ErrorObjectType`.kind = PyTypeToken.BaseError 87 | `py name ErrorObjectType`.base = pyBaseErrorObjectType 88 | 89 | result.add(getAst(addTpTmpl(ident(tokenStr)))) 90 | 91 | 92 | declareErrors 93 | 94 | 95 | template newProcTmpl(excpName) = 96 | # use template for lazy evaluation to use PyString 97 | # theses two templates are used internally to generate errors (default thrown) 98 | template `new excpName Error`*: PyBaseErrorObject = 99 | let excp = `newPy excpName ErrorSimple`() 100 | excp.tk = ExceptionToken.`excpName` 101 | excp.thrown = true 102 | excp 103 | 104 | 105 | template `new excpName Error`*(msgStr:string): PyBaseErrorObject = 106 | let excp = `newPy excpName ErrorSimple`() 107 | excp.tk = ExceptionToken.`excpName` 108 | excp.thrown = true 109 | excp.msg = newPyString(msgStr) 110 | excp 111 | 112 | 113 | macro genNewProcs: untyped = 114 | result = newStmtList() 115 | var tokenStr: string 116 | for i in ExceptionToken.low..ExceptionToken.high: 117 | let tokenStr = $ExceptionToken(i) 118 | result.add(getAst(newProcTmpl(ident(tokenStr)))) 119 | 120 | 121 | genNewProcs 122 | 123 | 124 | template newAttributeError*(tpName, attrName: string): PyExceptionObject = 125 | let msg = tpName & " has no attribute " & attrName 126 | newAttributeError(msg) 127 | 128 | 129 | 130 | template newIndexTypeError*(typeName:string, obj:PyObject): PyExceptionObject = 131 | let name = $obj.pyType.name 132 | let msg = typeName & " indices must be integers or slices, not " & name 133 | newTypeError(msg) 134 | 135 | 136 | proc isStopIter*(obj: PyObject): bool = 137 | if not obj.ofPyExceptionObject: 138 | return false 139 | let excp = PyExceptionObject(obj) 140 | return (excp.tk == ExceptionToken.StopIter) and (excp.thrown) 141 | 142 | 143 | method `$`*(e: PyExceptionObject): string = 144 | result = "Error: " & $e.tk & " " 145 | if not e.msg.isNil: 146 | result &= $e.msg 147 | 148 | 149 | 150 | template isThrownException*(pyObj: PyObject): bool = 151 | if pyObj.ofPyExceptionObject: 152 | cast[PyExceptionObject](pyObj).thrown 153 | else: 154 | false 155 | 156 | 157 | template errorIfNotString*(pyObj: untyped, methodName: string) = 158 | if not pyObj.ofPyStrObject: 159 | let typeName {. inject .} = pyObj.pyType.name 160 | let msg = methodName & fmt" returned non-string (type {typeName})" 161 | return newTypeError(msg) 162 | 163 | template errorIfNotBool*(pyObj: untyped, methodName: string) = 164 | if not pyObj.ofPyBoolObject: 165 | let typeName {. inject .} = pyObj.pyType.name 166 | let msg = methodName & fmt" returned non-bool (type {typeName})" 167 | return newTypeError(msg) 168 | 169 | 170 | template getIterableWithCheck*(obj: PyObject): (PyObject, UnaryMethod) = 171 | var retTuple: (PyObject, UnaryMethod) 172 | block body: 173 | let iterFunc = obj.getMagic(iter) 174 | if iterFunc.isNil: 175 | let msg = obj.pyType.name & " object is not iterable" 176 | retTuple = (newTypeError(msg), nil) 177 | break body 178 | let iterObj = iterFunc(obj) 179 | let iternextFunc = iterObj.getMagic(iternext) 180 | if iternextFunc.isNil: 181 | let msg = fmt"iter() returned non-iterator of type " & iterObj.pyType.name 182 | retTuple = (newTypeError(msg), nil) 183 | break body 184 | retTuple = (iterobj, iternextFunc) 185 | retTuple 186 | 187 | 188 | template checkArgNum*(expected: int, name="") = 189 | if args.len != expected: 190 | var msg: string 191 | if name != "": 192 | msg = name & " takes exactly " & $expected & fmt" argument ({args.len} given)" 193 | else: 194 | msg = "expected " & $expected & fmt" argument ({args.len} given)" 195 | return newTypeError(msg) 196 | 197 | 198 | template checkArgNumAtLeast*(expected: int, name="") = 199 | if args.len < expected: 200 | var msg: string 201 | if name != "": 202 | msg = name & " takes at least " & $expected & fmt" argument ({args.len} given)" 203 | else: 204 | msg = "expected at least " & $expected & fmt" argument ({args.len} given)" 205 | return newTypeError(msg) 206 | -------------------------------------------------------------------------------- /Objects/exceptionsImpl.nim: -------------------------------------------------------------------------------- 1 | import pyobject 2 | import baseBundle 3 | import tupleobject 4 | import exceptions 5 | import ../Utils/utils 6 | 7 | export exceptions 8 | 9 | macro genMethodMacros: untyped = 10 | result = newStmtList() 11 | for i in ExceptionToken.low..ExceptionToken.high: 12 | let objName = $ExceptionToken(i) & "Error" 13 | result.add(getAst(methodMacroTmpl(ident(objname)))) 14 | 15 | 16 | genMethodMacros 17 | 18 | 19 | template newMagicTmpl(excpName: untyped, excpNameStr: string) = 20 | 21 | `impl excpName ErrorMagic` repr: 22 | # must return pyStringObject, used when formatting traceback 23 | var msg: string 24 | if self.msg.isNil: 25 | msg = "" # could be improved 26 | elif self.msg.ofPyStrObject: 27 | msg = PyStrObject(self.msg).str 28 | else: 29 | # ensure this is either an throwned exception or string for user-defined type 30 | let msgObj = self.msg.callMagic(repr) 31 | if msgObj.isThrownException: 32 | msg = "evaluating __repr__ failed" 33 | else: 34 | msg = PyStrObject(msgObj).str 35 | let str = $self.tk & "Error: " & msg 36 | newPyString(str) 37 | 38 | # this is for initialization at Python level 39 | `impl excpName ErrorMagic` New: 40 | let excp = `newPy excpName ErrorSimple`() 41 | excp.tk = ExceptionToken.`excpName` 42 | excp.msg = newPyTuple(args) 43 | excp 44 | 45 | 46 | macro genNewMagic: untyped = 47 | result = newStmtList() 48 | for i in ExceptionToken.low..ExceptionToken.high: 49 | let tokenStr = $ExceptionToken(i) 50 | result.add(getAst(newMagicTmpl(ident(tokenStr), tokenStr & "Error"))) 51 | 52 | 53 | genNewMagic() 54 | 55 | 56 | proc matchExcp*(target: PyTypeObject, current: PyExceptionObject): PyBoolObject = 57 | var tp = current.pyType 58 | while tp != nil: 59 | if tp == target: 60 | return pyTrueObj 61 | tp = tp.base 62 | pyFalseObj 63 | 64 | 65 | proc isExceptionType*(obj: PyObject): bool = 66 | if not (obj.pyType.kind == PyTypeToken.Type): 67 | return false 68 | let objType = PyTypeObject(obj) 69 | objType.kind == PyTypeToken.BaseError 70 | 71 | 72 | proc fromBltinSyntaxError*(e: SyntaxError, fileName: PyStrObject): PyExceptionObject = 73 | let excpObj = newSyntaxError(e.msg) 74 | # don't have code name 75 | excpObj.traceBacks.add (fileName, nil, e.lineNo, e.colNo) 76 | excpObj 77 | -------------------------------------------------------------------------------- /Objects/frameobject.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | 3 | import pyobject 4 | import baseBundle 5 | import codeobject 6 | import dictobject 7 | import cellobject 8 | import ../Python/opcode 9 | 10 | declarePyType Frame(): 11 | # currently not used? 12 | back: PyFrameObject 13 | code: PyCodeObject 14 | # dicts and sequences for variable lookup 15 | # locals not used for now 16 | # locals*: PyDictObject 17 | globals: PyDictObject 18 | # builtins: PyDictObject 19 | fastLocals: seq[PyObject] 20 | cellVars: seq[PyCellObject] 21 | 22 | 23 | # initialized in neval.nim 24 | proc newPyFrame*: PyFrameObject = 25 | newPyFrameSimple() 26 | 27 | proc toPyDict*(f: PyFrameObject): PyDictObject {. cdecl .} = 28 | result = newPyDict() 29 | let c = f.code 30 | for idx, v in f.fastLocals: 31 | if v.isNil: 32 | continue 33 | result[c.localVars[idx]] = v 34 | let n = c.cellVars.len 35 | for idx, cell in f.cellVars[0.. b.digits.len: 109 | return Positive 110 | for i in countdown(a.digits.len-1, 0): 111 | let ad = a.digits[i] 112 | let bd = b.digits[i] 113 | if ad < bd: 114 | return Negative 115 | elif ad == bd: 116 | continue 117 | else: 118 | return Positive 119 | return Zero 120 | 121 | 122 | proc inplaceAdd(a: PyIntObject, b: Digit) = 123 | var carry = TwoDigits(b) 124 | for i in 0..= dn and dn >= 2 362 | raise newException(IntError, "") 363 | 364 | 365 | proc `div`*(a, b: PyIntObject): PyIntObject = 366 | case a.sign 367 | of Negative: 368 | case b.sign 369 | of Negative: 370 | let (q, r) = doDiv(a, b) 371 | if q.digits.len == 0: 372 | q.sign = Zero 373 | else: 374 | q.sign = Positive 375 | return q 376 | of Zero: 377 | assert false 378 | of Positive: 379 | let (q, r) = doDiv(a, b) 380 | if q.digits.len == 0: 381 | q.sign = Zero 382 | else: 383 | q.sign = Negative 384 | return q 385 | of Zero: 386 | return pyIntZero 387 | of Positive: 388 | case b.sign 389 | of Negative: 390 | let (q, r) = doDiv(a, b) 391 | if q.digits.len == 0: 392 | q.sign = Zero 393 | else: 394 | q.sign = Negative 395 | return q 396 | of Zero: 397 | assert false 398 | of Positive: 399 | let (q, r) = doDiv(a, b) 400 | if q.digits.len == 0: 401 | q.sign = Zero 402 | else: 403 | q.sign = Positive 404 | return q 405 | 406 | # a**b 407 | proc pow(a, b: PyIntObject): PyIntObject = 408 | assert(not b.negative) 409 | if b.zero: 410 | return pyIntOne 411 | let new_b = b div pyIntTwo 412 | let half_c = pow(a, new_b) 413 | if b.digits[0] mod 2 == 1: 414 | return half_c * half_c * a 415 | else: 416 | return half_c * half_c 417 | 418 | #[ 419 | proc newPyInt(i: Digit): PyIntObject = 420 | result = newPyIntSimple() 421 | if i != 0: 422 | result.digits.add i 423 | # can't be negative 424 | if i == 0: 425 | result.sign = Zero 426 | else: 427 | result.sign = Positive 428 | 429 | proc newPyInt(i: int): PyIntObject = 430 | var ii: int 431 | if i < 0: 432 | result = newPyIntSimple() 433 | result.sign = Negative 434 | ii = (not i) + 1 435 | elif i == 0: 436 | return pyIntZero 437 | else: 438 | result = newPyIntSimple() 439 | result.sign = Positive 440 | ii = i 441 | result.digits.add uint32(ii) 442 | result.digits.add uint32(ii shr 32) 443 | result.normalize 444 | ]# 445 | proc fromStr(s: string): PyIntObject = 446 | result = newPyIntSimple() 447 | var start = 0 448 | var sign: IntSign 449 | # assume s not empty 450 | if s[0] == '-': 451 | start = 1 452 | result.digits.add 0 453 | for i in start.. casted.v: 735 | return pyTrueObj 736 | else: 737 | return pyFalseObj 738 | 739 | 740 | implFloatMagic str: 741 | newPyString($self) 742 | 743 | 744 | implFloatMagic repr: 745 | newPyString($self) 746 | 747 | implFloatMagic hash: 748 | newPyInt(hash(self.v)) 749 | 750 | 751 | # used in list and tuple 752 | template getIndex*(obj: PyIntObject, size: int): int = 753 | # todo: if overflow, then thrown indexerror 754 | var idx = obj.toInt 755 | if idx < 0: 756 | idx = size + idx 757 | if (idx < 0) or (size <= idx): 758 | let msg = "index out of range. idx: " & $idx & ", len: " & $size 759 | return newIndexError(msg) 760 | idx 761 | 762 | 763 | when isMainModule: 764 | #let a = fromStr("-1234567623984672384623984712834618623") 765 | #let a = fromStr("3234567890") 766 | #[ 767 | echo a 768 | echo a + a 769 | echo a + a - a 770 | echo a + a - a - a 771 | echo a + a - a - a - a 772 | ]# 773 | #let a = fromStr("88888888888888") 774 | let a = fromStr("100000000000") 775 | echo a.pow(pyIntTen) 776 | echo a 777 | #echo a * pyIntTen 778 | #echo a.pow pyIntTen 779 | #let a = fromStr("100000000000") 780 | #echo a 781 | #echo a * fromStr("7") - a - a - a - a - a - a - a 782 | #let b = newPyInt(2) 783 | #echo pyIntTen 784 | #echo -pyIntTen 785 | #echo a 786 | #echo int(a) 787 | #echo -int(a) 788 | #echo IntSign(-int(a)) 789 | #echo newPyInt(3).pow(pyIntTwo) - pyIntOne + pyIntTwo 790 | #echo a div b 791 | #echo a div b * newPyInt(2) 792 | -------------------------------------------------------------------------------- /Objects/pyobject.nim: -------------------------------------------------------------------------------- 1 | # the object file is devided into two parts: pyobjectBase.nim is for very basic and 2 | # generic pyobject behavior; pyobject.nim is for helpful macros for object method 3 | # definition 4 | import macros except name 5 | import sets 6 | import sequtils 7 | import strformat 8 | import strutils 9 | import hashes 10 | import tables 11 | 12 | import ../Utils/utils 13 | import pyobjectBase 14 | 15 | export macros except name 16 | export pyobjectBase 17 | 18 | 19 | # some helper templates for internal object magics or methods call 20 | 21 | template getMagic*(obj: PyObject, methodName): untyped = 22 | obj.pyType.magicMethods.methodName 23 | 24 | template getFun*(obj: PyObject, methodName: untyped, handleExcp=false): untyped = 25 | if obj.pyType.isNil: 26 | unreachable("Py type not set") 27 | let fun = getMagic(obj, methodName) 28 | if fun.isNil: 29 | let objTypeStr = $obj.pyType.name 30 | let methodStr = astToStr(methodName) 31 | let msg = "No " & methodStr & " method for " & objTypeStr & " defined" 32 | let excp = newTypeError(msg) 33 | when handleExcp: 34 | handleException(excp) 35 | else: 36 | return excp 37 | fun 38 | 39 | 40 | # XXX: `obj` is used twice so it better be a simple identity 41 | # if it's a function then the function is called twice! 42 | 43 | # is there any ways to reduce the repetition? simple template won't work 44 | template callMagic*(obj: PyObject, methodName: untyped, handleExcp=false): PyObject = 45 | let fun = obj.getFun(methodName, handleExcp) 46 | let res = fun(obj) 47 | when handleExcp: 48 | if res.isThrownException: 49 | handleException(res) 50 | res 51 | else: 52 | res 53 | 54 | 55 | template callMagic*(obj: PyObject, methodName: untyped, arg1: PyObject, handleExcp=false): PyObject = 56 | let fun = obj.getFun(methodName, handleExcp) 57 | let res = fun(obj, arg1) 58 | when handleExcp: 59 | if res.isThrownException: 60 | handleException(res) 61 | res 62 | else: 63 | res 64 | 65 | template callMagic*(obj: PyObject, methodName: untyped, 66 | arg1, arg2: PyObject, handleExcp=false): PyObject = 67 | let fun = obj.getFun(methodName, handleExcp) 68 | let res = fun(obj, arg1, arg2) 69 | when handleExcp: 70 | if res.isThrownException: 71 | handleException(res) 72 | res 73 | else: 74 | res 75 | 76 | 77 | # get proc name according to type (e.g. `Dict`) and method name (e.g. `repr`) 78 | macro tpMagic*(tp, methodName: untyped): untyped = 79 | ident(methodName.strVal.toLowerAscii & "Py" & tp.strVal & "ObjectMagic") 80 | 81 | macro tpMethod*(tp, methodName: untyped): untyped = 82 | ident(methodName.strVal.toLowerAscii & "Py" & tp.strVal & "ObjectMethod") 83 | 84 | macro tpGetter*(tp, methodName: untyped): untyped = 85 | ident(methodName.strVal.toLowerAscii & "Py" & tp.strVal & "ObjectGetter") 86 | 87 | macro tpSetter*(tp, methodName: untyped): untyped = 88 | ident(methodName.strVal.toLowerAscii & "Py" & tp.strVal & "ObjectSetter") 89 | 90 | proc registerBltinMethod*(t: PyTypeObject, name: string, fun: BltinMethod) = 91 | if t.bltinMethods.hasKey(name): 92 | unreachable(fmt"Method {name} is registered twice for type {t.name}") 93 | t.bltinMethods[name] = fun 94 | 95 | 96 | # assert self type then cast 97 | macro castSelf*(ObjectType: untyped, code: untyped): untyped = 98 | code.body = newStmtList( 99 | nnkCommand.newTree( 100 | ident("assert"), 101 | nnkInfix.newTree( 102 | ident("of"), 103 | ident("selfNoCast"), 104 | ObjectType 105 | ) 106 | ), 107 | newLetStmt( 108 | ident("self"), 109 | newCall(ObjectType, ident("selfNoCast")) 110 | ), 111 | code.body 112 | ) 113 | code 114 | 115 | let unaryMethodParams {. compileTime .} = @[ 116 | ident("PyObject"), # return type 117 | newIdentDefs(ident("selfNoCast"), ident("PyObject")), # first arg, self 118 | ] 119 | 120 | let binaryMethodParams {. compileTime .} = unaryMethodParams & @[ 121 | newIdentDefs(ident("other"), ident("PyObject")), # second arg, other 122 | ] 123 | 124 | let ternaryMethodParams {. compileTime .} = unaryMethodParams & @[ 125 | newIdentDefs(ident("arg1"), ident("PyObject")), 126 | newIdentDefs(ident("arg2"), ident("PyObject")), 127 | ] 128 | 129 | let bltinMethodParams {. compileTime .} = unaryMethodParams & @[ 130 | newIdentDefs( 131 | ident("args"), 132 | nnkBracketExpr.newTree(ident("seq"), ident("PyObject")), 133 | nnkPrefix.newTree( # default arg 134 | ident("@"), 135 | nnkBracket.newTree() 136 | ) 137 | ), 138 | ] 139 | 140 | # used in bltinmodule.nim 141 | let bltinFuncParams* {. compileTime .} = @[ 142 | ident("PyObject"), # return type 143 | newIdentDefs( 144 | ident("args"), 145 | nnkBracketExpr.newTree(ident("seq"), ident("PyObject")), 146 | nnkPrefix.newTree( # default arg 147 | ident("@"), 148 | nnkBracket.newTree() 149 | ) 150 | ), 151 | ] 152 | 153 | proc getParams(methodName: NimNode): seq[NimNode] = 154 | var m: MagicMethods 155 | # the loop is no doubt slow, however we are at compile time and this won't cost 156 | # 1ms during the entire compile process on mordern CPU 157 | for name, tp in m.fieldPairs: 158 | if name == methodName.strVal: 159 | if tp is UnaryMethod: 160 | return unaryMethodParams 161 | elif tp is BinaryMethod: 162 | return binaryMethodParams 163 | elif tp is TernaryMethod: 164 | return ternaryMethodParams 165 | elif tp is BltinMethod: 166 | return bltinMethodParams 167 | elif tp is BltinFunc: 168 | return bltinFuncParams 169 | else: 170 | unreachable 171 | error(fmt"method name {methodName.strVal} is not magic method") 172 | 173 | 174 | proc objName2tpObjName(objName: string): string {. compileTime .} = 175 | result = objName & "Type" 176 | result[0] = result[0].toLowerAscii 177 | 178 | # example here: For a definition like `i: PyIntObject` 179 | # obj: i 180 | # tp: PyIntObject like 181 | # tpObj: pyIntObjectType like 182 | template checkTypeTmpl(obj, tp, tpObj, methodName) = 183 | # should use a more sophisticated way to judge type 184 | if not (obj of tp): 185 | let expected {. inject .} = tpObj.name 186 | let got {. inject .}= obj.pyType.name 187 | let mName {. inject .}= methodName 188 | let msg = fmt"{expected} is requred for {mName} (got {got})" 189 | return newTypeError(msg) 190 | 191 | 192 | macro checkArgTypes*(nameAndArg, code: untyped): untyped = 193 | let methodName = nameAndArg[0] 194 | var argTypes = nameAndArg[1] 195 | let body = newStmtList() 196 | var varargName: string 197 | if (argTypes.len != 0) and (argTypes[^1].kind == nnkPrefix): 198 | let varargs = argTypes[^1] 199 | assert varargs[0].strVal == "*" 200 | varargName = varargs[1].strVal 201 | let argNum = argTypes.len 202 | if varargName == "": 203 | # return `checkArgNum(1, "append")` like 204 | body.add newCall(ident("checkArgNum"), 205 | newIntLitNode(argNum), 206 | newStrLitNode(methodName.strVal) 207 | ) 208 | else: 209 | body.add newCall(ident("checkArgNumAtLeast"), 210 | newIntLitNode(argNum - 1), 211 | newStrLitNode(methodName.strVal) 212 | ) 213 | let remainingArgNode = ident(varargname) 214 | body.add(quote do: 215 | let `remainingArgNode` = args[`argNum`-1..^1] 216 | ) 217 | 218 | 219 | for idx, child in argTypes: 220 | if child.kind == nnkPrefix: 221 | continue 222 | let obj = nnkBracketExpr.newTree( 223 | ident("args"), 224 | newIntLitNode(idx), 225 | ) 226 | let name = child[0] 227 | let tp = child[1] 228 | if tp.strVal == "PyObject": # won't bother checking 229 | body.add(quote do: 230 | let `name` = `obj` 231 | ) 232 | else: 233 | let tpObj = ident(objName2tpObjName(tp.strVal)) 234 | let methodNameStrNode = newStrLitNode(methodName.strVal) 235 | body.add(getAst(checkTypeTmpl(obj, tp, tpObj, methodNameStrNode))) 236 | body.add(quote do: 237 | let `name` = `tp`(`obj`) 238 | ) 239 | body.add(code.body) 240 | code.body = body 241 | code 242 | 243 | 244 | # works with thingks like `append(obj: PyObject)` 245 | # if no parenthesis, then return nil as argTypes, means do not check arg type 246 | proc getNameAndArgTypes*(prototype: NimNode): (NimNode, NimNode) = 247 | if prototype.kind == nnkIdent or prototype.kind == nnkSym: 248 | return (prototype, nil) 249 | let argTypes = nnkPar.newTree() 250 | let methodName = prototype[0] 251 | if prototype.kind == nnkObjConstr: 252 | for i in 1..") 35 | 36 | implTypeGetter dict: 37 | newPyDictProxy(self.dict) 38 | 39 | implTypeSetter dict: 40 | newTypeError(fmt"can't set attributes of built-in/extension type {self.name}") 41 | 42 | pyTypeObjectType.getsetDescr["__dict__"] = (tpGetter(Type, dict), tpSetter(Type, dict)) 43 | 44 | proc getTypeDict*(obj: PyObject): PyDictObject = 45 | PyDictObject(obj.pyType.dict) 46 | 47 | # some generic behaviors that every type should obey 48 | proc defaultLe(o1, o2: PyObject): PyObject {. cdecl .} = 49 | let lt = o1.callMagic(lt, o2) 50 | let eq = o1.callMagic(eq, o2) 51 | lt.callMagic(Or, eq) 52 | 53 | proc defaultNe(o1, o2: PyObject): PyObject {. cdecl .} = 54 | let eq = o1.callMagic(eq, o2) 55 | eq.callMagic(Not) 56 | 57 | proc defaultGe(o1, o2: PyObject): PyObject {. cdecl .} = 58 | let gt = o1.callMagic(gt, o2) 59 | let eq = o1.callMagic(eq, o2) 60 | gt.callMagic(Or, eq) 61 | 62 | proc reprDefault(self: PyObject): PyObject {. cdecl .} = 63 | newPyString(fmt"<{self.pyType.name} at {self.idStr}>") 64 | 65 | # generic getattr 66 | proc getAttr(self: PyObject, nameObj: PyObject): PyObject {. cdecl .} = 67 | if not nameObj.ofPyStrObject: 68 | let typeStr = nameObj.pyType.name 69 | let msg = fmt"attribute name must be string, not {typeStr}" 70 | return newTypeError(msg) 71 | let name = PyStrObject(nameObj) 72 | let typeDict = self.getTypeDict 73 | if typeDict.isNil: 74 | unreachable("for type object dict must not be nil") 75 | var descr: PyObject 76 | if typeDict.hasKey(name): 77 | descr = typeDict[name] 78 | let descrGet = descr.pyType.magicMethods.get 79 | if not descrGet.isNil: 80 | return descr.descrGet(self) 81 | 82 | if self.hasDict: 83 | let instDict = PyDictObject(self.getDict) 84 | if instDict.hasKey(name): 85 | return instDict[name] 86 | 87 | if not descr.isNil: 88 | return descr 89 | 90 | return newAttributeError($self.pyType.name, $name) 91 | 92 | # generic getattr 93 | proc setAttr(self: PyObject, nameObj: PyObject, value: PyObject): PyObject {. cdecl .} = 94 | if not nameObj.ofPyStrObject: 95 | let typeStr = nameObj.pyType.name 96 | let msg = fmt"attribute name must be string, not {typeStr}" 97 | return newTypeError(msg) 98 | let name = PyStrObject(nameObj) 99 | let typeDict = self.getTypeDict 100 | if typeDict.isNil: 101 | unreachable("for type object dict must not be nil") 102 | var descr: PyObject 103 | if typeDict.hasKey(name): 104 | descr = typeDict[name] 105 | let descrSet = descr.pyType.magicMethods.set 106 | if not descrSet.isNil: 107 | return descr.descrSet(self, value) 108 | 109 | if self.hasDict: 110 | let instDict = PyDictObject(self.getDict) 111 | instDict[name] = value 112 | return pyNone 113 | 114 | return newAttributeError($self.pyType.name, $name) 115 | 116 | 117 | proc addGeneric(t: PyTypeObject) = 118 | template nilMagic(magicName): bool = 119 | t.magicMethods.magicName.isNil 120 | 121 | template trySetSlot(magicName, defaultMethod) = 122 | if nilMagic(magicName): 123 | t.magicMethods.magicName = defaultMethod 124 | 125 | if (not nilMagic(lt)) and (not nilMagic(eq)): 126 | trySetSlot(le, defaultLe) 127 | if (not nilMagic(eq)): 128 | trySetSlot(ne, defaultNe) 129 | if (not nilMagic(ge)) and (not nilMagic(eq)): 130 | trySetSlot(ge, defaultGe) 131 | trySetSlot(getattr, getAttr) 132 | trySetSlot(setattr, setAttr) 133 | trySetSlot(repr, reprDefault) 134 | trySetSlot(str, t.magicMethods.repr) 135 | 136 | 137 | # for internal objects 138 | proc initTypeDict(tp: PyTypeObject) = 139 | assert tp.dict.isNil 140 | let d = newPyDict() 141 | # magic methods. field loop syntax is pretty weird 142 | # no continue, no enumerate 143 | var i = -1 144 | for meth in tp.magicMethods.fields: 145 | inc i 146 | if not meth.isNil: 147 | let namePyStr = magicNameStrs[i] 148 | if meth is BltinFunc: 149 | d[namePyStr] = newPyStaticMethod(newPyNimFunc(meth, namePyStr)) 150 | else: 151 | d[namePyStr] = newPyMethodDescr(tp, meth, namePyStr) 152 | # getset descriptors. 153 | for key, value in tp.getsetDescr.pairs: 154 | let getter = value[0] 155 | let setter = value[1] 156 | let descr = newPyGetSetDescr(getter, setter) 157 | let namePyStr = newPyStr(key) 158 | d[namePyStr] = descr 159 | 160 | # bltin methods 161 | for name, meth in tp.bltinMethods.pairs: 162 | let namePyStr = newPyString(name) 163 | d[namePyStr] = newPyMethodDescr(tp, meth, namePyStr) 164 | 165 | tp.dict = d 166 | 167 | proc typeReady*(tp: PyTypeObject) = 168 | tp.pyType = pyTypeObjectType 169 | tp.addGeneric 170 | if tp.dict.isNil: 171 | tp.initTypeDict 172 | 173 | pyTypeObjectType.typeReady() 174 | 175 | 176 | implTypeMagic call: 177 | # quoting CPython: "ugly exception". 178 | # Deal with `type("abc") == str`. What a design failure. 179 | if (self == pyTypeObjectType) and (args.len == 1): 180 | return args[0].pyType 181 | 182 | let newFunc = self.magicMethods.New 183 | if newFunc.isNil: 184 | let msg = fmt"cannot create '{self.name}' instances because __new__ is not set" 185 | return newTypeError(msg) 186 | let newObj = newFunc(@[PyObject(self)] & args) 187 | if newObj.isThrownException: 188 | return newObj 189 | let initFunc = self.magicMethods.init 190 | if not initFunc.isNil: 191 | let initRet = initFunc(newObj, args) 192 | if initRet.isThrownException: 193 | return initRet 194 | # otherwise discard 195 | return newObj 196 | 197 | 198 | # create user defined class 199 | # As long as relying on Nim GC it's hard to do something like variable length object 200 | # as in CPython, so we have to use a somewhat traditional and clumsy way 201 | # The type declared here is never used, it's needed as a placeholder to declare magic 202 | # methods. 203 | declarePyType Instance(dict): 204 | discard 205 | 206 | 207 | # todo: should move to the base object when inheritance and mro is ready 208 | # todo: should support more complicated arg declaration 209 | implInstanceMagic New(tp: PyTypeObject, *actualArgs): 210 | result = newPyInstanceSimple() 211 | result.pyType = tp 212 | 213 | template instanceUnaryMethodTmpl(idx: int, nameIdent: untyped) = 214 | implInstanceMagic nameIdent: 215 | let magicNameStr = magicNameStrs[idx] 216 | let fun = self.getTypeDict[magicNameStr] 217 | return fun.fastCall(@[PyObject(self)]) 218 | 219 | template instanceBinaryMethodTmpl(idx: int, nameIdent: untyped) = 220 | implInstanceMagic nameIdent: 221 | let magicNameStr = magicNameStrs[idx] 222 | let fun = self.getTypeDict[magicNameStr] 223 | return fun.fastCall(@[PyObject(self), other]) 224 | 225 | template instanceTernaryMethodTmpl(idx: int, nameIdent: untyped) = 226 | implInstanceMagic nameIdent: 227 | let magicNameStr = magicNameStrs[idx] 228 | let fun = self.getTypeDict[magicNameStr] 229 | return fun.fastCall(@[PyObject(self), arg1, arg2]) 230 | 231 | template instanceBltinFuncTmpl(idx: int, nameIdent: untyped) = 232 | implInstanceMagic nameIdent: 233 | let magicNameStr = magicNameStrs[idx] 234 | let fun = self.getTypeDict[magicNameStr] 235 | return fun.fastCall(args) 236 | 237 | template instanceBltinMethodTmpl(idx: int, nameIdent: untyped) = 238 | implInstanceMagic nameIdent: 239 | let magicNameStr = magicNameStrs[idx] 240 | let fun = self.getTypeDict[magicNameStr] 241 | return fun.fastCall(@[PyObject(self)] & args) 242 | 243 | macro implInstanceMagics: untyped = 244 | result = newStmtList() 245 | var idx = -1 246 | var m: MagicMethods 247 | for name, v in m.fieldpairs: 248 | inc idx 249 | # no `continue` can be used... 250 | if name != "New": 251 | if v is UnaryMethod: 252 | result.add getAst(instanceUnaryMethodTmpl(idx, ident(name))) 253 | elif v is BinaryMethod: 254 | result.add getAst(instanceBinaryMethodTmpl(idx, ident(name))) 255 | elif v is TernaryMethod: 256 | result.add getAst(instanceTernaryMethodTmpl(idx, ident(name))) 257 | elif v is BltinFunc: 258 | result.add getAst(instanceBltinFuncTmpl(idx, ident(name))) 259 | elif v is BltinMethod: 260 | result.add getAst(instanceBltinMethodTmpl(idx, ident(name))) 261 | else: 262 | assert false 263 | 264 | implInstanceMagics 265 | 266 | template updateSlotTmpl(idx: int, slotName: untyped) = 267 | let magicNameStr = magicNameStrs[idx] 268 | if dict.hasKey(magicnameStr): 269 | tp.magicMethods.`slotName` = tpMagic(Instance, slotname) 270 | 271 | macro updateSlots(tp: PyTypeObject, dict: PyDictObject): untyped = 272 | result = newStmtList() 273 | var idx = -1 274 | var m: MagicMethods 275 | for name, v in m.fieldpairs: 276 | inc idx 277 | result.add getAst(updateSlotTmpl(idx, ident(name))) 278 | 279 | implTypeMagic New(metaType: PyTypeObject, name: PyStrObject, 280 | bases: PyTupleObject, dict: PyDictObject): 281 | assert metaType == pyTypeObjectType 282 | assert bases.len == 0 283 | let tp = newPyType(name.str) 284 | tp.kind = PyTypeToken.Type 285 | tp.magicMethods.New = tpMagic(Instance, new) 286 | updateSlots(tp, dict) 287 | tp.dict = PyDictObject(tpMethod(Dict, copy)(dict)) 288 | tp.typeReady 289 | tp 290 | -------------------------------------------------------------------------------- /Parser/Grammar: -------------------------------------------------------------------------------- 1 | # Grammar for Python 2 | 3 | # NOTE WELL: You should also follow all the steps listed at 4 | # https://devguide.python.org/grammar/ 5 | 6 | # Start symbols for the grammar: 7 | # single_input is a single interactive statement; 8 | # file_input is a module or sequence of commands read from an input file; 9 | # eval_input is the input for the eval() functions. 10 | # NB: compound_stmt in single_input is followed by extra NEWLINE! 11 | single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE 12 | file_input: (NEWLINE | stmt)* ENDMARKER 13 | eval_input: testlist NEWLINE* ENDMARKER 14 | 15 | decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE 16 | decorators: decorator+ 17 | decorated: decorators (classdef | funcdef | async_funcdef) 18 | 19 | async_funcdef: 'async' funcdef 20 | funcdef: 'def' NAME parameters ['->' test] ':' suite 21 | 22 | parameters: '(' [typedargslist] ')' 23 | typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [ 24 | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] 25 | | '**' tfpdef [',']]] 26 | | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] 27 | | '**' tfpdef [',']) 28 | tfpdef: NAME [':' test] 29 | varargslist: (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [ 30 | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] 31 | | '**' vfpdef [',']]] 32 | | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] 33 | | '**' vfpdef [','] 34 | ) 35 | vfpdef: NAME 36 | 37 | stmt: simple_stmt | compound_stmt 38 | simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE 39 | small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt | 40 | import_stmt | global_stmt | nonlocal_stmt | assert_stmt) 41 | expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | 42 | ('=' (yield_expr|testlist_star_expr))*) 43 | annassign: ':' test ['=' test] 44 | testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [','] 45 | augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | 46 | '<<=' | '>>=' | '**=' | '//=') 47 | # For normal and annotated assignments, additional restrictions enforced by the interpreter 48 | del_stmt: 'del' exprlist 49 | pass_stmt: 'pass' 50 | flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt 51 | break_stmt: 'break' 52 | continue_stmt: 'continue' 53 | return_stmt: 'return' [testlist] 54 | yield_stmt: yield_expr 55 | raise_stmt: 'raise' [test ['from' test]] 56 | import_stmt: import_name | import_from 57 | import_name: 'import' dotted_as_names 58 | # note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS 59 | import_from: ('from' (('.' | '...')* dotted_name | ('.' | '...')+) 60 | 'import' ('*' | '(' import_as_names ')' | import_as_names)) 61 | import_as_name: NAME ['as' NAME] 62 | dotted_as_name: dotted_name ['as' NAME] 63 | import_as_names: import_as_name (',' import_as_name)* [','] 64 | dotted_as_names: dotted_as_name (',' dotted_as_name)* 65 | dotted_name: NAME ('.' NAME)* 66 | global_stmt: 'global' NAME (',' NAME)* 67 | nonlocal_stmt: 'nonlocal' NAME (',' NAME)* 68 | assert_stmt: 'assert' test [',' test] 69 | 70 | compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt 71 | async_stmt: 'async' (funcdef | with_stmt | for_stmt) 72 | if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] 73 | while_stmt: 'while' test ':' suite ['else' ':' suite] 74 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] 75 | try_stmt: ('try' ':' suite 76 | ((except_clause ':' suite)+ 77 | ['else' ':' suite] 78 | ['finally' ':' suite] | 79 | 'finally' ':' suite)) 80 | with_stmt: 'with' with_item (',' with_item)* ':' suite 81 | with_item: test ['as' expr] 82 | # NB compile.c makes sure that the default except clause is last 83 | except_clause: 'except' [test ['as' NAME]] 84 | suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT 85 | 86 | test: or_test ['if' or_test 'else' test] | lambdef 87 | test_nocond: or_test | lambdef_nocond 88 | lambdef: 'lambda' [varargslist] ':' test 89 | lambdef_nocond: 'lambda' [varargslist] ':' test_nocond 90 | or_test: and_test ('or' and_test)* 91 | and_test: not_test ('and' not_test)* 92 | not_test: 'not' not_test | comparison 93 | comparison: expr (comp_op expr)* 94 | # <> isn't actually a valid comparison operator in Python. It's here for the 95 | # sake of a __future__ import described in PEP 401 (which really works :-) 96 | comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' 97 | star_expr: '*' expr 98 | expr: xor_expr ('|' xor_expr)* 99 | xor_expr: and_expr ('^' and_expr)* 100 | and_expr: shift_expr ('&' shift_expr)* 101 | shift_expr: arith_expr (('<<'|'>>') arith_expr)* 102 | arith_expr: term (('+'|'-') term)* 103 | term: factor (('*'|'@'|'/'|'%'|'//') factor)* 104 | factor: ('+'|'-'|'~') factor | power 105 | power: atom_expr ['**' factor] 106 | atom_expr: ['await'] atom trailer* 107 | atom: ('(' [yield_expr|testlist_comp] ')' | 108 | '[' [testlist_comp] ']' | 109 | '{' [dictorsetmaker] '}' | 110 | NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') 111 | testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) 112 | trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME 113 | subscriptlist: subscript (',' subscript)* [','] 114 | subscript: test | [test] ':' [test] [sliceop] 115 | sliceop: ':' [test] 116 | exprlist: (expr|star_expr) (',' (expr|star_expr))* [','] 117 | testlist: test (',' test)* [','] 118 | dictorsetmaker: ( ((test ':' test | '**' expr) 119 | (comp_for | (',' (test ':' test | '**' expr))* [','])) | 120 | ((test | star_expr) 121 | (comp_for | (',' (test | star_expr))* [','])) ) 122 | 123 | classdef: 'class' NAME ['(' [arglist] ')'] ':' suite 124 | 125 | arglist: argument (',' argument)* [','] 126 | 127 | # The reason that keywords are test nodes instead of NAME is that using NAME 128 | # results in an ambiguity. ast.c makes sure it's a NAME. 129 | # "test '=' test" is really "keyword '=' test", but we have no such token. 130 | # These need to be in a single rule to avoid grammar that is ambiguous 131 | # to our LL(1) parser. Even though 'test' includes '*expr' in star_expr, 132 | # we explicitly match '*' here, too, to give it proper precedence. 133 | # Illegal combinations and orderings are blocked in ast.c: 134 | # multiple (test comp_for) arguments are blocked; keyword unpackings 135 | # that precede iterable unpackings are blocked; etc. 136 | argument: ( test [comp_for] | 137 | test '=' test | 138 | '**' test | 139 | '*' test ) 140 | 141 | comp_iter: comp_for | comp_if 142 | sync_comp_for: 'for' exprlist 'in' or_test [comp_iter] 143 | comp_for: ['async'] sync_comp_for 144 | comp_if: 'if' test_nocond [comp_iter] 145 | 146 | # not used in grammar, but may appear in "node" passed from Parser to Compiler 147 | encoding_decl: NAME 148 | 149 | yield_expr: 'yield' [yield_arg] 150 | yield_arg: 'from' test | testlist 151 | -------------------------------------------------------------------------------- /Parser/grammar.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import algorithm 3 | import hashes 4 | import parseutils 5 | import sequtils 6 | import strformat 7 | import sets 8 | import tables 9 | import deques 10 | 11 | import token 12 | import ../Utils/[utils, compat] 13 | 14 | 15 | type 16 | Repeat* {.pure.} = enum 17 | None 18 | Star 19 | Plus 20 | Query 21 | 22 | GrammarNode* = ref object 23 | id: int 24 | father*: GrammarNode 25 | repeat*: Repeat 26 | epsilonSet*: HashSet[GrammarNode] # grammar nodes reachable by \epsilon operaton 27 | case kind*: char 28 | of 'a', 's': # terminal and dummy sentinel 29 | token*: Token 30 | nextSet*: HashSet[GrammarNode] 31 | of 'A'..'H': # non-terminal 32 | children*: seq[GrammarNode] 33 | else: # + * ? 34 | discard 35 | 36 | Grammar = ref object 37 | token: Token 38 | grammarString: string 39 | rootNode*: GrammarNode 40 | firstSet*: set[Token] 41 | cursor: int 42 | 43 | 44 | const grammarLines = slurp("Grammar").split("\n") 45 | 46 | 47 | var 48 | grammarSet* = initTable[Token, Grammar]() 49 | firstSet = initTable[Token, set[Token]]() 50 | 51 | proc `$`*(grammarNode: GrammarNode): string 52 | 53 | # Grammar of EBNF used in python 54 | # ast: a seq of B with E as attribute, if only one element, factored to one B with E 55 | # A -> B E H 56 | # B -> C | D | a ast: C or D or reserved name or grammar token 57 | # C -> '[' F ']' ast: a seq of A by expanding F 58 | # ast: same as above, rename to F, D info in attribute 59 | # D -> '(' F ')' 60 | # E -> + | * | \epsilon 61 | # F -> A G ast: a seq of A, if only one element, factored 62 | # G -> '|' A G | \epsilon ast: a seq of A 63 | # H -> A | \epsilon ast: nil or single A 64 | # 65 | # Resulting AST only contains A F and a (sometimes with s as sentinel) 66 | # A stands for a sequence of stmt 67 | # F stands for an Or sequence 68 | # a is (grammar) terminator 69 | 70 | proc matchA(grammar: Grammar): GrammarNode 71 | proc matchB(grammar: Grammar): GrammarNode 72 | proc matchC(grammar: Grammar): GrammarNode 73 | proc matchD(grammar: Grammar): GrammarNode 74 | proc matchE(grammar: Grammar): GrammarNode 75 | proc matchF(grammar: Grammar): GrammarNode 76 | proc matchG(grammar: Grammar): GrammarNode 77 | proc matchH(grammar: Grammar): GrammarNode 78 | 79 | proc newGrammarNode(name: string, tokenString=""): GrammarNode 80 | proc hash(node: GrammarNode): Hash 81 | proc assignId(node: GrammarNode) 82 | proc nextInTree(node: GrammarNode): HashSet[GrammarNode] 83 | proc isOptional*(node: GrammarNode): bool 84 | proc isGrammarTerminator*(node: GrammarNode): bool 85 | proc childTerminator(node: GrammarNode): GrammarNode 86 | proc genEpsilonSet(root: GrammarNode) 87 | proc genNextSet(root: GrammarNode) 88 | proc matchToken*(node: GrammarNode, token: Token): bool 89 | 90 | let successGrammarNode* = newGrammarNode("s") # sentinel 91 | 92 | proc newGrammarNode(name: string, tokenString=""): GrammarNode = 93 | new result 94 | result.epsilonSet = initSet[GrammarNode]() 95 | case name[0] 96 | of 'A'..'H', '+', '?', '*': 97 | result.kind = name[0] 98 | of 'a': 99 | result.kind = 'a' 100 | result.token = strTokenMap[tokenString] 101 | result.nextSet = initSet[GrammarNode]() 102 | of 's': # finish sentinel 103 | result.kind = 's' 104 | result.nextSet = initSet[GrammarNode]() 105 | else: 106 | raise newException(ValueError, fmt"unknown name: {name}") 107 | 108 | 109 | # not to confuse with token terminator 110 | proc isGrammarTerminator(node: GrammarNode): bool = 111 | node.kind == 'a' or node.kind == 's' 112 | 113 | 114 | proc newGrammar(name: string, grammarString: string): Grammar = 115 | new result 116 | result.token = strTokenMap[name] 117 | result.grammarString = grammarString 118 | result.rootNode = matchF(result) 119 | # eliminate non-terminators as root (which happens in cases such as 120 | # pass_stmt). The following procedure will be simpler this way 121 | assert (not result.rootNode.isGrammarTerminator()) 122 | result.cursor = 0 123 | result.rootNode.assignId 124 | result.rootNode.genEpsilonSet 125 | result.rootNode.genNextSet 126 | 127 | 128 | proc childTerminator(node: GrammarNode): GrammarNode = 129 | if node.isGrammarTerminator: 130 | return node 131 | return childTerminator(node.children[0]) 132 | 133 | proc nextInTree(node: GrammarNode): HashSet[GrammarNode] = 134 | result = initSet[GrammarNode]() 135 | var curNode = node 136 | while true: 137 | let father = curNode.father 138 | if father == nil: 139 | result.incl(successGrammarNode) 140 | break 141 | case father.kind 142 | of 'F': 143 | if father.repeat == Repeat.Plus or father.repeat == Repeat.Star: 144 | result.incl(father) 145 | curNode = father 146 | of 'A': 147 | let idx = father.children.find(curNode) 148 | assert idx != -1 149 | if idx == father.children.len - 1: 150 | if father.repeat == Repeat.Plus or father.repeat == Repeat.Star: 151 | result.incl(father) 152 | curNode = father 153 | else: 154 | result.incl(father.children[idx+1]) 155 | break 156 | else: 157 | echo curNode 158 | echo father 159 | assert false 160 | 161 | proc assignId(node: GrammarNode) = 162 | var toVisit = initDeque[GrammarNode]() 163 | toVisit.addLast(node) 164 | var idx = 1 165 | while 0 < toVisit.len: 166 | var node = toVisit.popFirst 167 | node.id = idx 168 | inc idx 169 | if node.isGrammarTerminator: 170 | continue 171 | for child in node.children: 172 | toVisit.addLast(child) 173 | 174 | 175 | proc genEpsilonSet(root: GrammarNode) = 176 | var toVisit = @[root] 177 | var allNode = initSet[GrammarNode]() 178 | # collect direct epsilon set. Reachable by a single epsilon 179 | while 0 < toVisit.len: 180 | let curNode = toVisit.pop 181 | allNode.incl(curNode) 182 | case curNode.kind 183 | of 'F': 184 | for child in curNode.children: 185 | curNode.epsilonSet.incl(child) 186 | of 'A': 187 | curNode.epsilonSet.incl(curNode.children[0]) 188 | if curNode.isOptional: 189 | curNode.epsilonSet.incl(curNode.nextInTree) 190 | for child in curNode.children: 191 | if child.isOptional: 192 | child.epsilonSet.incl(child.nextInTree) 193 | of 'a': 194 | discard # no need to do anything 195 | else: 196 | echo curNode.kind 197 | assert false 198 | if not curNode.isGrammarTerminator: 199 | for child in curNode.children: 200 | toVisit.add(child) 201 | 202 | # collect epsilons of member of epsilon set recursively 203 | var collected = initSet[GrammarNode]() 204 | collected.incl(successGrammarNode) 205 | for curNode in allNode: 206 | if not collected.contains(curNode): 207 | toVisit.add(curNode) 208 | while 0 < toVisit.len: 209 | let curNode = toVisit.pop 210 | if curNode.epsilonSet.len == 0: 211 | collected.incl(curNode) 212 | continue 213 | var allChildrenCollected = true 214 | for child in curNode.epsilonSet: 215 | if not collected.contains(child): 216 | allChildrenCollected = false 217 | break 218 | if allChildrenCollected: 219 | for child in curNode.epsilonSet: 220 | curNode.epsilonSet.incl(child.epsilonSet) 221 | collected.incl(curNode) 222 | else: 223 | toVisit.add(curNode) 224 | for child in curNode.epsilonSet: 225 | if not collected.contains(child): 226 | toVisit.add(child) 227 | 228 | # exclude 'A' and 'F' in epsilon set 229 | if curNode.epsilonSet.len == 0: 230 | continue 231 | var toExclude = initSet[GrammarNode]() 232 | for child in curNode.epsilonSet: 233 | case child.kind 234 | of 'A', 'F': 235 | toExclude.incl(child) 236 | else: 237 | discard 238 | curNode.epsilonSet.excl(toExclude) 239 | 240 | proc genNextSet(root: GrammarNode) = 241 | 242 | # next set for 'A' and 'F' not accurate. we don't rely on them, use epsilon set instead 243 | var toVisit: seq[GrammarNode] 244 | # root.nextSet.incl(successGrammarNode) 245 | if not root.isGrammarTerminator: 246 | # toVisit &= root.children 247 | toVisit.addCompat root.children 248 | while 0 < toVisit.len: 249 | let curNode = toVisit.pop 250 | if not curNode.isGrammarTerminator: 251 | toVisit &= curNode.children 252 | continue 253 | 254 | var nextNodes = curNode.nextInTree 255 | if curNode.repeat == Repeat.Plus or curNode.repeat == Repeat.Star: 256 | nextNodes.incl(curNode) 257 | 258 | for nextNode in nextNodes: 259 | for epsilonNextNode in nextNode.epsilonSet: 260 | curNode.nextSet.incl(epsilonNextNode) 261 | case nextNode.kind 262 | of 'A', 'F': # no need to add the node to next set 263 | continue 264 | of 'a', 's': 265 | curNode.nextSet.incl(nextNode) 266 | else: 267 | assert false 268 | 269 | proc errorGrammar(grammar: Grammar) = 270 | let 271 | s = grammar.grammarString 272 | c = grammar.cursor 273 | 274 | let msg = fmt"invalid syntax for {s[0.." 313 | of 'A', 'F': 314 | let epsilonSet = sorted(toSeq(grammarNode.epsilonSet.map(mapProc).items), cmp).join(", ") 315 | tail &= fmt"<{epsilonSet}>" 316 | else: 317 | discard 318 | var name: string 319 | case grammarNode.kind 320 | of 'a': 321 | name = $grammarNode.token 322 | else: 323 | name = $grammarNode.kind 324 | stringSeq.add(head & name & tail) 325 | if not grammarNode.isGrammarTerminator: 326 | for child in grammarNode.children: 327 | if child == nil: 328 | continue 329 | for substr in split($(child), "\n"): 330 | stringSeq.add(" " & substr) 331 | result = stringSeq.join("\n") 332 | 333 | 334 | proc `$`*(grammar: Grammar): string = 335 | result = [$grammar.token & " " & grammar.grammarString, $(grammar.rootNode)].join("\n") 336 | 337 | 338 | proc getChar(grammar: Grammar): char = 339 | grammar.cursor.inc grammar.grammarString.skipWhitespace(grammar.cursor) 340 | result = grammar.grammarString[grammar.cursor] 341 | 342 | 343 | proc exhausted(grammar: Grammar): bool = 344 | result = grammar.cursor == len(grammar.grammarString) 345 | 346 | 347 | proc matchA(grammar: Grammar): GrammarNode = 348 | var 349 | b = matchB(grammar) 350 | e = matchE(grammar) 351 | h = matchH(grammar) 352 | if e != nil: 353 | case e.kind 354 | of '+': 355 | assert b.repeat == Repeat.None 356 | b.repeat = Repeat.Plus 357 | of '*': 358 | assert b.repeat == Repeat.None 359 | b.repeat = Repeat.Star 360 | else: 361 | assert false 362 | if h != nil: 363 | result = newGrammarNode("A") 364 | result.addChild(b) 365 | if h.repeat != Repeat.None or h.kind == 'F' or h.isGrammarTerminator: 366 | result.addChild(h) 367 | else: # basically it's a simple A 368 | for child in h.children: 369 | result.addChild(child) 370 | else: 371 | result = b 372 | 373 | 374 | proc matchB(grammar: Grammar): GrammarNode = 375 | case grammar.getChar() 376 | of '[': 377 | result = matchC(grammar) 378 | of '(': 379 | result = matchD(grammar) 380 | of '\'': 381 | inc(grammar.cursor) 382 | var prev = grammar.cursor 383 | grammar.cursor.inc(grammar.grammarString.skipUntil('\'', grammar.cursor)) 384 | inc(grammar.cursor) 385 | let substr = grammar.grammarString[prev..grammar.cursor-2] 386 | result = newGrammarNode("a", substr) 387 | else: 388 | let first = grammar.cursor 389 | grammar.cursor.inc(grammar.grammarString.skipWhile(IdentStartChars, grammar.cursor)) 390 | let substr = grammar.grammarString[first..'): 180 | result = newTokenNodeWithNo(Rarrow) 181 | idx += 2 182 | else: 183 | addSingleCharToken(Minus) 184 | of '*': 185 | if tailing('*'): 186 | inc idx 187 | if tailing('='): 188 | result = newTokenNodeWithNo(DoubleStarEqual) 189 | idx += 2 190 | else: 191 | result = newTokenNodeWithNo(DoubleStar) 192 | inc idx 193 | else: 194 | addSingleCharToken(Star) 195 | of '/': 196 | if tailing('/'): 197 | inc idx 198 | if tailing('='): 199 | result = newTokenNodeWithNo(DoubleSlashEqual) 200 | idx += 2 201 | else: 202 | result = newTokenNodeWithNo(DoubleSlash) 203 | inc idx 204 | else: 205 | addSingleCharToken(Slash) 206 | of '|': 207 | addSingleOrDoubleCharToken(Vbar, VbarEqual, '=') 208 | of '&': 209 | addSingleOrDoubleCharToken(Amper, AmperEqual, '=') 210 | of '<': 211 | if tailing('='): 212 | result = newTokenNodeWithNo(LessEqual) 213 | idx += 2 214 | elif tailing('<'): 215 | inc idx 216 | if tailing('='): 217 | result = newTokenNodeWithNo(LeftShiftEqual) 218 | idx += 2 219 | else: 220 | result = newTokenNodeWithNo(LeftShift) 221 | inc idx 222 | elif tailing('>'): 223 | raiseSyntaxError("<> in PEP401 not implemented") 224 | else: 225 | addSingleCharToken(Less) 226 | of '>': 227 | if tailing('='): 228 | result = newTokenNodeWithNo(GreaterEqual) 229 | idx += 2 230 | elif tailing('>'): 231 | inc idx 232 | if tailing('='): 233 | result = newTokenNodeWithNo(RightShiftEqual) 234 | idx += 2 235 | else: 236 | result = newTokenNodeWithNo(RightShift) 237 | inc idx 238 | else: 239 | addSingleCharToken(Greater) 240 | of '=': 241 | addSingleOrDoubleCharToken(Equal, EqEqual, '=') 242 | of '.': 243 | if idx < line.len - 2 and line[idx+1] == '.' and line[idx+2] == '.': 244 | result = newTokenNodeWithNo(Ellipsis) 245 | idx += 3 246 | else: 247 | addSingleCharToken(Dot) 248 | of '%': 249 | addSingleOrDoubleCharToken(Percent, PercentEqual, '=') 250 | of '{': 251 | addSingleCharToken(Lbrace) 252 | of '}': 253 | addSingleCharToken(Rbrace) 254 | of '!': 255 | if tailing('='): 256 | inc idx 257 | addSingleCharToken(NotEqual) 258 | else: 259 | raiseSyntaxError("Single ! not allowed") 260 | of '~': 261 | addSingleCharToken(Tilde) 262 | of '^': 263 | addSingleOrDoubleCharToken(Circumflex, CircumflexEqual, '=') 264 | of '@': 265 | addSingleOrDoubleCharToken(At, AtEqual, '=') 266 | else: 267 | raiseSyntaxError(fmt"Unknown character {line[idx]}") 268 | assert result != nil 269 | 270 | 271 | proc lexOneLine(lexer: Lexer, line: TaintedString) = 272 | # process one line at a time 273 | assert line.find("\n") == -1 274 | 275 | var idx = 0 276 | 277 | while idx < line.len and line[idx] == ' ': 278 | inc(idx) 279 | if idx == line.len or line[idx] == '#': # full of spaces or comment line 280 | return 281 | 282 | if idx mod 4 != 0: 283 | raiseSyntaxError("Indentation must be 4 spaces.", "", lexer.lineNo, 0) 284 | let indentLevel = idx div 4 285 | let diff = indentLevel - lexer.indentLevel 286 | if diff < 0: 287 | for i in diff..<0: 288 | lexer.add(Token.Dedent, (lexer.indentLevel+i)*4) 289 | else: 290 | for i in 0.." , "Greater"), 36 | ("=" , "Equal"), 37 | ("." , "Dot"), 38 | ("%" , "Percent"), 39 | ("{" , "Lbrace"), 40 | ("}" , "Rbrace"), 41 | ("==" , "Eqequal"), 42 | ("!=" , "Notequal"), 43 | ("<=" , "Lessequal"), 44 | (">=" , "Greaterequal"), 45 | ("~" , "Tilde"), 46 | ("^" , "Circumflex"), 47 | ("<<" , "Leftshift"), 48 | (">>" , "Rightshift"), 49 | ("**" , "Doublestar"), 50 | ("+=" , "Plusequal"), 51 | ("-=" , "Minequal"), 52 | ("*=" , "Starequal"), 53 | ("/=" , "Slashequal"), 54 | ("%=" , "Percentequal"), 55 | ("&=" , "Amperequal"), 56 | ("|=" , "Vbarequal"), 57 | ("^=" , "Circumflexequal"), 58 | ("<<=" , "Leftshiftequal"), 59 | (">>=" , "Rightshiftequal"), 60 | ("**=" , "Doublestarequal"), 61 | ("//" , "Doubleslash"), 62 | ("//=" , "Doubleslashequal"), 63 | ("@" , "At"), 64 | ("@=" , "Atequal"), 65 | ("->" , "Rarrow"), 66 | ("..." , "Ellipsis"), 67 | ("<>" , "PEP401"), 68 | ] 69 | 70 | 71 | proc readGrammarToken: seq[string] {.compileTime.} = 72 | var textLines = slurp(grammarFileName).splitLines() 73 | for line in textLines: 74 | if line.len == 0: 75 | continue 76 | if line[0] in 'a'..'z': 77 | var tokenString: string 78 | discard line.parseUntil(tokenString, ':') # stored in tokenString 79 | result.add(tokenString) 80 | 81 | 82 | # everything inside pars 83 | proc readReserveName: HashSet[string] {.compileTime.} = 84 | let text = slurp(grammarFileName) 85 | result = initSet[string]() 86 | var idx = 0 87 | while idx < text.len: 88 | case text[idx] 89 | of '\'': 90 | inc idx 91 | let l = text.skipUntil('\'', idx) 92 | result.incl(text[idx.. {node.content}" 178 | else: 179 | fmt"<{node.token}>" 180 | 181 | when isMainModule: 182 | echo grammarTokenList 183 | echo reserveNameSet 184 | echo strTokenMap 185 | -------------------------------------------------------------------------------- /Python/asdl.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import hashes 3 | import sequtils 4 | import strutils 5 | import strformat 6 | 7 | import ../Objects/[pyobject, stringobject] 8 | 9 | type 10 | AstNodeBase* = ref object of RootObj 11 | 12 | Asdlint* = ref object of AstNodeBase 13 | value*: int 14 | 15 | AsdlIdentifier* = ref object of AstNodeBase 16 | value*: PyStrObject 17 | 18 | AsdlConstant* = ref object of AstNodeBase 19 | value*: PyObject 20 | 21 | 22 | method hash*(node: AstNodeBase): Hash {. base .} = 23 | hash(cast[int](node)) 24 | 25 | 26 | proc genMember(member: NimNode): NimNode = 27 | case member.kind 28 | of nnkCommand: # int x, etc 29 | result = newIdentDefs(postFix(member[1], "*"), ident("Asdl" & $member[0])) 30 | of nnkInfix: # expr* a, etc 31 | let op = member[0] 32 | expectKind(op, nnkIdent) 33 | if op.strval == "*": 34 | result = newIdentDefs( 35 | postFix(member[2], "*"), 36 | nnkBracketExpr.newTree( 37 | ident("seq"), 38 | ident("Asdl" & $member[1]) 39 | ) 40 | ) 41 | else: # don't mind `?` 42 | result = newIdentDefs(postFix(member[2], "*"), ident("Asdl" & $member[1])) 43 | else: 44 | assert false 45 | 46 | # name of the type 47 | proc getDefName(def: NimNode): string = 48 | expectKind(def, {nnkCall, nnkIdent}) 49 | case def.kind 50 | of nnkCall: 51 | result = $def[0] 52 | of nnkIdent: 53 | result = $def 54 | else: 55 | assert false 56 | 57 | proc genType(def: NimNode, prefix: string, parent: string): NimNode = 58 | # with () or not 59 | expectKind(def, {nnkCall, nnkIdent}) 60 | # the list of members 61 | let recList = nnkRecList.newTree() 62 | if def.kind == nnkCall: 63 | for i in 1..") 25 | var prevF: PyFrameObject 26 | echoCompat "NPython 0.1.0" 27 | while true: 28 | var input: string 29 | var prompt: string 30 | if finished: 31 | prompt = ">>> " 32 | rootCst = nil 33 | lexer.clearIndent 34 | else: 35 | prompt = "... " 36 | assert (not rootCst.isNil) 37 | 38 | try: 39 | input = readLineCompat(prompt) 40 | except EOFError, IOError: 41 | quit(0) 42 | 43 | try: 44 | rootCst = parseWithState(input, lexer, Mode.Single, rootCst) 45 | except SyntaxError: 46 | let e = SyntaxError(getCurrentException()) 47 | let excpObj = fromBltinSyntaxError(e, newPyStr("")) 48 | excpObj.printTb 49 | finished = true 50 | continue 51 | 52 | if rootCst.isNil: 53 | continue 54 | finished = rootCst.finished 55 | if not finished: 56 | continue 57 | 58 | let compileRes = compile(rootCst, "") 59 | if compileRes.isThrownException: 60 | PyExceptionObject(compileRes).printTb 61 | continue 62 | let co = PyCodeObject(compileRes) 63 | 64 | when defined(debug): 65 | echo co 66 | 67 | var globals: PyDictObject 68 | if prevF != nil: 69 | globals = prevF.globals 70 | else: 71 | globals = newPyDict() 72 | let fun = newPyFunc(newPyString("Bla"), co, globals) 73 | let f = newPyFrame(fun) 74 | var retObj = f.evalFrame 75 | if retObj.isThrownException: 76 | PyExceptionObject(retObj).printTb 77 | else: 78 | prevF = f 79 | 80 | proc nPython(args: seq[string]) = 81 | pyInit(args) 82 | if pyConfig.filepath == "": 83 | interactiveShell() 84 | 85 | if not pyConfig.filepath.existsFile: 86 | echo fmt"File does not exist ({pyConfig.filepath})" 87 | quit() 88 | let input = readFile(pyConfig.filepath) 89 | let retObj = runString(input, pyConfig.filepath) 90 | if retObj.isThrownException: 91 | PyExceptionObject(retObj).printTb 92 | 93 | when isMainModule: 94 | dispatch(nPython) 95 | -------------------------------------------------------------------------------- /Python/jspython.nim: -------------------------------------------------------------------------------- 1 | import neval 2 | import compile 3 | import coreconfig 4 | import traceback 5 | import lifecycle 6 | import ../Parser/[lexer, parser] 7 | import ../Objects/bundle 8 | import ../Utils/[utils, compat] 9 | 10 | var finished = true 11 | var rootCst: ParseNode 12 | let lexerInst = newLexer("") 13 | var prevF: PyFrameObject 14 | 15 | proc interactivePython(input: cstring): bool {. exportc .} = 16 | echo input 17 | if finished: 18 | rootCst = nil 19 | lexerInst.clearIndent 20 | else: 21 | assert (not rootCst.isNil) 22 | 23 | try: 24 | rootCst = parseWithState($input, lexerInst, Mode.Single, rootCst) 25 | except SyntaxError: 26 | let e = SyntaxError(getCurrentException()) 27 | let excpObj = fromBltinSyntaxError(e, newPyStr("")) 28 | excpObj.printTb 29 | finished = true 30 | return true 31 | 32 | if rootCst.isNil: 33 | return true 34 | finished = rootCst.finished 35 | if not finished: 36 | return false 37 | 38 | let compileRes = compile(rootCst, "") 39 | if compileRes.isThrownException: 40 | PyExceptionObject(compileRes).printTb 41 | return true 42 | let co = PyCodeObject(compileRes) 43 | 44 | when defined(debug): 45 | echo co 46 | 47 | var globals: PyDictObject 48 | if prevF != nil: 49 | globals = prevF.globals 50 | else: 51 | globals = newPyDict() 52 | let fun = newPyFunc(newPyString("Bla"), co, globals) 53 | let f = newPyFrame(fun) 54 | var retObj = f.evalFrame 55 | if retObj.isThrownException: 56 | PyExceptionObject(retObj).printTb 57 | else: 58 | prevF = f 59 | true 60 | 61 | 62 | # karax not working. gh-86 63 | #[ 64 | include karax/prelude 65 | import karax/kdom 66 | 67 | proc createDom(): VNode = 68 | result = buildHtml(tdiv): 69 | tdiv(class="stream"): 70 | echo stream.len 71 | for line in stream: 72 | let (prompt, content) = line 73 | tdiv(class="line"): 74 | p(class="prompt"): 75 | if prompt.len == 0: 76 | text kstring" " 77 | else: 78 | text prompt 79 | p: 80 | text content 81 | tdiv(class="line editline"): 82 | p(class="prompt"): 83 | text prompt 84 | p(class="edit", contenteditable="true"): 85 | proc onKeydown(ev: Event, n: VNode) = 86 | if KeyboardEvent(ev).keyCode == 13: 87 | let input = n.dom.innerHTML 88 | echo input 89 | interactivePython($input) 90 | n.dom.innerHTML = kstring"" 91 | ev.preventDefault 92 | 93 | setRenderer createDom 94 | 95 | ]# 96 | # init without arguments 97 | pyInit(@[]) 98 | -------------------------------------------------------------------------------- /Python/lifecycle.nim: -------------------------------------------------------------------------------- 1 | import coreconfig 2 | # init bltinmodule 3 | import bltinmodule 4 | import ../Objects/[pyobject, typeobject] 5 | import ../Utils/utils 6 | 7 | when not defined(js): 8 | import os 9 | import ospaths 10 | 11 | proc outOfMemHandler = 12 | let e = new OutOfMemError 13 | raise e 14 | 15 | system.outOfMemHook = outOfMemHandler 16 | 17 | when not defined(js): 18 | proc controlCHandler {. noconv .} = 19 | raise newException(InterruptError, "") 20 | 21 | system.setControlCHook(controlCHandler) 22 | 23 | proc pyInit*(args: seq[string]) = 24 | for t in bltinTypes: 25 | t.typeReady 26 | 27 | when defined(js): 28 | discard 29 | else: 30 | if args.len == 0: 31 | pyConfig.path = os.getCurrentDir() 32 | else: 33 | pyConfig.filepath = joinPath(os.getCurrentDir(), args[0]) 34 | pyConfig.filename = pyConfig.filepath.extractFilename() 35 | pyConfig.path = pyConfig.filepath.parentDir() 36 | when defined(debug): 37 | echo "Python path: " & pyConfig.path 38 | 39 | 40 | -------------------------------------------------------------------------------- /Python/opcode.nim: -------------------------------------------------------------------------------- 1 | 2 | type 3 | OpCode* {. pure .} = enum 4 | NULLCODE, 5 | POP_TOP, 6 | ROT_TWO, 7 | ROT_THREE, 8 | DUP_TOP, 9 | DUP_TOP_TWO, 10 | ROT_FOUR, 11 | NOP, 12 | UNARY_POSITIVE, 13 | UNARY_NEGATIVE, 14 | UNARY_NOT, 15 | UNARY_INVERT, 16 | BINARY_MATRIX_MULTIPLY, 17 | INPLACE_MATRIX_MULTIPLY, 18 | BINARY_POWER, 19 | BINARY_MULTIPLY, 20 | BINARY_MODULO, 21 | BINARY_ADD, 22 | BINARY_SUBTRACT, 23 | BINARY_SUBSCR, 24 | BINARY_FLOOR_DIVIDE, 25 | BINARY_TRUE_DIVIDE, 26 | INPLACE_FLOOR_DIVIDE, 27 | INPLACE_TRUE_DIVIDE, 28 | GET_AITER, 29 | GET_ANEXT, 30 | BEFORE_ASYNC_WITH, 31 | BEGIN_FINALLY, 32 | END_ASYNC_FOR, 33 | INPLACE_ADD, 34 | INPLACE_SUBTRACT, 35 | INPLACE_MULTIPLY, 36 | INPLACE_MODULO, 37 | STORE_SUBSCR, 38 | DELETE_SUBSCR, 39 | BINARY_LSHIFT, 40 | BINARY_RSHIFT, 41 | BINARY_AND, 42 | BINARY_XOR, 43 | BINARY_OR, 44 | INPLACE_POWER, 45 | GET_ITER, 46 | GET_YIELD_FROM_ITER, 47 | PRINT_EXPR, 48 | LOAD_BUILD_CLASS, 49 | YIELD_FROM, 50 | GET_AWAITABLE, 51 | INPLACE_LSHIFT, 52 | INPLACE_RSHIFT, 53 | INPLACE_AND, 54 | INPLACE_XOR, 55 | INPLACE_OR, 56 | WITH_CLEANUP_START, 57 | WITH_CLEANUP_FINISH, 58 | RETURN_VALUE, 59 | IMPORT_STAR, 60 | SETUP_ANNOTATIONS, 61 | YIELD_VALUE, 62 | POP_BLOCK, 63 | END_FINALLY, 64 | POP_EXCEPT, 65 | HAVE_ARGUMENT, 66 | STORE_NAME, 67 | DELETE_NAME, 68 | UNPACK_SEQUENCE, 69 | FOR_ITER, 70 | UNPACK_EX, 71 | STORE_ATTR, 72 | DELETE_ATTR, 73 | STORE_GLOBAL, 74 | DELETE_GLOBAL, 75 | LOAD_CONST, 76 | LOAD_NAME, 77 | BUILD_TUPLE, 78 | BUILD_LIST, 79 | BUILD_SET, 80 | BUILD_MAP, 81 | LOAD_ATTR, 82 | COMPARE_OP, 83 | IMPORT_NAME, 84 | IMPORT_FROM, 85 | JUMP_FORWARD, 86 | JUMP_IF_FALSE_OR_POP, 87 | JUMP_IF_TRUE_OR_POP, 88 | JUMP_ABSOLUTE, 89 | POP_JUMP_IF_FALSE, 90 | POP_JUMP_IF_TRUE, 91 | LOAD_GLOBAL, 92 | SETUP_FINALLY, 93 | LOAD_FAST, 94 | STORE_FAST, 95 | DELETE_FAST, 96 | RAISE_VARARGS, 97 | CALL_FUNCTION, 98 | MAKE_FUNCTION, 99 | BUILD_SLICE, 100 | LOAD_CLOSURE, 101 | LOAD_DEREF, 102 | STORE_DEREF, 103 | DELETE_DEREF, 104 | CALL_FUNCTION_KW, 105 | CALL_FUNCTION_EX, 106 | SETUP_WITH, 107 | EXTENDED_ARG, 108 | LIST_APPEND, 109 | SET_ADD, 110 | MAP_ADD, 111 | LOAD_CLASSDEREF, 112 | BUILD_LIST_UNPACK, 113 | BUILD_MAP_UNPACK, 114 | BUILD_MAP_UNPACK_WITH_CALL, 115 | BUILD_TUPLE_UNPACK, 116 | BUILD_SET_UNPACK, 117 | SETUP_ASYNC_WITH, 118 | FORMAT_VALUE, 119 | BUILD_CONST_KEY_MAP, 120 | BUILD_STRING, 121 | BUILD_TUPLE_UNPACK_WITH_CALL, 122 | LOAD_METHOD, 123 | CALL_METHOD, 124 | CALL_FINALLY, 125 | POP_FINALLY, 126 | EXCEPT_HANDLER, 127 | 128 | type CmpOp* {.pure.} = enum 129 | Lt, Le, Eq, Ne, Gt, Ge, In, NotIn, Is, IsNot, ExcpMatch 130 | 131 | proc hasArg*(opCode: OpCode): bool = 132 | OpCode.HaveArgument < opCode 133 | 134 | proc genHasArgSet: set[OpCode] {.compileTime.} = 135 | for opCode in OpCode: 136 | if opCode.hasArg: 137 | result.incl(opCode) 138 | 139 | const hasArgSet* = genHasArgSet() 140 | 141 | const jumpSet* = { 142 | OpCode.JUMP_FORWARD, 143 | OpCode.JUMP_IF_FALSE_OR_POP, 144 | OpCode.JUMP_IF_TRUE_OR_POP, 145 | OpCode.JUMP_ABSOLUTE, 146 | OpCode.POP_JUMP_IF_FALSE, 147 | OpCode.POP_JUMP_IF_TRUE, 148 | OpCode.For_ITER, # jump if stopiteration is raised 149 | OpCode.SETUP_FINALLY, # jump if exception is raised 150 | } 151 | 152 | 153 | when isMainModule: 154 | echo OpCode.StoreName in hasArgSet 155 | -------------------------------------------------------------------------------- /Python/python.nim: -------------------------------------------------------------------------------- 1 | when defined(js): 2 | include jspython 3 | else: 4 | include cpython 5 | -------------------------------------------------------------------------------- /Python/symtable.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | import sequtils 3 | import sets 4 | import macros 5 | 6 | import ast 7 | import asdl 8 | import ../Objects/stringobjectImpl 9 | import ../Utils/utils 10 | 11 | type 12 | Scope* {. pure .} = enum 13 | Local, 14 | Cell, 15 | Free, 16 | Global 17 | 18 | type 19 | SymTable* = ref object 20 | # map ast node address to ste 21 | entries: Table[AstNodeBase, SymTableEntry] 22 | root: SymTableEntry 23 | 24 | SymTableEntry* = ref object 25 | # the symbol table entry tree 26 | parent: SymTableEntry 27 | children: seq[SymTableEntry] 28 | 29 | # function arguments, name to index in argument list 30 | argVars*: Table[PyStrObject, int] 31 | 32 | declaredVars: HashSet[PyStrObject] 33 | usedVars: HashSet[PyStrObject] 34 | 35 | # for scope lookup 36 | scopes: Table[PyStrObject, Scope] 37 | 38 | # the difference between names and localVars is subtle. 39 | # In runtime, py object in names are looked up in local 40 | # dict and global dict by string key. 41 | # At least global dict can be modified dynamically. 42 | # whereas py object in localVars are looked up in var 43 | # sequence, thus faster. localVar can't be made global 44 | # def foo(x): 45 | # global x 46 | # will result in an error (in CPython) 47 | # names also responds for storing attribute names 48 | names: Table[PyStrObject, int] 49 | localVars: Table[PyStrObject, int] 50 | # used for closures 51 | cellVars: Table[PyStrObject, int] # declared in the scope 52 | freeVars: Table[PyStrObject, int] # not declared in the scope 53 | 54 | proc newSymTableEntry(parent: SymTableEntry): SymTableEntry = 55 | result = new SymTableEntry 56 | result.parent = parent 57 | if not parent.isNil: # not root 58 | parent.children.add result 59 | result.argVars = initTable[PyStrObject, int]() 60 | result.declaredVars = initSet[PyStrObject]() 61 | result.usedVars = initSet[PyStrObject]() 62 | result.scopes = initTable[PyStrObject, Scope]() 63 | result.names = initTable[PyStrObject, int]() 64 | result.localVars = initTable[PyStrObject, int]() 65 | result.cellVars = initTable[PyStrObject, int]() 66 | result.freeVars = initTable[PyStrObject, int]() 67 | 68 | {. push inline, cdecl .} 69 | 70 | proc getSte*(st: SymTable, key: AstNodeBase): SymTableEntry = 71 | st.entries[key] 72 | 73 | proc isRootSte(ste: SymTableEntry): bool = 74 | ste.parent.isNil 75 | 76 | proc declared(ste: SymTableEntry, localName: PyStrObject): bool = 77 | localName in ste.declaredVars 78 | 79 | proc getScope*(ste: SymTableEntry, name: PyStrObject): Scope = 80 | ste.scopes[name] 81 | 82 | proc addDeclaration(ste: SymTableEntry, name: PyStrObject) = 83 | ste.declaredVars.incl name 84 | 85 | proc addDeclaration(ste: SymTableEntry, name: AsdlIdentifier) = 86 | let nameStr = name.value 87 | ste.addDeclaration nameStr 88 | 89 | proc addUsed(ste: SymTableEntry, name: PyStrObject) = 90 | ste.usedVars.incl name 91 | 92 | proc addUsed(ste: SymTableEntry, name: AsdlIdentifier) = 93 | let nameStr = name.value 94 | ste.addUsed(nameStr) 95 | 96 | proc localId*(ste: SymTableEntry, localName: PyStrObject): int = 97 | ste.localVars[localName] 98 | 99 | proc nameId*(ste: SymTableEntry, nameStr: PyStrObject): int = 100 | # add entries for attribute lookup 101 | if ste.names.hasKey(nameStr): 102 | return ste.names[nameStr] 103 | else: 104 | result = ste.names.len 105 | ste.names[nameStr] = result 106 | 107 | proc cellId*(ste: SymTableEntry, nameStr: PyStrObject): int = 108 | ste.cellVars[nameStr] 109 | 110 | proc freeId*(ste: SymTableEntry, nameStr: PyStrObject): int = 111 | # they end up in the same seq 112 | ste.freeVars[nameStr] + ste.cellVars.len 113 | 114 | proc hasCell*(ste: SymTableEntry, nameStr: PyStrObject): bool = 115 | ste.cellVars.hasKey(nameStr) 116 | 117 | proc hasFree*(ste: SymTableEntry, nameStr: PyStrObject): bool = 118 | ste.freeVars.hasKey(nameStr) 119 | 120 | proc toInverseSeq(t: Table[PyStrObject, int]): seq[PyStrObject] = 121 | result = newSeq[PyStrObject](t.len) 122 | for name, id in t: 123 | result[id] = name 124 | 125 | proc namesToSeq*(ste: SymTableEntry): seq[PyStrObject] = 126 | ste.names.toInverseSeq 127 | 128 | proc localVarsToSeq*(ste: SymTableEntry): seq[PyStrObject] = 129 | ste.localVars.toInverseSeq 130 | 131 | proc cellVarsToSeq*(ste: SymTableEntry): seq[PyStrObject] = 132 | ste.cellVars.toInverseSeq 133 | 134 | proc freeVarsToSeq*(ste: SymTableEntry): seq[PyStrObject] = 135 | ste.freeVars.toInverseSeq 136 | 137 | {. pop .} 138 | 139 | # traverse the ast to collect local vars 140 | # local vars can be defined in Name List Tuple For Import 141 | # currently we only have Name, For, Import, so it's pretty simple. 142 | # lot's of discard out there, because we want to quit early if something 143 | # goes wrong. In future when the symtable is basically done these codes 144 | # can probably be deleted 145 | # Note that Assert implicitly uses name "AssertionError" 146 | 147 | proc collectDeclaration*(st: SymTable, astRoot: AsdlModl) = 148 | var toVisit: seq[(AstNodeBase, SymTableEntry)] 149 | toVisit.add((astRoot, nil)) 150 | while toVisit.len != 0: 151 | let (astNode, parentSte) = toVisit.pop 152 | let ste = newSymTableEntry(parentSte) 153 | st.entries[astNode] = ste 154 | var toVisitPerSte: seq[AstNodeBase] 155 | template visit(n) = 156 | if not n.isNil: 157 | toVisitPerSte.add n 158 | template visitSeq(s) = 159 | for astNode in s: 160 | toVisitPerSte.add(astNode) 161 | 162 | template addBodies(TypeName) = 163 | for node in TypeName(astNode).body: 164 | toVisitPerSte.add(node) 165 | # these asts mean new scopes 166 | if astNode of AstModule: 167 | addBodies(AstModule) 168 | elif astNode of AstInteractive: 169 | addBodies(AstInteractive) 170 | elif astNode of AstFunctionDef: 171 | addBodies(AstFunctionDef) 172 | # deal with function args 173 | let f = AstFunctionDef(astNode) 174 | let args = AstArguments(f.args).args 175 | for idx, arg in args: 176 | assert arg of AstArg 177 | ste.addDeclaration(AstArg(arg).arg) 178 | ste.argVars[AstArg(arg).arg.value] = idx 179 | elif astNode of AstClassDef: 180 | addBodies(AstClassDef) 181 | elif astNode of AstListComp: 182 | let compNode = AstListComp(astNode) 183 | toVisitPerSte.add compNode.elt 184 | for gen in compNode.generators: 185 | let genNode = AstComprehension(gen) 186 | toVisitPerSte.add(genNode.target) 187 | # the iterator. Need to add here to let symbol table make room for the localVar 188 | ste.addDeclaration(newPyString(".0")) 189 | ste.argVars[newPyString(".0")] = 0 190 | else: 191 | unreachable 192 | 193 | while toVisitPerSte.len != 0: 194 | let astNode = toVisitPerSte.pop 195 | if astNode of AsdlStmt: 196 | case AsdlStmt(astNode).kind 197 | 198 | of AsdlStmtTk.FunctionDef: 199 | let funcNode = AstFunctionDef(astNode) 200 | ste.addDeclaration(funcNode.name) 201 | toVisit.add((astNode, ste)) 202 | visitSeq(funcNode.decorator_list) 203 | 204 | of AsdlStmtTk.ClassDef: 205 | let classNode = AstClassDef(astNode) 206 | assert classNode.bases.len == 0 207 | assert classNode.keywords.len == 0 208 | assert classNode.decoratorList.len == 0 209 | ste.addDeclaration(classNode.name) 210 | toVisit.add((astNode, ste)) 211 | 212 | of AsdlStmtTk.Return: 213 | visit AstReturn(astNode).value 214 | 215 | of AsdlStmtTk.Assign: 216 | let assignNode = AstAssign(astNode) 217 | assert assignNode.targets.len == 1 218 | visit assignNode.targets[0] 219 | visit assignNode.value 220 | 221 | of AsdlStmtTk.For: 222 | let forNode = AstFor(astNode) 223 | if not (forNode.target.kind == AsdlExprTk.Name): 224 | raiseSyntaxError("only name as loop variable", forNode.target) 225 | visit forNode.target 226 | visit forNode.iter 227 | visitSeq(forNode.body) 228 | assert forNode.orelse.len == 0 229 | 230 | of AsdlStmtTk.While: 231 | let whileNode = AstWhile(astNode) 232 | visit whileNode.test 233 | visitSeq(whileNode.body) 234 | assert whileNode.orelse.len == 0 235 | 236 | of AsdlStmtTk.If: 237 | let ifNode = AstIf(astNode) 238 | visit ifNode.test 239 | visitSeq(ifNode.body) 240 | visitSeq(ifNode.orelse) 241 | 242 | of AsdlStmtTk.Raise: 243 | let raiseNode = AstRaise(astNode) 244 | visit raiseNode.exc 245 | visit raiseNode.cause 246 | 247 | of AsdlStmtTk.Assert: 248 | let assertNode = AstAssert(astNode) 249 | ste.addUsed(newPyString("AssertionError")) 250 | visit assertNode.test 251 | visit assertNode.msg 252 | 253 | of AsdlStmtTk.Try: 254 | let tryNode = AstTry(astNode) 255 | visitSeq(tryNode.body) 256 | visitSeq(tryNode.handlers) 257 | visitSeq(tryNode.orelse) 258 | visitSeq(tryNode.finalbody) 259 | 260 | of AsdlStmtTk.Import: 261 | assert AstImport(astNode).names.len == 1 262 | ste.addDeclaration(AstAlias(AstImport(astNode).names[0]).name) 263 | 264 | of AsdlStmtTk.Expr: 265 | visit AstExpr(astNode).value 266 | 267 | of AsdlStmtTk.Pass, AsdlStmtTk.Break, AsdlStmtTk.Continue: 268 | discard 269 | else: 270 | unreachable($AsdlStmt(astNode).kind) 271 | elif astNode of AsdlExpr: 272 | case AsdlExpr(astNode).kind 273 | 274 | of AsdlExprTk.BoolOp: 275 | visitSeq AstBoolOp(astNode).values 276 | 277 | of AsdlExprTk.BinOp: 278 | let binOpNode = AstBinOp(astNode) 279 | visit binOpNode.left 280 | visit binOpNode.right 281 | 282 | of AsdlExprTk.UnaryOp: 283 | visit AstUnaryOp(astNode).operand 284 | 285 | of AsdlExprTk.Dict: 286 | let dictNode = AstDict(astNode) 287 | visitSeq dictNode.keys 288 | visitSeq dictNode.values 289 | 290 | of AsdlExprTk.Set: 291 | let setNode = AstSet(astNode) 292 | visitSeq setNode.elts 293 | 294 | of AsdlExprTk.ListComp: 295 | # tricky here. Parts in this level, parts in a new function 296 | toVisit.add((astNode, ste)) 297 | let compNode = AstListComp(astNode) 298 | for gen in compNode.generators: 299 | let genNode = AstComprehension(gen) 300 | visit genNode.iter 301 | 302 | of AsdlExprTk.Compare: 303 | let compareNode = AstCompare(astNode) 304 | visit compareNode.left 305 | visitSeq compareNode.comparators 306 | 307 | of AsdlExprTk.Call: 308 | let callNode = AstCall(astNode) 309 | visit callNode.fun 310 | visitSeq callNode.args 311 | assert callNode.keywords.len == 0 312 | 313 | of AsdlExprTk.Attribute: 314 | visit AstAttribute(astNode).value 315 | 316 | of AsdlExprTk.Subscript: 317 | let subsNode = AstSubscript(astNode) 318 | visit subsNode.value 319 | visit subsNode.slice 320 | 321 | of AsdlExprTk.Name: 322 | let nameNode = AstName(astNode) 323 | case nameNode.ctx.kind 324 | of AsdlExprContextTk.Store: 325 | ste.addDeclaration(nameNode.id) 326 | of AsdlExprContextTk.Load: 327 | ste.addUsed(nameNode.id) 328 | else: 329 | unreachable 330 | 331 | of AsdlExprTk.List: 332 | let listNode = AstList(astNode) 333 | case listNode.ctx.kind 334 | of AsdlExprContextTk.Store, AsdlExprContextTk.Load: 335 | visitSeq listNode.elts 336 | else: 337 | unreachable 338 | 339 | of AsdlExprTk.Tuple: 340 | let tupleNode = AstTuple(astNode) 341 | case tupleNode.ctx.kind 342 | of AsdlExprContextTk.Store, AsdlExprContextTk.Load: 343 | visitSeq tupleNode.elts 344 | else: 345 | unreachable 346 | 347 | of AsdlExprTk.Constant: 348 | discard 349 | 350 | else: 351 | unreachable 352 | 353 | elif astNode of AsdlSlice: 354 | case AsdlSlice(astNode).kind 355 | 356 | of AsdlSliceTk.Slice: 357 | let sliceNode = AstSlice(astNode) 358 | visit sliceNode.lower 359 | visit sliceNode.upper 360 | visit sliceNode.step 361 | 362 | of AsdlSliceTk.ExtSlice: 363 | unreachable 364 | 365 | of AsdlSliceTk.Index: 366 | visit AstIndex(astNode).value 367 | 368 | elif astNode of AsdlExceptHandler: 369 | let excpNode = AstExcepthandler(astNode) 370 | assert excpNode.name.isNil 371 | visitSeq(excpNode.body) 372 | visit(excpNode.type) 373 | else: 374 | unreachable() 375 | 376 | proc determineScope(ste: SymTableEntry, name: PyStrObject) = 377 | if ste.scopes.hasKey(name): 378 | return 379 | if ste.isRootSte: 380 | ste.scopes[name] = Scope.Global 381 | return 382 | if ste.declared(name): 383 | ste.scopes[name] = Scope.Local 384 | return 385 | var traceback = @[ste, ste.parent] 386 | var scope: Scope 387 | while true: 388 | let curSte = traceback[^1] 389 | if curSte.isRootSte: 390 | scope = Scope.Global 391 | break 392 | if curSte.declared(name): 393 | scope = Scope.Cell 394 | break 395 | traceback.add curSte.parent 396 | traceback[^1].scopes[name] = scope 397 | case scope 398 | of Scope.Cell: 399 | scope = Scope.Free 400 | of Scope.Global: 401 | discard 402 | else: 403 | unreachable 404 | for curSte in traceback[0..^2]: 405 | curSte.scopes[name] = scope 406 | 407 | proc determineScope(ste: SymTableEntry) = 408 | # DFS ensures proper closure behavior (cells and frees correctly determined) 409 | for child in ste.children: 410 | child.determineScope() 411 | for name in ste.usedVars: 412 | ste.determineScope(name) 413 | # for those not set as cell or free, determine local or global 414 | for name in ste.declaredVars: 415 | ste.determineScope(name) 416 | # setup the indeces 417 | for name, scope in ste.scopes.pairs: 418 | var d: ptr Table[PyStrObject, int] 419 | case scope 420 | of Scope.Local: 421 | d = ste.localVars.addr 422 | of Scope.Global: 423 | d = ste.names.addr 424 | of Scope.Cell: 425 | d = ste.cellVars.addr 426 | of Scope.Free: 427 | d = ste.freeVars.addr 428 | d[][name] = d[].len 429 | 430 | proc determineScope(st: SymTable) = 431 | st.root.determineScope 432 | 433 | proc newSymTable*(astRoot: AsdlModl): SymTable = 434 | new result 435 | result.entries = initTable[AstNodeBase, SymTableEntry]() 436 | # traverse ast tree for 2 passes for symbol scopes 437 | # first pass 438 | result.collectDeclaration(astRoot) 439 | result.root = result.getSte(astRoot) 440 | # second pass 441 | result.determineScope() 442 | -------------------------------------------------------------------------------- /Python/traceback.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | import strutils 3 | import algorithm 4 | 5 | import ../Objects/bundle 6 | import ../Parser/lexer 7 | import ../Utils/compat 8 | 9 | 10 | proc fmtTraceBack(tb: TraceBack): string = 11 | assert tb.fileName.ofPyStrObject 12 | # lineNo should starts from 1. 0 means not initialized properly 13 | assert tb.lineNo != 0 14 | let fileName = PyStrObject(tb.fileName).str 15 | var atWhere: string 16 | if tb.funName.isNil: 17 | atWhere = "" 18 | else: 19 | assert tb.funName.ofPyStrObject 20 | atWhere = ", in " & PyStrObject(tb.funName).str 21 | result &= fmt(" File \"{fileName}\", line {tb.lineNo}{atWhere}\n") 22 | result &= " " & getSource(fileName, tb.lineNo).strip(chars={' '}) 23 | if tb.colNo != -1: 24 | result &= "\n " & "^".indent(tb.colNo) 25 | 26 | 27 | proc printTb*(excp: PyExceptionObject) = 28 | var cur = excp 29 | var excpStrs: seq[string] 30 | while not cur.isNil: 31 | var singleExcpStrs: seq[string] 32 | singleExcpStrs.add "Traceback (most recent call last):" 33 | for tb in cur.traceBacks.reversed: 34 | singleExcpStrs.add tb.fmtTraceBack 35 | singleExcpStrs.add PyStrObject(tpMagic(BaseError, repr)(cur)).str 36 | excpStrs.add singleExcpStrs.join("\n") 37 | cur = cur.context 38 | let joinMsg = "\n\nDuring handling of the above exception, another exception occured\n\n" 39 | echoCompat excpStrs.reversed.join(joinMsg) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NPython 2 | 3 | (Subset of) Python programming language implemented in Nim, from the compiler to the VM. 4 | 5 | [Online interactive demo by compiling Nim to Javascript](https://liwt31.github.io/NPython-demo/). 6 | 7 | ### Purpose 8 | Fun and practice. Learn both Python and Nim. 9 | 10 | 11 | ### Status 12 | Capable of: 13 | * flow control with `if else`, `while` and `for` 14 | * basic function (closure) defination and call. Decorators. 15 | * builtin print, dir, len, range, tuple, list, dict, exceptions and bunch of other simple helper functions 16 | * list comprehension (no set or dict yet). 17 | * basic import such as `import foo`. No alias, no `from`, etc 18 | * raise exceptions, basic `try ... except XXXError ... `, with detailed traceback message. Assert statement. 19 | * primitive `class` defination. No inheritance, no metatype, etc 20 | * interactive mode and file mode 21 | 22 | Check out `./tests` to see more examples. 23 | 24 | 25 | ### How to use 26 | ``` 27 | git clone https://github.com/liwt31/NPython.git 28 | cd NPython 29 | nimble c ./Python/python 30 | ./Python/python 31 | ``` 32 | 33 | ### Todo 34 | * more features on user defined class 35 | * builtin compat dict 36 | * yield stmt 37 | * better bigint lib 38 | 39 | ### Performance 40 | Nim is claimed to be as fast as C, and indeed it is. According to some primitive micro benchmarks (`spin.py` and `f_spin.py` in `tests/benchmark/`), although NPython is currently 5x-10x slower than CPython 3.7, it is at least in some cases faster than CPython < 2.4. This is already a huge achievement considering the numerous optimizations out there in the CPython codebase and NPython is focused on quick prototyping and lefts many rooms for optimization. For comparison, [RustPython0.0.1](https://github.com/RustPython/RustPython) is 100x slower than CPython3.7 and uses 10x more memory. 41 | 42 | Currently, the performance bottlenecks are object allocation, seq accessing (compared with CPython direct memory accessing). The object allocation and seq accessing issue are basically impossible to solve unless we do GC on our own just like CPython. 43 | 44 | 45 | ### Drawbacks 46 | NPython aims for both C and JavaScript targets, so it's hard (if not impossible) to perform low-level address based optimization. 47 | NPython currently relies on Nim GC. Frankly speaking it's not satisfactory. 48 | * The GC uses thread-local heap, makes threading nearly impossible (for Python). 49 | * The GC can hardly be shared between different dynamic libs, which means NPython can not import extensions written in Nim. 50 | 51 | If memory is managed manually, hopefully these drawbacks can be overcomed. Of course that's a huge sacrifice. 52 | 53 | 54 | ### License 55 | Not sure. I think it should follow CPython license, but other Python implementations like RustPython use licenses like MIT. 56 | -------------------------------------------------------------------------------- /Utils/compat.nim: -------------------------------------------------------------------------------- 1 | when defined(js): 2 | import strutils 3 | #[ 4 | include karax/prelude 5 | var stream*: seq[(kstring, kstring)] 6 | ]# 7 | proc log*(prompt, info: cstring) {. importc .} 8 | 9 | # how to read from console? 10 | template readLineCompat*(prompt): TaintedString = 11 | "" 12 | 13 | template echoCompat*(content: string) = 14 | echo content 15 | for line in content.split("\n"): 16 | log(cstring" ", line) 17 | #stream.add((kstring"", kstring(content))) 18 | 19 | 20 | # combining two seq directly leads to a bug in the compiler when compiled to JS 21 | # see gh-10651 22 | template addCompat*[T](a, b: seq[T]) = 23 | for item in b: 24 | a.add item 25 | 26 | else: 27 | import rdstdin 28 | import os 29 | 30 | template readLineCompat*(prompt): TaintedString = 31 | readLineFromStdin(prompt) 32 | 33 | template echoCompat*(content) = 34 | echo content 35 | 36 | template addCompat*[T](a, b: seq[T]) = 37 | a.add b 38 | 39 | 40 | -------------------------------------------------------------------------------- /Utils/utils.nim: -------------------------------------------------------------------------------- 1 | type 2 | # exceptions used internally 3 | InternalError* = object of Exception 4 | 5 | SyntaxError* = ref object of Exception 6 | fileName*: string 7 | lineNo*: int 8 | colNo*: int 9 | 10 | # internal error for wrong type of dict function (`hash` and `eq`) return value 11 | DictError* = object of Exception 12 | 13 | # internal error for not implemented bigint lib 14 | IntError* = object of Exception 15 | 16 | # internal error for keyboard interruption 17 | InterruptError* = object of Exception 18 | 19 | proc newSyntaxError(msg, fileName: string, lineNo, colNo: int): SyntaxError = 20 | new result 21 | result.msg = msg 22 | result.fileName = fileName 23 | result.lineNo = lineNo 24 | result.colNo = colNo 25 | 26 | 27 | template raiseSyntaxError*(msg: string, fileName:string, lineNo=0, colNo=0) = 28 | raise newSyntaxError(msg, fileName, lineNo, colNo) 29 | 30 | 31 | template unreachable*(msg = "Shouldn't be here") = 32 | # let optimizer to eliminate related branch 33 | when not defined(release): 34 | raise newException(InternalError, msg) 35 | -------------------------------------------------------------------------------- /npython.nimble: -------------------------------------------------------------------------------- 1 | version = "0.1.0" 2 | author = "Weitang Li" 3 | description = "(Subset of) Python programming language implemented in Nim" 4 | license = "CPython license" 5 | 6 | requires "cligen", "regex" 7 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # NPython test sets 2 | 3 | * `asserts`, tests containing asserts. Use `run.sh` to run all tests. 4 | * `basics`, basic sanity check, run by hand 5 | * `benchmark`, some primitive benchmark scripts 6 | -------------------------------------------------------------------------------- /tests/asserts/and.py: -------------------------------------------------------------------------------- 1 | a=1 2 | b=2 3 | c=3 4 | d=True 5 | flag = False 6 | if a and b and c and d: 7 | flag = True 8 | assert flag 9 | 10 | flag = False 11 | if (0 and False) or a: 12 | flag = True 13 | assert flag 14 | 15 | print("ok") 16 | -------------------------------------------------------------------------------- /tests/asserts/assert.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | 3 | assert True 4 | def foo(): 5 | assert False 6 | xfail.xfail(foo, AssertionError) 7 | 8 | print("ok") 9 | -------------------------------------------------------------------------------- /tests/asserts/bigint.py: -------------------------------------------------------------------------------- 1 | assert 100000000000 ** 10 == 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 2 | 3 | a = 1000000000000 4 | assert str(a) == "1000000000000" 5 | assert str(a * a) == "1000000000000000000000000" 6 | 7 | print("ok") 8 | -------------------------------------------------------------------------------- /tests/asserts/breakcontinue.py: -------------------------------------------------------------------------------- 1 | def single(): 2 | for i in range(10): 3 | if i == 5: 4 | break 5 | assert i == 5 6 | 7 | 8 | def double(): 9 | for i in range(10): 10 | for j in range(10): 11 | if i == j and j == 5: 12 | break 13 | if i == j: 14 | assert j == 5 or j == 9 15 | while 1: 16 | break 17 | assert i == 9 18 | 19 | while 0 < i: 20 | i = i - 1 21 | continue 22 | assert i == 0 23 | 24 | 25 | single() 26 | 27 | 28 | double() 29 | 30 | 31 | print("ok") 32 | -------------------------------------------------------------------------------- /tests/asserts/closure.py: -------------------------------------------------------------------------------- 1 | def get_add(n): 2 | def add(x): 3 | return x + n 4 | return add 5 | 6 | 7 | myadd = get_add(1) 8 | 9 | assert 2 == myadd(1) 10 | 11 | 12 | def foo(): 13 | x = 1 14 | def bar(y): 15 | def baz(): 16 | z = 1 17 | return x + y + z 18 | return baz 19 | return bar(1) 20 | 21 | 22 | assert 3 == foo()() 23 | 24 | 25 | def change(): 26 | x = 1 27 | def bar(): 28 | assert x == 2 29 | x = 2 30 | bar() 31 | 32 | change() 33 | 34 | print("ok") 35 | -------------------------------------------------------------------------------- /tests/asserts/comprehension.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | l = [i for i in range(10)] 3 | def foo(): 4 | print(i) 5 | 6 | xfail.xfail(foo, NameError) 7 | 8 | for i in range(10): 9 | assert l[i] == i 10 | print("ok") 11 | -------------------------------------------------------------------------------- /tests/asserts/decorators.py: -------------------------------------------------------------------------------- 1 | def foo(f): 2 | def bar(): 3 | return 1 4 | return bar 5 | 6 | 7 | @foo 8 | @foo 9 | def baz(): 10 | return 2 11 | 12 | 13 | assert baz() == 1 14 | 15 | 16 | def foobar(i): 17 | def foo(f): 18 | def bar(): 19 | return i 20 | return bar 21 | return foo 22 | 23 | 24 | @foobar(10) 25 | def baz(): 26 | return 2 27 | 28 | 29 | assert baz() == 10 30 | 31 | print("ok") 32 | -------------------------------------------------------------------------------- /tests/asserts/descriptors.py: -------------------------------------------------------------------------------- 1 | assert list(list.__iter__.__get__([1, 2])())[0] == 1 2 | 3 | print("ok") 4 | -------------------------------------------------------------------------------- /tests/asserts/factorial.py: -------------------------------------------------------------------------------- 1 | def factorial(x): 2 | if x == 0: 3 | return 1 4 | return x * factorial(x-1) 5 | 6 | 7 | assert factorial(10) == 3628800 8 | print("ok") 9 | -------------------------------------------------------------------------------- /tests/asserts/fib.py: -------------------------------------------------------------------------------- 1 | def fib(x): 2 | if x == 0: 3 | return 0 4 | if x == 1: 5 | return 1 6 | return fib(x-1) + fib(x-2) 7 | 8 | 9 | assert fib(20) == 6765 10 | print("ok") 11 | -------------------------------------------------------------------------------- /tests/asserts/for.py: -------------------------------------------------------------------------------- 1 | j = 0 2 | for i in [1,2,3]: 3 | j = j + i 4 | 5 | 6 | assert j == 6 7 | print("ok") 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/asserts/function.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | 3 | 4 | def cmp(a, b): 5 | return a < b 6 | 7 | 8 | assert cmp(1, 2) 9 | assert not cmp(5, 1) 10 | 11 | 12 | def more_arg(): 13 | cmp(2, 1, 3) 14 | 15 | 16 | xfail.xfail(more_arg, TypeError) 17 | 18 | 19 | def less_arg(): 20 | cmp() 21 | 22 | 23 | xfail.xfail(less_arg, TypeError) 24 | 25 | print("ok") 26 | -------------------------------------------------------------------------------- /tests/asserts/ifelse.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | 3 | 4 | if a: 5 | assert 5 6 | else: 7 | assert False 8 | 9 | 10 | print("ok") 11 | -------------------------------------------------------------------------------- /tests/asserts/import.py: -------------------------------------------------------------------------------- 1 | import fib 2 | 3 | assert 4181 == fib.fib(19) 4 | 5 | -------------------------------------------------------------------------------- /tests/asserts/in.py: -------------------------------------------------------------------------------- 1 | l = [1,2,3] 2 | assert 1 in l 3 | assert 5 not in l 4 | 5 | d = {1:2} 6 | assert 1 in d 7 | assert 2 not in d 8 | d[2] = d 9 | assert 2 in d 10 | 11 | print("ok") 12 | -------------------------------------------------------------------------------- /tests/asserts/insertsort.py: -------------------------------------------------------------------------------- 1 | array = [13, 5, 6, 12, 1, 14, 3, 2, -2, 11, 8, 0, 9, 4, 10, -1] 2 | 3 | 4 | for i in range(1, len(array)): 5 | j = i 6 | var = array[j] 7 | while j != 0 and var < array[j-1]: 8 | array[j] = array[j-1] 9 | j = j - 1 10 | array[j] = var 11 | 12 | for i in range(1, len(array)): 13 | assert array[i-1] < array[i] 14 | 15 | print("ok") 16 | -------------------------------------------------------------------------------- /tests/asserts/int.py: -------------------------------------------------------------------------------- 1 | assert int(1) == 1 2 | assert int("0") == 0 3 | assert int(-14.5) == -14 4 | print("ok") 5 | -------------------------------------------------------------------------------- /tests/asserts/list.py: -------------------------------------------------------------------------------- 1 | l = [1,3.01,2,3,4,5, 1] 2 | 3 | 4 | assert len(l) == 7 5 | 6 | 7 | l.append(False) 8 | 9 | 10 | assert not l[-1] 11 | 12 | 13 | assert l.count(1) == 2 14 | 15 | 16 | assert l.index(3) == 3 17 | 18 | 19 | l.insert(0, 0) 20 | 21 | 22 | assert l[0] == 0 23 | 24 | 25 | l.pop() 26 | 27 | 28 | assert l.pop() == 1 29 | 30 | l[-1] = 100 31 | 32 | 33 | assert l.pop() == 100 34 | 35 | 36 | print("ok") 37 | -------------------------------------------------------------------------------- /tests/asserts/magic.py: -------------------------------------------------------------------------------- 1 | class A: 2 | 3 | def __init__(self, x): 4 | self.x = x 5 | 6 | def __add__(self, y): 7 | return self.x + y 8 | 9 | def __len__(self): 10 | return 10 11 | 12 | def __iter__(self): 13 | return iter(range(10)) 14 | 15 | def __str__(self): 16 | return "bla" 17 | 18 | 19 | a = A(3) 20 | 21 | assert a + 1 == 4 22 | assert len(a) == 10 23 | 24 | i = 0 25 | for j in a: 26 | assert i == j 27 | i = i + 1 28 | 29 | assert str(a) == "bla" 30 | 31 | print("ok") 32 | -------------------------------------------------------------------------------- /tests/asserts/none.py: -------------------------------------------------------------------------------- 1 | assert None == None 2 | assert None != 1 3 | assert None == print("ok") 4 | -------------------------------------------------------------------------------- /tests/asserts/property.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | 3 | 4 | class A: 5 | def __init__(self): 6 | self.x = 1 7 | 8 | @property 9 | def y(self): 10 | return self.x 11 | 12 | 13 | a = A() 14 | assert a.x == 1 15 | assert a.y == 1 16 | 17 | 18 | def foo(): 19 | A.x 20 | 21 | 22 | xfail.xfail(foo, AttributeError) 23 | 24 | 25 | assert A.y.__get__(a) == 1 26 | 27 | 28 | print("ok") 29 | -------------------------------------------------------------------------------- /tests/asserts/quicksort.py: -------------------------------------------------------------------------------- 1 | array = [13, 5, 6, 12, 1, 14, 3, 2, -2, 11, 8, 0, 9, 4, 10, -1] 2 | 3 | 4 | def quicksort(array, start, end): 5 | if end - start < 1: 6 | return 7 | pivot = array[start] 8 | l = start 9 | r = end - 1 10 | while l < r: 11 | while l < r and pivot < array[r]: 12 | r = r - 1 13 | if l < r: 14 | array[l], array[r] = array[r], array[l] 15 | l = l + 1 16 | while l < r and array[l] < pivot: 17 | l = l + 1 18 | if l < r: 19 | array[l], array[r] = array[r], array[l] 20 | r = r - 1 21 | quicksort(array, start, l) 22 | quicksort(array, l+1, end) 23 | 24 | 25 | quicksort(array, 0, len(array)) 26 | 27 | for i in range(1, len(array)): 28 | assert array[i-1] < array[i] 29 | 30 | print("ok") 31 | -------------------------------------------------------------------------------- /tests/asserts/raise.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | 3 | 4 | def raise_name_error(): 5 | raise NameError 6 | 7 | 8 | def empty_raise(): 9 | raise 10 | 11 | 12 | def reraise(): 13 | try: 14 | 1//0 15 | except ZeroDivisionError: 16 | raise 17 | 18 | 19 | xfail.xfail(raise_name_error, NameError) 20 | xfail.xfail(empty_raise, RuntimeError) 21 | xfail.xfail(reraise, ZeroDivisionError) 22 | 23 | 24 | print("ok") 25 | -------------------------------------------------------------------------------- /tests/asserts/simpleclass.py: -------------------------------------------------------------------------------- 1 | A = type("A", (), {"x":1}) 2 | a = A() 3 | assert a.x == 1 4 | a.y = 2 5 | assert a.y == 2 6 | print("ok") 7 | -------------------------------------------------------------------------------- /tests/asserts/simpleclass2.py: -------------------------------------------------------------------------------- 1 | class A: 2 | def __init__(self): 3 | self.x = 1 4 | 5 | def foo(self, b): 6 | return self.x + b 7 | 8 | 9 | a = A() 10 | assert a.x == 1 11 | assert a.foo(1) == 2 12 | 13 | 14 | def foo(): 15 | x = 1 16 | 17 | class B: 18 | def __init__(self): 19 | self.x = x 20 | return B 21 | 22 | 23 | assert foo()().x == 1 24 | 25 | 26 | class C: 27 | def __init__(self, x): 28 | self.x = x 29 | 30 | c = C(True) 31 | assert c.x 32 | print("ok") 33 | -------------------------------------------------------------------------------- /tests/asserts/slice.py: -------------------------------------------------------------------------------- 1 | l = list(range(10)) 2 | 3 | assert l[::-1][0] == 9 4 | assert l[1:5:2][1] == 3 5 | for i in range(10): 6 | assert l[:][i] == i 7 | assert id(l) != id(l[:]) 8 | assert len(l[:9:-1]) == 0 9 | print("ok") 10 | -------------------------------------------------------------------------------- /tests/asserts/tryexcept.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | try: 3 | assert False 4 | except: 5 | pass 6 | 7 | 8 | main() 9 | 10 | 11 | def catch(): 12 | try: 13 | assert False 14 | except AssertionError: 15 | pass 16 | 17 | catch() 18 | 19 | 20 | def nocatch(): 21 | try: 22 | a 23 | except AssertionError: 24 | pass 25 | 26 | 27 | 28 | def get_name_error(fun): 29 | flag = False 30 | try: 31 | fun() 32 | except NameError: 33 | flag = True 34 | 35 | assert flag 36 | 37 | 38 | get_name_error(nocatch) 39 | 40 | 41 | def multiple_except(): 42 | flag = False 43 | try: 44 | multiple 45 | except ValueError: 46 | pass 47 | except NameError: 48 | flag = True 49 | except AssertionError: 50 | a = 1+2 51 | 52 | assert flag 53 | 54 | 55 | multiple_except() 56 | 57 | print("nested exception with A B C means ok") 58 | 59 | 60 | def nested(): 61 | try: 62 | a 63 | except: 64 | try: 65 | b 66 | except: 67 | c 68 | 69 | 70 | nested() 71 | 72 | 73 | -------------------------------------------------------------------------------- /tests/asserts/tuple.py: -------------------------------------------------------------------------------- 1 | a = True 2 | t = (1, 2, 4, a) 3 | 4 | assert t[1] == 2 5 | assert len(t) == 4 6 | assert t[::2][-1] == 4 7 | assert len(()) == 0 8 | 9 | t = 1, 10 | assert t[0] == 1 11 | 12 | t = 3, 2 13 | assert t[0] == 3 14 | assert t[1] == 2 15 | 16 | 17 | def foo(): 18 | return 1, 2 19 | 20 | 21 | assert foo() == (1, 2) 22 | 23 | assert (1, 2) != [1, 2] 24 | 25 | assert tuple([3, 4]) == (3, 4) 26 | 27 | print("ok") 28 | -------------------------------------------------------------------------------- /tests/asserts/unpack.py: -------------------------------------------------------------------------------- 1 | import xfail 2 | 3 | x, y = 1, 2 4 | 5 | assert x == 1 6 | 7 | def foo(): 8 | return True, False 9 | 10 | t, f = foo() 11 | 12 | assert t 13 | assert not f 14 | 15 | a, b, c, d = range(4) 16 | 17 | assert a < d 18 | 19 | def fail(): 20 | x,y = range(1) 21 | 22 | xfail.xfail(fail, ValueError) 23 | 24 | print("ok") 25 | -------------------------------------------------------------------------------- /tests/asserts/xfail.py: -------------------------------------------------------------------------------- 1 | def xfail(fun, excp): 2 | flag = False 3 | try: 4 | fun() 5 | except excp: 6 | flag = True 7 | 8 | assert flag 9 | -------------------------------------------------------------------------------- /tests/basics/basic.py: -------------------------------------------------------------------------------- 1 | # basic sanity check without fancy stuff 2 | a = 2 3 | 4 | b = 3 ** a - 1 5 | print(a + b) 6 | -------------------------------------------------------------------------------- /tests/basics/calc.py: -------------------------------------------------------------------------------- 1 | # basic calclation without fancy stuff 2 | a = 1 + 2 - 3 * 4 / 5 3 | b = a ** 2 % 4 4 | c = a // b 5 | 6 | print(a, b, c) 7 | -------------------------------------------------------------------------------- /tests/basics/everything.py: -------------------------------------------------------------------------------- 1 | l = [] 2 | 3 | 4 | d = dict() 5 | def foo(): 6 | for i in [1,2,3,4,5]: 7 | l.append(i ** i / (i + 2 * (i - 10))) 8 | 9 | for j in range(2 * 5): 10 | l.append(j * i) 11 | d[j] = i 12 | 13 | 14 | 15 | def bar(): 16 | i = 1 17 | while i != 10: 18 | i = i + 1 19 | foo() 20 | 21 | 22 | if True and 1 and 3: 23 | bar() 24 | 25 | print(d) 26 | 27 | if 0 or False: 28 | l.clear() 29 | 30 | 31 | print(l) 32 | print(list(range(-1, -14, -2))) 33 | 34 | 35 | b = [] 36 | b.append(b) 37 | print(b) 38 | 39 | import function 40 | 41 | print(function.foobar(1,2)) 42 | 43 | def stress(): 44 | ll = [] 45 | sz = 10 ** 4 46 | for i in range(sz): 47 | ll.append(i) 48 | print(len(ll) + 1) 49 | for j in range(sz): 50 | ll[j] = j * j 51 | return ll 52 | 53 | 54 | 55 | print(stress()[100]) 56 | 57 | -------------------------------------------------------------------------------- /tests/basics/function.py: -------------------------------------------------------------------------------- 1 | 2 | def foo(x): 3 | print(-x) 4 | 5 | def bar(): 6 | foo(2) 7 | 8 | def foobar(x,y): 9 | z = 2 10 | return x+y+2 / z 11 | 12 | foo(1) 13 | # some ccomment 14 | bar() 15 | 16 | print(foobar(1, 4)) 17 | -------------------------------------------------------------------------------- /tests/basics/ifelse.py: -------------------------------------------------------------------------------- 1 | a = 0 2 | if a: 3 | x = 2 4 | else: 5 | x = 1 6 | 7 | print(x) 8 | 9 | 10 | if not False: 11 | print("hello") 12 | 13 | if 1 <= 1: 14 | print("world") 15 | -------------------------------------------------------------------------------- /tests/basics/import.py: -------------------------------------------------------------------------------- 1 | import function 2 | 3 | 4 | print(function.foobar(1,2)) 5 | -------------------------------------------------------------------------------- /tests/basics/loop.py: -------------------------------------------------------------------------------- 1 | i = 1 2 | while i < 10: 3 | print(i) 4 | i = i + 1 5 | -------------------------------------------------------------------------------- /tests/basics/setitem.py: -------------------------------------------------------------------------------- 1 | l = [1,2,3] 2 | l[1] = True 3 | print(l[1]) 4 | -------------------------------------------------------------------------------- /tests/benchmark/f_spin.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | f = 0.0 3 | while f < 10000000.0: 4 | f = f + 1.0 5 | 6 | foo() 7 | -------------------------------------------------------------------------------- /tests/benchmark/fib.py: -------------------------------------------------------------------------------- 1 | def fib(x): 2 | if x == 0: 3 | return 0 4 | if x == 1: 5 | return 1 6 | return fib(x-1) + fib(x-2) 7 | 8 | 9 | fib(28) 10 | -------------------------------------------------------------------------------- /tests/benchmark/leak.py: -------------------------------------------------------------------------------- 1 | while True: 2 | i = 1 3 | b = "sdf" 4 | c = [i, b] 5 | -------------------------------------------------------------------------------- /tests/benchmark/mem.py: -------------------------------------------------------------------------------- 1 | i = 0 2 | l = [] 3 | while i < 4000000: 4 | i = i + 1 5 | l.append(i+0.0) 6 | 7 | while True: 8 | pass 9 | 10 | -------------------------------------------------------------------------------- /tests/benchmark/pass.py: -------------------------------------------------------------------------------- 1 | while True: 2 | pass 3 | -------------------------------------------------------------------------------- /tests/benchmark/spin.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | i = 0 3 | while i < 10000000: 4 | i = i + 1 5 | foo() 6 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | echo "tryexcept.py is expected to fail" 2 | echo "fib.py and import.py (imports fib.py) are expected to be slow" 3 | PYTHON=../Python/python 4 | for fname in ./asserts/*.py; do 5 | echo $fname 6 | $PYTHON $fname 7 | done 8 | --------------------------------------------------------------------------------