~fabrixxm/prain

802c8305cd60082c182d7a445adbccc4d08cea27 — fabrixxm 5 months ago 094a1de
Char as optimized str w len 1, add `ord()`,`chr()`, fix typos
3 files changed, 110 insertions(+), 45 deletions(-)

M README.md
M prain.py
M test.py
M README.md => README.md +6 -5
@@ 14,18 14,19 @@ run as

what works:

- data types: `int`*1*, `string`
- data types: `int`*1*, `string`, `char`*2*
- variable assignment from constant
- variables copy
- `int + int`, `int - int`
- `print()`, `int()`, `len()`
- `int + int`, `int - int`, `char + char`
- `print()`, `int()`, `len()`, `ord()`, `chr()`
- `input([prompt])`
- function definition with default arguments
- function call with positional parameters*2*
- function call with positional parameters*3*
- local vars in functions
- `global` keyword

---

1. 8-bit unsigned integer only
2. `print()` function accept `end=str` parameter
2. created from static single-char string, eg `k = "c"`
3. `print()` function accept `end=str` parameter

M prain.py => prain.py +91 -40
@@ 10,7 10,7 @@ run as
    pyrain.py [-d] <input file|-> [output file]

    -d : print debug data to stdout
    input file: python2 script. if "-", read from stdin
    input file: python3 script. if "-", read from stdin
    output file: write result to stdout if not specified
"""



@@ 22,11 22,16 @@ from typing import Any, Optional
ReturnValue = tuple[Optional[str], "Var"]
Ptr = int

class PrainInvalidNumberOfArguments(Exception):
class PrainException(Exception):
    pass

class PrainTypeError(PrainException):
    pass

class PrainInvalidNumberOfArguments(PrainException):
    pass

class PrainUndefined(Exception):
class PrainUndefined(PrainException):
    def __init__(self, name, message) -> None:
        super().__init__(message)
        self.name = name


@@ 72,21 77,27 @@ class Var:
    def _bf_print(self, ctx:'Prain') -> None:
        raise NotImplementedError()

    def _bf_copy(self, ctx) -> 'Var':
    def _bf_copy(self, ctx:'Prain') -> 'Var':
        """Copy current value to new position, return new Var"""
        raise NotImplementedError()

    def _bf_len(self, cxt) -> 'IntVal':
        raise Exception(f"Object {self} as no len")
    def _bf_len(self, cxt:'Prain') -> 'IntVal':
        raise PrainTypeError(f"Object {self} has no len")

    def _bf_int(self, ctx:'Prain') -> 'IntVal':
        raise PrainTypeError(f"Object {self} has no int")

    def _bf_int(self, cxt) -> 'IntVal':
        raise Exception(f"Object {self} as no int")
    def _bf_ord(self, ctx:'Prain') -> 'IntVal':
        raise PrainTypeError(f"Object {self} has no ord")

    def _bf_chr(self, ctx:'Prain') -> 'CharVal':
        raise PrainTypeError(f"Object {self} has no chr")

    def _bf_add(self, right: 'Var', ctx:'Prain') -> 'Var':
        raise Exception(f"Unsupported operand type(s) for +: '{self}' and '{right}'")
        raise PrainTypeError(f"Unsupported operand type(s) for +: '{self}' and '{right}'")

    def _bf_sub(self, right:'Var', ctx:'Prain') -> 'Var':
        raise Exception(f"Unsupported operand type(s) for -: '{self}' and '{right}'")    
        raise PrainTypeError(f"Unsupported operand type(s) for -: '{self}' and '{right}'")    


class IntVar(Var):


@@ 149,7 160,7 @@ class IntVar(Var):
            temp0[x+temp0-]
        """
        if right.type != IntVar:
            raise Exception(f"Unsupported operand type(s) for +: '{self}' and '{right}'")
            raise PrainTypeError(f"Unsupported operand type(s) for +: '{self}' and '{right}'")
        left_ptr = self.ptr
        right_ptr = right.ptr
        ret_ptr = ctx.ptr_next


@@ 194,7 205,7 @@ class IntVar(Var):
            temp0[y+temp0-]
        """
        if right.type != IntVar:
            raise Exception(f"Unsupported operand type(s) for -: '{self}' and '{right}'")
            raise PrainTypeError(f"Unsupported operand type(s) for -: '{self}' and '{right}'")
        left_ptr = self.ptr
        right_ptr = right.ptr
        ret_ptr = ctx.ptr_next


@@ 229,7 240,12 @@ class IntVar(Var):
        return IntVar(ret_ptr)

    def _bf_int(self, ctx) -> 'IntVar':
        return IntVar._bf_copy(self, ctx)
        return self._bf_copy(ctx)

    def _bf_chr(self, ctx:'Prain') -> 'CharVal':
        copy = self._bf_copy(ctx)
        return CharVar(copy.ptr)


class CharVar(IntVar):
    """An int-like var which is print as a single char


@@ 245,14 261,41 @@ class CharVar(IntVar):
    def _bf_print(self, ctx:'Prain') -> None:
        ctx._bf_move_pointer(self.ptr)
        ctx.out(".")
        ctx.out("\n")   
        ctx.out("\n")

    def _bf_add(self, right: 'CharVar', ctx:'Prain') -> 'CharVar':
        raise Exception(f"Unsupported operand type(s) for +: '{self}' and '{right}'")
    def _bf_int(self, ctx:'Prain') -> 'IntVal':
        raise PrainTypeError(f"Object {self} has no int")

    def _bf_len(self, ctx:'Prain') -> 'IntVal':
        return  IntVar.new_from_value(1, ctx)
    
    def _bf_ord(self, ctx:'Prain') -> 'IntVal':
        copy = self._bf_copy(ctx)
        return IntVar(copy.ptr)

    def _bf_add(self, right: 'CharVar', ctx:'Prain') -> 'StrVar':
        """
            dest = 0 self right 0
        """
        if right.type != CharVar:
            raise PrainTypeError(f"Unsupported operand type(s) for +: '{self}' and '{right}'")
        dest_ptr = ctx.ptr_next
        ctx._bf_move_pointer(dest_ptr)
        ctx._bf_clear()
        ctx.ptr_next += 1

        self._bf_copy(ctx)
        right._bf_copy(ctx)

        ctx._bf_move_pointer(dest_ptr)
        ctx._bf_clear()
        ctx.ptr_next += 1

        return StrVar(dest_ptr)


class StrVar(Var):
    """Zero-ended char string
    """Zero-delimited char string
    
    A string is saved starting at `ptr` as:
    0 <char> <char> ... 0


@@ 692,6 735,12 @@ class Prain(ast.NodeVisitor):

    def stdlib_int(self, var:Var) -> ReturnValue:
        return None, var._bf_int(self)
    
    def stdlib_ord(self, var:Var) -> ReturnValue:
        return None, var._bf_ord(self)
    
    def stdlib_chr(self, var:Var) -> ReturnValue:
        return None, var._bf_chr(self)

    #def visit_Constant(self, node: ast.Constant) -> ReturnValue:
    #    self.log(ast.dump(node))


@@ 699,21 748,22 @@ class Prain(ast.NodeVisitor):

    def visit_Num(self,node) -> ReturnValue:
        """ crea un nuovo valore numero """
        self.log("Num", ast.dump(node))
        return None, IntVar.new_from_value(node.n, self)

    def visit_Str(self, node) -> ReturnValue:
        """ crea un nuovo valore stringa """
        self.log("Str", ast.dump(node))
        return None, StrVar.new_from_value(node.s, self)
        s = node.s
        if len(s) == 1:
            return None, CharVar.new_from_value(node.s, self)
        else:
            return None, StrVar.new_from_value(node.s, self)

    def visit_Name(self, node) -> ReturnValue:
        self.log(ast.dump(node))
        var_name = node.id
        ctx = node.ctx.__class__.__name__
        if ctx=="Load":
            if not var_name in self.var_map:
                raise PrainUndefinedVariable(var_name, f"Variable '{var_name}' not defined at {node.lineno}")
                raise PrainUndefinedVariable(var_name, f"Variable '{var_name}' not defined")
            return var_name, self.var_map[var_name]
        if ctx=="Store":
            return var_name, self.var_map.get(var_name, None)


@@ 721,7 771,6 @@ class Prain(ast.NodeVisitor):
    def visit_Assign(self, node) -> ReturnValue:
        """ assign: var = value
        """
        self.log(ast.dump(node))
        left_op_name, left_op_value = self.visit(node.targets[0])
        right_op_name, right_op_value = self.visit(node.value)



@@ 735,22 784,20 @@ class Prain(ast.NodeVisitor):
        return None, None

    def visit_Return(self, node: ast.Return) -> ReturnValue:
        self.log(ast.dump(node))
        return self.visit(node.value)

    def visit_Call(self, node) -> ReturnValue:
        self.log(ast.dump(node))
        name = None
        func_name = None
        name = node.func.id
        func_name = name
        stdlib_func = None
        try:

        if name in self.var_map:
            func_name, func_var = self.visit(node.func)
            name = func_name
        except PrainUndefinedVariable as e:
        else:
            func_name = None
            # function undefined, try the stdlib
            self.log("stdlib", e.name)
            stdlib_func = getattr(self, f"stdlib_{e.name}", None)
            name = e.name
            self.log("stdlib", name)
            stdlib_func = getattr(self, f"stdlib_{name}", None)

        if stdlib_func is not None:
                args = []


@@ 775,7 822,7 @@ class Prain(ast.NodeVisitor):
            self.log(args)
            # if args are still not enough, raise an exception
            if len(args) != len(func_var.args):
                raise PrainInvalidNumberOfArguments(f"Invalid number of arguments calling '{func_name}({','.join(func_var.args)})' at {node.lineno}")
                raise PrainInvalidNumberOfArguments(f"Invalid number of arguments calling '{func_name}({','.join(func_var.args)})'")
            
            self.var_map.push()
            # define vars from aruments


@@ 789,14 836,13 @@ class Prain(ast.NodeVisitor):
                _, ret = self.visit(n)
            self.var_map.pop()
        else:
            raise PrainUndefinedFunction(name, f"Undefined function '{name}()' at {node.lineno}")
            raise PrainUndefinedFunction(name, f"Undefined function '{name}()'")
        return None, ret

    def visit_BinOp(self, node) -> ReturnValue:
        """
            operatore binario
        """
        self.log(ast.dump(node))
        _,left_op = self.visit(node.left)
        _,right_op = self.visit(node.right)
        op = node.op.__class__.__name__.lower()


@@ 810,7 856,6 @@ class Prain(ast.NodeVisitor):
        """
            store function args and body in variable.
        """
        self.log(ast.dump(node))
        argsdefs = node.args
        args = [a.arg for a in argsdefs.args]
        defaults = argsdefs.defaults


@@ 818,21 863,27 @@ class Prain(ast.NodeVisitor):
        return None, None

    def visit_Global(self, node:ast.Global) -> ReturnValue:
        self.log(ast.dump(node))
        for n in node.names:
            self.var_map.set_global(n)
        return None, None

    def visit_Expr(self, node:ast.Expr) -> ReturnValue:
        self.log(ast.dump(node))
        return self.visit(node.value)

    def visit_Module(self, node) -> ReturnValue:
        self.log(ast.dump(node))
        for n in  ast.iter_child_nodes(node):
            self.visit(n)
        return None, None

    def visit(self, node):
        if hasattr(node, 'lineno'):
            self.log(f"@{node.lineno}:{node.col_offset} : ", ast.dump(node))
        try:
            return super().visit(node)
        except PrainException as e:
            print(f"{e} at {node.lineno}")
            sys.exit(-1)


def main():
    """Main entry point"""

M test.py => test.py +13 -0
@@ 16,6 16,19 @@ print("b - a = ", d)
s = "abcdefg"
print(s)

# one-char optimized string
c = "c"
print(c, " is long ", len(c), " chars")
# which supports add
d = c + "d"
print(d,  " is long ", len(d), " chars")

# chr() and ord() to convert int<->char
c = "c"
d = ord(c)
e = chr(d + 1)
print (c, " (", d, ") + 1 = ", e)

# print with different `end`
print("test", end=":")
print("ok")