Τετάρτη 1 Φεβρουαρίου 2023

A Compiler Example (With all stages, from Lexical Analyzer to Virtual Machine)


This code included in info.gsb (in the setup file). There are 4 stages with a fifth alternative, the M2000 transpiler. So there is a simple language, and the source going to Lexical Analyzer, then to Syntax Analyzer, then  to code generator and we have the option to run it in a virtual machine or  translate it to M2000 source and executed (is faster from virtual machine, which also the M2000 run it).

There are less than  1000 lines of code, A 10% hold the examples from the tiny language. This code always used to test new versions/revisions of language. 

Added a comma: t=(line$) now we have to turn it as t=(line$,)  ' see comma

 Module CompilerExample {

    Set Fast !
    Module lexical_analyzer (a$){
        lim=Len(a$)
        LineNo=1
        ColumnNo=1
        Document Output$
        Buffer Scanner as Integer*lim
        Return Scanner, 0:=a$
        offset=0
        buffer1$=""
        flag_rem=true
        Ahead=lambda Scanner (a$, offset)->{
            =false
            Try {
                // second parameter is the offset in buffer units
                // third parameter is length in bytes
                =Eval$(Scanner, offset,2*len(a$))=a$
            }
        }
        Ahead2=lambda Scanner (a$, offset)->{
            =false
            Try {
                =Eval$(Scanner, offset,2) ~ a$
            }
        }
        const nl$=chr$(13)+chr$(10), quo$="""", er$="@", Ansi=3
        Try {
            Do
            If Ahead("/*", offset) Then {
                offset+=2 : ColumnNo+=2
                While not Ahead("*/", offset)
                    If Ahead(nl$, offset) Then
                        lineNo++: ColumnNo=1 : offset+=2
                    Else
                        offset++ : ColumnNo++
                    End If
                    if offset>lim then
                        Error "End-of-file in comment. Closing comment characters not found"+er$
                    End if
                End While
                offset+=2 : ColumnNo+=2
            } Else.if Ahead(nl$, offset) Then{
                LineNo++: ColumnNo=1
                offset+=2
            } Else.if Ahead(quo$, offset) Then {
                Output$=format$("{0::-10}{1::-10} ", LineNo, ColumnNo)
                offset++ : ColumnNo++
                strin=offset
                While not Ahead(quo$, offset)
                    If Ahead("/", offset) Then
                        offset+=2 : ColumnNo+=2
                    else
                        offset++ : ColumnNo++
                    End if
                    checkerror()
                End While
                Output$="String "+quote$(Eval$(Scanner, strin, (offset-strin)*2))+nl$
                offset++ : ColumnNo++
            } Else.if Ahead("'", offset) Then {
                Output$=format$("{0::-10}{1::-10} ", LineNo, ColumnNo)
                offset++ : ColumnNo++
                strin=offset
                While not Ahead("'", offset)
                    If Ahead("/", offset) Then
                        offset+=2 : ColumnNo+=2
                    else
                        offset++ : ColumnNo++
                    End if
                    checkerror()
                End While
                lit$=format$(Eval$(Scanner, strin, (offset-strin)*2))
                select case len(lit$)
                case 1
                    Output$="Integer "+str$(asc(lit$),0)+nl$
                case >1
                    {Error "Multi-character constant."+er$}
                case 0
                    {Error "Empty character constant."+er$}
                end select
                offset++ : ColumnNo++
            } Else.if Ahead2("[a-z]", offset) Then {
                strin=offset
                Output$=format$("{0::-10}{1::-10} ", LineNo, ColumnNo)
                offset++ : ColumnNo++
                While Ahead2("[a-zA-Z0-9_]", offset)
                    offset++ : ColumnNo++
                End While
                Keywords(Eval$(Scanner, strin, (offset-strin)*2))
            } Else.if Ahead2("[0-9]", offset) Then {
                strin=offset
                Output$=format$("{0::-10}{1::-10} Integer ", LineNo, ColumnNo)
                offset++ : ColumnNo++
                While Ahead2("[0-9]", offset)
                    offset++ : ColumnNo++
                End While
                if Ahead2("[a-zA-Z_]", offset) then
                    {Error " Invalid number. Starts like a number, but ends in non-numeric characters."+er$}
                else
                    Output$=Eval$(Scanner, strin, (offset-strin)*2)+nl$
                end if
            } Else {
                Symbols(Eval$(Scanner, Offset, 2))    
                offset++ : ColumnNo++
            }
            Until offset>=lim
        }
        er1$=leftpart$(error$,er$)
        if er1$<>"" then
            Print
            Report "Error:"+er1$
            Output$="(Error)"+nl$+"Error:"+er1$
        else
            Output$=format$("{0::-10}{1::-10}", LineNo, ColumnNo)+" End_of_Input"+nl$
        end if
        Push Output$
        Exit
        Clipboard Output$
        Save.Doc Output$, "lex.t", Ansi
        document lex$
        Load.Doc lex$,"lex.t", Ansi
        Report lex$

        Sub Keywords(a$)
            select case a$
            case "if"
                a$="Keyword_if"
            case "else"
                a$="Keyword_else"
            case "while"
                a$="Keyword_while"
            case "print"
                a$="Keyword_print"
            case "putc"
                a$="Keyword_putc"
            else case
                a$="Identifier "+a$
            end select
            Output$=a$+nl$
        End sub
        Sub Symbols(a$)
            select case a$
            case " ", chr$(9)
                a$=""
            case "("
                a$="LeftParen"
            case ")"
                a$="RightParen"
            case "{"
                a$="LeftBrace"
            case "}"
                a$="RightBrace"
            case ";"
                a$="Semicolon"
            case ","
                a$="Comma"
            case "*"
                a$="Op_multiply"
            case "/"
                a$="Op_divide"
            case "+"
                a$="Op_add"
            case "-"
                a$="Op_subtract"
            case "%"
                a$="Op_mod"
            case "<"
            {    if Ahead("=", offset+1) Then
                    offset++
                    a$="Op_lessequal"
                    ColumnNo++
                else
                    a$="Op_less"
                end if
            }
            case ">"
            {    if Ahead("=", offset+1) Then
                    offset++
                    ColumnNo++
                    a$="Op_greaterequal"
                else
                    a$="Op_greater"
                end if
            }
            case "="
            {    if Ahead("=", offset+1) Then
                    offset++
                    ColumnNo++
                    a$="Op_equal"
                else
                    a$="Op_assign"
                end if
            }
            case "!"
            {    if Ahead("=", offset+1) Then
                    offset++
                    ColumnNo++
                    a$="Op_notequal"
                else
                    a$="Op_not"
                end if
            }
            case "&"
            {    if Ahead("&", offset+1) Then
                    offset++
                    ColumnNo++
                    a$="Op_and"
                else
                    a$=""
                end if
            }
            case "|"
            {    if Ahead("|", offset+1) Then
                    offset++
                    ColumnNo++
                    a$="Op_or"
                else
                    a$=""
                end if
            }
            else case
                {Error "Unrecognized character."+er$}
            end select
            if a$<>"" then
            Output$=format$("{0::-10}{1::-10} ", LineNo, ColumnNo)+a$+nl$
            end if
        End Sub
        Sub checkerror()
            if offset>lim then {
                Error "End-of-line while scanning string literal. Closing string character not found before end-of-line."+er$
            } else.if Ahead(nl$,offset) then {
                Error "End-of-file while scanning string literal. Closing string character not found."+er$
            }
        End Sub
    }
    Module syntax_analyzer (b$){
        enum tokens {
            Op_add, Op_subtract, Op_not=5, Op_multiply=10, Op_divide, Op_mod,
            Op_negate, Op_less, Op_lessequal, Op_greater, Op_greaterequal,
            Op_equal, Op_notequal, Op_and, Op_or, Op_assign=100, Keyword_if=110,
            Keyword_else, Keyword_while, Keyword_print, Keyword_putc, LeftParen, RightParen,
            LeftBrace, RightBrace, Semicolon, Comma, Identifier, Integer, String, End_of_input
        }

        Inventory precedence=Op_multiply:=13, Op_divide:=13, Op_mod:=13, Op_add:=12, Op_subtract:=12
        Append precedence, Op_negate:=14, Op_not:=14, Op_less:=10, Op_lessequal:=10, Op_greater:=10
        Append precedence, Op_greaterequal:=10, Op_equal:=9, Op_notequal:=9, Op_assign:=-1, Op_and:=5
        Append precedence, Op_or:=4

        Inventory symbols=Op_multiply:="Multiply", Op_divide:="Divide", Op_mod:="Mod", Op_add:="Add"
        Append symbols, Op_negate:="Negate", Op_not:="Not", Op_less:="Less", Op_subtract:="Subtract"
        Append symbols, Op_lessequal:="LessEqual", Op_greater:="Greater", Op_greaterequal:="GreaterEqual"
        Append symbols, Op_equal:="Equal", Op_notequal:="NotEqual", Op_and:="And", Op_or:="Or"

        def lineNo, ColumnNo, m, line$, a, lim, cur=-1
        const nl$=chr$(13)+chr$(10), Ansi=3
        Dim lex$()
        lex$()=piece$(b$,chr$(13)+chr$(10))
        lim=dimension(lex$(),1)-1
        op=End_of_input
        flush
        k=0
        Try {
            push (,) ' Null
            getone(&op)
            repeat
            stmt(&op)
            shift 2 ' swap two top items
            push ("Sequence", array, array)
            k++
            until op=End_of_Input
        }
        er$=error$
        if er$<>"" then print er$ : flush: break
        Print "Ast"
        Document Output$
        prt_ast()
        Push Output$
        exit
        clipboard Output$
        Save.Doc Output$, "parse.t", Ansi
        document parse$
        Load.Doc parse$,"parse.t", Ansi
        Report parse$

        sub prt_ast(t)
            if len(t)<1 then
                Output$=";"+nl$
            else.if len(t)=3 then
                Output$=t#val$(0) +nl$
                prt_ast(t#val(1)) : prt_ast(t#val(2))
            else
                Output$=t#val$(0) +nl$
            end if
        end sub
        sub expr(p) ' only a number
            local x=(,), prev=op
            if op>=Identifier then
                x=(line$,)
                getone(&op)
            else.if op=LeftParen then
                paren_exp()
                x=array
            else.if op<10 then
                getone(&op)
                expr(precedence(int(Op_negate)))
                read local y
                if prev=Op_add then
                    x=y
                else
                    if prev=Op_subtract then prev=Op_negate
                    x=(symbols(prev), y,(,))
                End if
            else
                 {error "??? "+eval$(op)}
            end if
            local prec
            while exist(precedence, int(op))
                prev=op : prec=eval(precedence)
                if prec<14 and prec>=p else exit
                getone(&op)
                expr(prec+1) ' all operators are left associative (use prec for right a.)
                x=(symbols(int(prev)), x, array)
            End While
            Push x
        end sub
        sub paren_exp()
            expected(LeftParen)
            getone(&op)
            expr(0)
            expected(RightParen)
            getone(&op)
        end sub
        sub stmt(&op)
            local t=(,)
            if op=Identifier then
                t=(line$,)
                getone(&op)
                expected(Op_assign)
                getone(&op)
                expr(0)
                read local rightnode
                Push ("Assign",t,rightnode)
                expected(Semicolon)
                getone(&op)
            else.if op=Semicolon then
                getone(&op)
                Push (";",)
            else.if op=Keyword_print then
                getone(&op)
                expected(LeftParen)
                repeat
                    getone(&op)
                    if op=String then
                        Push ("Prts",(line$,),(,))
                        getone(&op)
                    else
                        expr(0)
                        Push ("Prti", array,(,))
                    end if
                    t=("Sequence", t, array)
                until op<>Comma
                expected(RightParen)
                getone(&op)
                expected(Semicolon)
                getone(&op)
                push t
            else.if op=Keyword_while then
                getone(&op)
                paren_exp()
                stmt(&op)
                shift 2
                Push ("While",array, array)
            else.if op=Keyword_if then
                getone(&op)
                paren_exp()
                stmt(&op)
                local s2=(,)
                if op=Keyword_else then
                    getone(&op)
                    stmt(&op)
                    read s2
                end if
                shift 2
                Push ("If",array ,("If",array,s2))
            else.if op=Keyword_putc then
                getone(&op)
                paren_exp()
                Push ("Prtc",array,t)
                expected(Semicolon)
                getone(&op)
            else.if op=LeftBrace then
                Brace()
            else
                error "Unkown Op"
            end if
        end sub
        Sub Brace()
                getone(&op)
                while op<>RightBrace and op<>End_of_input
                    stmt(&op)
                    t=("Sequence", t, array)
                end while
                expected(RightBrace)
                getone(&op)
                push t
        End Sub
        Sub expected(what)
            if not op=what then {Error "Expected "+eval$(what)+str$(LineNo)+","+Str$(ColumnNo)}
        End Sub
        sub getone(&op)
            op=End_of_input
            while cur<lim
            cur++
            line$=trim$(lex$(cur))
            if line$<>"" then exit
            end while
            if cur=lim then exit sub
            LineNo=Val(line$,"int",m)
            line$=mid$(line$, m)
            ColumnNo=Val(line$,"int",m)
            line$=trim$(mid$(line$, m))
            Rem : Print LineNo, ColumnNo
            m=instr(line$," ")
            if m>0 then op=Eval("."+leftpart$(line$, " ")) else op=Eval("."+line$)
        end sub
    }
    Module CodeGenerator (s$){
        Function code$(op$) {
            =format$("{0::-6} {1}", pc, op$)
            pc++
        }
        Function code2$(op$, n$) {
            =format$("{0::-6} {1} {2}", pc, op$, n$)
            pc+=5
        }
        Function code3$(op$,pc, st, ed) {
            =format$("{0::-6} {1} ({2}) {3}", pc, op$, ed-st-1, ed)
        }

        Enum tok {
            gneg, gnot, gmul, gdiv, gmod, gadd, gle, gsub, glt
            gle, ggt, gge, geq, gne, gand, gor, gprtc, gprti, gprts,
            gif, gwhile, gAssign, gSeq, gstring, gidentifier, gint, gnone
        }

        // Inventories are lists with keys, or keys/data (key must be unique)
        // there is one type more the Invetory Queue which get same keys.
        // But here not used.
        Inventory symb="Multiply":=gmul, "Divide":=gdiv, "Mod":=gmod, "Add":=gadd
        Append symb, "Negate":=gneg, "Not":=gnot,"Less":=glt,"Subtract":=gsub
        Append symb, "LessEqual":=gle, "Greater":=ggt, "GreaterEqual":=gge, "Sequence":=gSeq
        Append symb, "Equal":=geq, "NotEqual":=gne, "And":=gand, "Or":=gor, "While":=gwhile
        Append symb, "Prtc":=gprtc,"Prti":=gprti,"Prts":=gprts, "Assign":=gAssign, "If":=gif
        Append symb, "String":=gstring, "Identifier":=gidentifier, "Integer":=gint, ";", gnone

        Inventory DataSet
        // We set string as key. key maybe an empty string, a string or a number.
        // so we want eash string to saved one time only.
        Inventory Strings

        Const nl$=chr$(13)+chr$(10), Ansi=3
        Def z$, lim, line$, newvar_ok, i=0
        Document message$=nl$
        Global pc     // functions have own scope, so we make it global, for this module, and childs.

        Dim lines$()
        s$=filter$(s$,chr$(9)) // exclude tabs
        Lines$()=piece$(s$,nl$) // break to lines
        lim=len(Lines$())
        Flush ' empty stack (there is a current stack of values which we use here)

        Load_Ast()
        If not stack.size=1 Then Flush : Error "Ast not loaded"
        AST=array // pop the array from stack
        Document Assembly$, Header$

        // all lines of assembly goes to stack. Maybe not in right order.
        // Push statement push to top, Data statement push to bottom of stack

        CodeGenerator(Ast)
        Data code$("halt") ' append to end of stack
        // So now we get all data (letters) from stack
        While not empty
            Assembly$=letter$+nl$
        end while
        // So now we have to place them in order
        Sort Assembly$

        // Let's make the header
        Header$=format$("Datasize: {0} Strings: {1}", Len(Dataset),Len(strings))
        // we use an iterator object, str^ is the counter, readonly, but Eval$() use it from object.
        str=each(strings)
        While str
            Header$=nl$+Eval$(str)
        End while
        Assembly$=nl$
        // insert to line 1 the Header
        Insert 1 Assembly$=Header$
        // Also we check for warnings
        If len(message$)>2 then Assembly$="Warnings: "+nl$+message$
        // So now we get a report
        // (at each 3/4 of window's lines, the printing stop and wait for user response, any key)
        Push Assembly$
        Exit
        Report Assembly$
        Clipboard Assembly$
        Save.Doc Assembly$, "code.t", Ansi
        End
        // subs have 10000 limit for recursion but can be extended to 1000000 or more.
        Sub CodeGenerator(t)

            If len(t)=3 then
                select case t#val(0)
                Case gSeq
                    CodeGenerator(t#val(1)) : CodeGenerator(t#val(2))
                Case gwhile
                {
                    local spc=pc
                    CodeGenerator(t#val(1))
                    local pc1=pc
                    pc+=5 ' room for jz
                    CodeGenerator(t#val(2))
                    data code3$("jz",pc1, pc1, pc+5)
                    data code3$("jmp",pc, pc, spc)
                    pc+=5 ' room for jmp
                }
                Case gif
                {
                    CodeGenerator(t#val(1))
                    local pc1=pc, pc2
                    pc+=5
                    CodeGenerator(t#val(2)#val(1))
                    If len(t#val(2)#val(2))>0 then
                        pc2=pc
                        pc+=5
                        data code3$("jz",pc1, pc1, pc)
                        CodeGenerator(t#val(2)#val(2))
                        data code3$("jmp",pc2, pc2, pc)
                    else
                        data code3$("jz",pc1, pc1, pc)
                    end If    
                }
                Case gAssign
                {
                    CodeGenerator(t#val(2))
                    local newvar_ok=true
                    CodeGenerator(t#val(1))
                }
                case gneg to gnot, gprtc to gprts
                    CodeGenerator(t#val(1)) : data code$(mid$(eval$(t#val(0)),2))
                case gmul to gor
                {
                    CodeGenerator(t#val(1))
                    CodeGenerator(t#val(2))
                    data code$(mid$(eval$(t#val(0)),2))
                }
                End select
            Else.if len(t)=2 then
                select case t#val(0)
                Case gString
                {
                    local spos
                    If exist(strings,t#val$(1)) then
                        spos=eval(strings!)
                    else
                        append strings, t#val$(1)    
                        spos=len(strings)-1
                    end If
                    Push code2$("push",str$(spos,0))
                }
                Case gInt
                    Push code2$("push",t#val$(1), pc)
                Case gIdentifier
                {
                    local ipos
                    If exist(dataset,t#val$(1)) then
                        ipos=Eval(dataset!) ' return position
                    else.if newvar_ok then
                        Append dataset, t#val$(1)
                        ipos=len(dataset)-1
                    else
                        message$="Variable "+t#val$(1)+" not initialized"+nl$

                    end If
                    If newvar_ok then
                        Push code2$("store","["+str$(ipos, 0)+"]")
                    else
                        Push code2$("fetch","["+str$(ipos, 0)+"]")
                    end If
                }
                end select
            End If
        End Sub
        Sub Load_Ast()
                If i>=lim then Push (,) : exit sub
                do
                line$=Trim$(lines$(i))
                I++
                tok$=piece$(line$," ")(0)
                until line$<>"" or i>=lim
                If tok$="Identifier" then
                    Push (gidentifier,trim$(Mid$(line$,11)))
                else.if tok$="Integer" then
                    long n=Val(Mid$(line$,8)) ' check overflow
                    Push (gint, Trim$(Mid$(line$,8)))
                else.if tok$="String" then
                    Push (gstring,Trim$(Mid$(line$,7)))
                else.if tok$=";" then
                    Push (,)
                Else
                    local otok=symb(tok$)
                    Load_Ast()
                    Load_Ast()
                    Shift 2
                    Push (otok,array, array)
                End If
        End Sub
    }
    Module Virtual_Machine_Interpreter (a$){
        // function to extract string, replacing escape codes.
        Function GetString$(a$) {
            s=instr(a$, chr$(34))
            m=rinstr(a$,chr$(34))-s
            if m>1 then
                // process escape codes
                =format$(mid$(a$, s+1, m-1))
            else
                =""
            end if
        }
        const nl$=chr$(13)+chr$(10)
        // we can set starting value to any number  n where 0<=n<=232
        enum op {    halt_=232, add_, sub_, mul_, div_, mod_, not_, neg_, and_, or_, lt_,
                    gt_, le_, ge_, ne_, eq_, prts_, prti_, prtc_, store_, fetch_, push_,
                jmp_, jz_
            }
             exit_now=false
        Inventory func=halt_:=lambda->{exit_now=true}
        Append func, push_:=lambda->{sp--:return stack_, sp:=eval(code_, pc as long):pc+=4}
        Append func, jz_:=lambda->{
            sp++: if eval(stack_, sp-1)=0 then pc=eval(code_, pc as long) else pc+=4
        }
        Append func, jmp_:=lambda->{pc=eval(code_, pc as long)}
        Append func, fetch_:=lambda->{sp--:Return stack_, sp:=eval(stack_, eval(code_, pc as long)):pc+=4}
        Append func, store_:=lambda->{Return stack_, eval(code_, pc as long):=eval(stack_, sp):sp++:pc+=4}
        Append func, add_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))+sint(eval(stack_, sp))):sp++}
        Append func, sub_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))-sint(eval(stack_, sp))):sp++}
        Append func, mul_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))*sint(eval(stack_, sp))):sp++}
        Append func, div_:=lambda->{Return stack_, sp+1:=uint(div(sint(eval(stack_, sp+1)),sint(eval(stack_, sp)))):sp++}
        Append func, mod_:=lambda->{Return stack_, sp+1:=uint(mod(sint(eval(stack_, sp+1)),sint(eval(stack_, sp)))) :sp++}
        Append func, not_:=lambda->{Return stack_, sp:=if(eval(stack_, sp)=0->uint(-1),0)}
        Append func, neg_:=lambda->{Return stack_, sp:=uint(-sint(eval(stack_, sp)))}
        Append func, and_:=lambda->{Return stack_, sp+1:=binary.and(eval(stack_, sp+1),eval(stack_, sp)):sp++    }
        Append func, or_:=lambda->{Return stack_, sp+1:=binary.or(eval(stack_, sp+1),eval(stack_, sp)):sp++    }
        Append func, lt_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<sint(eval(stack_, sp))->-1, 0)):sp++}
        Append func, gt_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>sint(eval(stack_, sp))->-1, 0)):sp++}
        Append func, le_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<=sint(eval(stack_, sp))->-1, 0)):sp++}
        Append func, ge_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>=sint(eval(stack_, sp))->-1, 0)):sp++}
        Append func, ne_:=lambda->{Return stack_, sp+1:=uint(if(eval(stack_, sp+1)<>eval(stack_, sp)->-1, 0)):sp++}
        Append func, eq_:=lambda->{Return stack_, sp+1:=uint(if(eval(stack_, sp+1)=eval(stack_, sp)->-1, 0)):sp++}
        Append func, prts_:=lambda->{Print #-2, strings$(eval(stack_,sp));: Refresh:sp++}
        Append func, prti_:=lambda->{Print #-2, str$(sint(eval(stack_,sp)),0);: Refresh:sp++}
        Append func, prtc_:=lambda->{Print #-2, chrcode$(eval(stack_,sp));: Refresh:sp++}
        Rem : Form 120, 60 ' change console width X height to run Ascii Mandlebrot example
        // change Report with Print #-2,   (report stop when scrolling 3/4 of height of console, waiting key or mouse key to continue)
        Print #-2, "Virtual Assembly Code:"+{
        }+a$
        Print "Prepare Byte Code"

        // get datasize
        a$=rightpart$(a$, "Datasize:")
        m=0
        data_size=val(a$, "int", m)
        a$=mid$(a$, m)
        // make stack
        if data_size>0 then Buffer Clear stack_ as long*data_size
        // dim or redim buffer append 1000 long as is.
        Buffer stack_ as long*(1000+data_size)
        // get strings
        a$=rightpart$(a$, "Strings:")
        m=0
        strings=val(a$, "int", m)
        a$=rightpart$(a$, nl$)

        if strings>0 then
            Dim strings$(strings)
            for i=0 to strings-1
                strings$(i)=GetString$(leftpart$(a$, nl$))
                a$=rightpart$(a$, nl$)
            Next i
        End if
        buffer clear code_ as byte*1000
        do
            m=0
            offset=val(a$,"int", m)
            if m<0 then exit
            a$=mid$(a$,m)
            line$=trim$(leftpart$(a$,nl$))
            if line$="" then line$=trim$(a$) else a$=trim$(rightpart$(a$, nl$))
            op$=if$(instr(line$," ")>0->leftpart$(line$," "), line$)
            if not valid(eval(op$+"_")) then exit
            opc=eval(op$+"_")
            Return code_, offset:=opc
            if opc>=store_ then
                line$=rightpart$(line$," ")
                select case opc
                case store_, fetch_
                    Return code_, offset+1:=val(rightpart$(leftpart$(line$,"]"),"[")) as long : offset+=4
                case push_
                    Return code_, offset+1:=uint(val(line$)) as long : offset+=4
                case jz_, jmp_
                    Return code_, offset+1:=val(rightpart$(line$,")")) as long : offset+=4
                end select
            end if
        Always
        Print "Press any key" : Push key$ : Drop
        // Prepare VM
        def mod(a,b)=a- sint(uint(a / b))*b
        def div(a,b)=sint(uint(a / b))
        profiler
        let pc=0, sp=len(stack_) div 4
        do
            b=func(eval(code_, pc))
            pc++
            call local b()
        until exit_now
        Print
        Print "", round(Timecount/1000,2),"s"
    }
    Module Transpiler_to_M2000 (code$){
        Print #-2, "Virtual Assembly Code:"+{
        }+code$
        Print "Prepare M2000 Code"
        Flush
        prolog$={Global z
            global mod=lambda (b, a)->{push a- sint(uint(a / b))*b}
            global div=lambda (b,a)->{push sint(uint(a / b))}
            Module Halt {Error "stop"}
            Module neg {push -number}
            Module not {push number=0}
            Module and {push sint(binary.and(uint(number), uint(number)))}
            Module or {push sint(binary.or(uint(number), uint(number)))}
            Module gt {push number<number}
            Module lt {push number>number}
            Module ge {push number<=number}
            Module le {push number>=number}
            Module ne {push number<>number}
            Module eq {push number=number}
            Module mul {push number*number}
            Module div {call div()}
            Module mod {call mod()}
            Module sub {shift 2: push number - number}
            Module add {push number + number}
            Module prtc {Print #-2, chrcode$(number);:Refresh}
            Module prts {Print #-2, format$(var$(number));:Refresh}
            Module prti {Print #-2, str$(number, 0);:Refresh}
            }
        Variable$=lambda$ (many, Name$, glob as boolean=false)->{
            if many else exit
            if glob then
                ="Global "+name$+"(0 to "+str$(many,0)+")"+{
                }
            else
                ="Dim Base 0, "+name$+"("+str$(many,0)+")"+{
                }
            end if
        }
        firstline$=leftpart$(code$,13)
        code$=rightpart$(code$, 10)
        firstline$=rightpart$(firstline$, ": ")
        Vars=val(firstline$)
        firstline$=rightpart$(firstline$, ": ")
        Strings=val(firstline$)
        document feedstrings$
        if Strings Then
        for i=0 to Strings-1
            firstline$=Trim$(leftpart$(code$,13))
            code$=rightpart$(code$, 10)
            feedstrings$="var$("+str$(i,0)+") = "+firstline$+{
            }
        next
        end if
        stack new {
            {
                g$=leftpart$(code$, " (")
                if g$="" or code$="" then
                    data code$ : code$="": exit
                end if
                data g$
                code$=rightpart$(code$, 41)
                loop
            }
            while not empty{
                read g$
                code$+=g$
            }
        }
        code$=replace$("]", ")", code$)
        code$=replace$("fetch [", "fetch(", code$)
        code$=replace$("store [", "store(", code$)
        code$=replace$("jmp ", "goto ", code$)
        code$=replace$("jz", "if number else ", code$)
        Subs$={
            End
            Sub store(x)
                Read var(x)
            End Sub
            Sub fetch(x)
                Push var(x)
            End Sub
        }
        document M2000Code$="Module Run {" +prolog$ + Variable$(Vars,"var")
        M2000Code$=Variable$(Strings,"var$", true) +feedstrings$ +{
        }+"Try {" + {
        } + code$+"}"+{
        }+Subs$+"}"
         // clipboard M2000code$
        // exit
        Inline M2000Code$
        Print "Press any key" : Push key$ : Drop
        profiler
        Run
        Print
        Print round(Timecount/1000,2),"s"
    }
    print "choose example",
    menu "Mandelbrot", "primes","test case 4", "99 Bottles of Beer", "FizzBuzz", "fibonacci of 44 is 701408733", "12 factorial is 479001600"
    if menu then print menu$(menu) else exit
    select case menu
    case 1
        {
            Push {
            {
                /*
                This is an integer ascii Mandelbrot generator
                */
                left_edge= -420;
                right_edge=300;
                top_edge=300;
                bottom_edge = -300;
                x_step=7;
                y_step=15;
                
                max_iter=200;
                
                y0 = top_edge;
                while (y0 > bottom_edge) {
                    x0 = left_edge;
                    while (x0 < right_edge) {
                        y = 0;
                        x = 0;
                        the_char = ' ';
                        i = 0;
                        while (i < max_iter) {
                            x_x = (x * x) / 200;
                            y_y = (y * y) / 200;
                            if (x_x + y_y > 800 ) {
                                the_char = '0' + i;
                                if (i > 9) {
                                    the_char = '@';
                                }
                                i = max_iter;
                            }
                            y = x * y / 100 + y0;
                            x = x_x - y_y + x0;
                            i = i + 1;
                        }
                        putc(the_char);
                        x0 = x0 + x_step;
                    }
                    putc('\n');
                    y0 = y0 - y_step;
                }
            }
            }
        }
    case 2
        {
            push {
            /*
            Simple prime number generator
            */
            count = 1;
            n = 1;
            limit = 100;
            while (n < limit) {
                k=3;
                p=1;
                n=n+2;
                while ((k*k<=n) && (p)) {
                    p=n/k*k!=n;
                    k=k+2;
                }
                if (p) {
                    print(n, " is prime\n");
                    count = count + 1;
                }
            }
            print("Total primes found: ", count, "\n");
            }
        }
    case 3
    {
        push {
        /*** test printing, embedded \n and comments with lots of '*' ***/ print(42); print("\nHello World\nGood Bye\nok\n"); print("Print a slash n - \\n.\n");
        }
    }
    case 4
    {
        push {
         /* 99 bottles */ bottles = 99; while (bottles > 0) {
            print(bottles, " bottles of beer on the wall\n");
            print(bottles, " bottles of beer\n");
            print("Take one down, pass it around\n");
            bottles = bottles - 1;
            print(bottles, " bottles of beer on the wall\n\n");
            }
        }
    }
    case 5
    {
        push {
        /* FizzBuzz */ i = 1; while (i <= 100) {


            if (!(i % 15))
                print("FizzBuzz");
            else if (!(i % 3))
                print("Fizz");
            else if (!(i % 5))
                print("Buzz");
            else
                print(i);
                print("\n");
                i = i + 1;
            }
        }
    }
    case 6
    {
        push {
            /* fibonacci of 44 is 701408733 */
            
            n = 44; i = 1; a = 0; b = 1; while (i < n) {
            
                w = a + b;
                a = b;
                b = w;
                i = i + 1;
            } print(w, "\n");
        }
    }
    case 7
    {
        push {
            /* 12 factorial is 479001600 */
            
            n = 12; result = 1; i = 1; while (i <= n) {
            
            result = result * i;
            i = i + 1;
            } print(result);
        }
    }
    end select
    Form! 104, 60
    Refresh
    Print "Lexical Analyzer" : Refresh
    lexical_analyzer
    if not islet then exitr
    Print "Syntax Analyzer" : Refresh
    syntax_analyzer
    if not islet then exit
    Print "Code Generator" : Refresh
    CodeGenerator
    if not islet then exit
    if ask("Choose execution style", "Info", "Virtual Machine", "*M2000 Transpiler")=1 then
        Virtual_Machine_Interpreter
    else
        Transpiler_to_M2000
    end if
    Print "done"
    Set Fast 'restore speed setting
}
CompilerExample


Vesion 12, Revision 7

 This is the most complete version of M2000. I have to revise the old English Manual, an a not too old Greek one.

Version 12 include:

Arrays with types .Previous versions use variant type internal, and have two interfaces, one with parenthesis and another with a pointer to array.


// Before Version 12 - Still in use in Version 12
Dim Alfa(10, -3 to 3)=100&
// if we paass something else because it is a variant type, take it as is.
Alfa(2, -1)=100%
Print type$(Alfa(2,-1))="Integer", type$(Alfa(9, 3))="Long"


// Version 12
Dim Beta(-2 to 2, -2 to 2) as Long Long
Beta(1,2)=100 ' 100 has the default type Double
// Cannot change type.
Print Type$(Beta(1,2))="Long Long"
// We get overflow if we try to put something which not fit.


Define variables using the type before the name, and we can omit values, (they get the default for their type). Also Variant type included for variables. Before version 12 all variables get the type from the first assignment, and there was a way to define them but only literals allowed for first value.

// old style, we can pass second time from this line, an error raised
// you have to exit the module and then you can use it (like it is a first time)
Def integer a=100, b=200


// Defining local variables when the scope is more wide, like in subs.
// also define Local arrays. We can use it for arrays with types two, look z()
// we can make strings using names without suffix S. Although suffix $ are recommented for clarity.
// Local always make new variables/arrays. So we don't include this in a Loop.
// new same names hide the old same names.


Local a1 as long=100, b1 as boolean=True, kappa(10)=10&, Z(10) as string


// Like Local, Global always make new variables, arrays.


Global K, ZZ(30) as boolean
Print type$(ZZ(2))="Boolean"


// this statement show all defined variables (but maybe not in scope with this code)
List


Variant V="Hello"
Print V+V="HelloHello", "HelloHello"=V+V // true  true
V=10
Print V+V=20, 20=V+V // return true true
V=(1,2,3,4)
Print V#sum()=10
V="Now I am String Again"
Print Mid$(V, 5)="I am String Again"


V$="I am not variable V"


// With the type before, we can redefine a variable. Always Local variables.
Double M=100, N
Print Type$(M)="Double"
// Now M converted to Integer, and stay as Integer, with overflow control.
Integer M
Print Type$(M)="Integer"
Print M=100


// we can use Global and Local before
Global double M1, M2


New type of arrays (one or two dimensions but internal the first dimension is a one dimension of arrays, so each "row" can expand as we wish, and the first column to include more arrays. We can't reduce size. We have pointer and we pass it or pass a copy easy:

// new type of arrays
// this is an array of arrays
// with one type
integer ia[10][10]=100 // has 11x11 items
long lk[3]=3 // 4 items
variant vb[10]
vb[3]=lk[] // we pass a copy of ik
Print vb[3][2]=3
vb[3][2]++
Print vb[3][2]=4
Print lk[2]=3
lk[2]+=100
vb[1]=lk // we pass a pointer (object)
Print vb[1][2]=103
vb[1][2]+=200
Print vb[1][2]=303
Print lk[2]=303 // so now ik[2] has the same value


Epression Evaluator.

Version 12 is more VB6 like, on expression evaluation. This break a little the compatibility with older versions. The differences are two:

The minus sign as unary operator behave different before power, so -2^2=2^2  (same as -2**2-2**2, M2000 has the two operators for power) , now return -8, and the older versions return 0 (because the - sign was unary to 2 so we get (-2)^2-(-2)^2 or 4-4 or 0. But the new one do something else -(2^2)-(2^2), because power operator has higher priority from unary minus.

Here we see that the unary minus change the type of second integer value (in hexadecimal format).

Print 0x8000%, -0x8000%
def typename(x)=type$(x)
Print typename(0x8000%)="Integer", typename(-0x8000%)="Long"
Print 0x8000%^2, -0x8000%^2 // Overflow error

We get overflow error because power for integer (16bit), long (32bit) and Long Long (64bit) always use internal fast multiplication and return same type or overflow. This is different from VB6 which always return a Double value.

Print 0x8000%, -0x8000%
def typename(x)=type$(x)
Print typename(0x8000%)="Integer", typename(-0x8000%)="Long"
//Print 0x8000%^2, -0x8000%^2 // Overflow error


LLz=0x8000_0000_0000_0000&&
clipboard LLz
Print LLz=-9223372036854775808&&
LLz=2&&^63&&
// Long Long return negative number (same bits with unsigned long long)
// at the 2^63
Print LLz=-9223372036854775808


Literals for different type/use:

Print 10% // Integer
Print 10& // Long
Print 10&& // Long Long
Print 10~ // Single (float 4 bytes)
Print 10 // double (8 bytes)
Print 10# // Currency (8 bytes integer X1000 - has 4 decimal digits)
Print 10@ // Decimal 29 digits (96bit) without sign and the position of decimal point


Print True, False // -1 0 (they are double values - from version 1 of M2000)
//we can define boolean, and all comparisons return boolean (Except <=>)
// a non zero value is true.
boolean k=True


def string a1$="Hello there"
string a="hello"


Print 0x100 ' Currency type used as unsigned long
Print 0x100& ' long
Print 0x100% ' Integer
Print 0x100&& ' Long Long
Print 0x0_1000_1000 ' Decimal used for unsigned Long Long
// we can use _ character inside hexadecimal value
Hex 233% ' we have the Hex as Print but for Hexadecimal values.  Return 0x00E9


Print &hFFFF& ' this is long type, using the VB6 &H


Pen #AA88FF // this is a Html Color (has a negative value, 1 to 15 are the standard colors, 0 for black)

We can use  { } for block of codes and for string literals using line breaks, Depending the code Interpreter find where a block is code or string (also the syntax coloring system for M2000 editor, which included in M2000 environment can be colorized the code using the same technic (from the statement before the block).


Enum constants now take string values too.


Many other improvements. The Test form (for executing step by step code) now has list of errors. So when we trap errors we have the list which can be open in the help pad form, and see them in real time.

There is a stop statement which can stop the execution, and writing from console we can alter local variables, define new variables, alter functions etc.

Version 12 can load controls which are register as OCX, on user forms. There are 5 controls including the base for the controls for forms (button, combo, checkbox, textbox, editbox, listbox, Image) which I design and programmed them. These are the UCPieChart, UCChart, NineButtons, ShapeEx, Radial. 

The button can be used as a timer for blinkig, combo used as menu too, textbox as spinbox, editbox for numeric/text one line input with multiple undo, as well for multiple lines, using an internal system for adjusting the coloring code procedure. Image box can be used as a frame to hold other controls, and can be used as console (There is a demo, Ver11 in info file). Listbox can be used as a multicolumn box, but isn't like Flex. Although can be used for virtual list (not included in the control but handle it with interrupt, and feeding it when ask for it. Combo boxes are a textbox with a list, and we can use it with textbox for editing with autocomplete, sorting the drop down list, or as a button with a dropdown list, or just as a list we change the caption of the button, like a split button. All the controls can be used with transparency (and the EditBox), and are unicode.

I am looking for new controls (there are some objects for different tasks, not controls such as for zip files, json files, xml files).


Here is a program to interact with internet, calling remote functions, using WinHttpRequest.    The idea was from a Bulgarian friend. You have to register as user in Microsoft 

global string DEF_ENDPOINT="https://api.cognitive.microsofttranslator.com"
declare Request "WinHttp.WinHttpRequest.5.1"
Module TranslateInit(Req, sKey as string, sRegion as String, sFromLang as String="gr", ToLang as String="en") {
    Method Req, "Open", "POST", DEF_ENDPOINT+"/translate?api-version=3.0&from="+ sFromLang + "&to=" + ToLang
    Method Req,"SetRequestHeader", "Ocp-Apim-Subscription-Key", sKey
    Method Req,"SetRequestHeader", "Ocp-Apim-Subscription-Region", sRegion
    Method Req,"SetRequestHeader", "Content-Type", "application/json"
}
Function TranslateText(Req, vTexts as variant) {
    declare Json JsonArray
    with json, "json" as json.format$()
        if type$(vTexts)="String" then
        method json, "assignpath", "0.text", vTexts
    else.if type$(vTexts)="mArray" then
        k=each(vTexts)
        while k
            method json, "assignpath", str$(k^,0)+".text", array$(k)
        end while
    end if
    /report json.format$(4)
    Method Req, "Send", json.format$(0)
    with Req, "ResponseText" as r$
    method json, "parser", r$ as resp
    =resp
}
TranslateInit Request, "SorryYourPasswordHere", "westeurope", "bg","en"
words_bg_en=("Здравейте", "Лека нощ")
json= TranslateText(Request, words_bg_en)
'json= TranslateText(Request, "Лека нощ")
with json, "json" as json.format$(), "count" as count
with json, "itempath" as json.path(), "itempath" as json.path$()
report json.format$(4)
if count>0 then
    for i=0 to count-1
        Print words_bg_en#val$(i)+" = "+json.path$(str$(i,0)+".translations.0.text")
    next
end if


         [    
        {        
            "translations" : [            
                {                
                    "text" : "Hello",
                    "to" : "en"
                }
            ]
        },
        {        
            "translations" : [            
                {                
                    "text" : "Good night",
                    "to" : "en"
                }
            ]
        }
    ]
    Здравейте = Hello
    Лека нощ = Good night

    


There is an Info file with over 300 modules. Because M2000 use modules in modules, we load all of them and we can use any of them (we can run multiple programs each one with showing or hidden environment (the console).

 

 










Τετάρτη 25 Ιανουαρίου 2023

Version 12 now is ready (99%) and work nice.

 The new version has these:

New expression evaluator: -2^2-2^2 now return -8 (older versions return 0). So Version 12 on evaluator is not compatible with older versions. Although the difference is small, some programs may need work to used with Version 12.

String variables on older and this version can have suffix $, but for older versions this was mandatory. Now the new evaluator check the types and perform string concatenation if found a string in a series of addition. So new version do that ? 123+"mm" return 123mm as string value.? 

Example 1

a=100

? "["+a+"]"="[100]"

return true

Example 2

Greek

? 123.45

return 123,45  (in code decimal pointer is always dot but print use the current one. We can change it using Greek, Latin or Locale 1033 or other number, so according locale id we get the decimal point to be used for input and print from M2000. console.

Evaluator convert always like code, using dot for decimal point.

a=123.45

? "["+a+"]"="[123.45]"

? "["+str$(a, 1032)+"]"  'ising Str$() to choose locale id for specific conversion.

String internal functions have $ suffix (for compatibility with older versions).


I have to make the new manual, there are plenty of new thing, so stay tuned....

GK


Πέμπτη 12 Ιανουαρίου 2023

Version 12 Revision 3 (test only)

You can test this pre-release here: https://github.com/M2000Interpreter/Environment/releases/tag/Ver12Rev3

New type of arrays using [ ] 

These arrays are different, they are based on RefArray class, and can be any  type, numeric and string. Can be one dimension or two. But it isn't the same as the normal array. So a 2X3 array has two arrays of 1X3 (Normal arrays are flat inside, 1x6, for the example, and the mArray class handle the indexes according the Dimension definition).

Also the editor fixed for colorize these types. We can use them in classes also, and with pointers to objects. Actually, the V member has no [ ], so we can refer to V as is (without indexes) and that is the raw object, (the RefArray) which have some useful properties, like ArrPtr (which return the memory address of the first item (for each row). Also these arrays can expand easy, by using indexes greater than the last greater index. Also these arrays may expand in both dimensions and maybe some rows have different size. We can use a definition like this:

Long Long LL[99][99] and we take a 100X100 array of Long Long (64 bit, 8 bytes each).

There is no operators like +=, so to increase 1 to 5th element in 3rd row, we use:

LL[0][4]=LL[0][4]+1

We can extract a copy of 3rd row 

M=LL[2]

We can get a copy of 100X100 items

M=LL[]

We can get just a pointer 

M=LL


class alfa {
string V[4]
}
A->alfa()
A=>v[0]="ok"
? A=>v[0]

Using variant we can change type per item.

class alfa {
variant V[4]
}
A->alfa()
A=>v[0]="ok"
? A=>v[0]
A=>v[0]=123456789012345678&&
def typ$(x)=type$(x)
? A=>v[0], typ$(A=>v[0])="Long Long"

Using a simple group, we can pass the pointer to sub alfa()

group A {
string V[4]
}
A.v[0]="ok"
? A.v[0]


alfa(a.v, 0)
? A.v[0]


alfa(a.v, 1)
? A.v[1]


sub alfa(b, x)
b[x]=b[x]+"..."
end sub


A string array use a 4 byte pointer for each string,. A variant type like the tuple below, use 16 bytes for each item (for string use only  2 bytes  type+ 4 bytes pointer from the 16 bytes). So the above code produce 1/4 for memory which aren't string data. comparing with the default variant size array of M2000.


group A {
V=("","","","")
}
link A.v to A.v()
A.v(0)="ok"
? A.v(0)


alfa(&a.v(), 0)
? A.v(0)


alfa(&a.v(), 1)
? A.v(1)


sub alfa(&b(), x)
b(x)=b(x)+"..."
end sub


More tests here

c$="ok!!!"
global string k[10], s=string$(c$,4)
? s
m=k
m[2]="ok"
? k[2]


class alfa {
// k[] is a copy of array k
variant z=k[], m
// k is a pointer to array
variant z_ponter=k
Long long k[99]
}
alfa->alfa()
? alfa=>z[2]
alfa=>m=(1,2,3,4,5,6,7,8)
? alfa=>m
alfa=>m=1000
? alfa=>m
m[2]="ok1234"
? alfa=>z[2]<>m[2]
? alfa=>z_ponter[2]=m[2]
pk=alfa=>k
for alfa {
? len(.k)=100, type$(.k[0])="Long Long"
.k[0]=1
.z[2]="ok...this is z[2]"
}
? pk[0]
alfa=>k[0]=alfa=>k[0]+1
? pk[0]
? alfa=>z[2]

The above arrays have always 0 for the first item.

The nArray which handle dimensions (max 10), can combine with refArray so we have these types (which are mArray class, but have a connection with a RefArray class)

The mArray interface and the M2000 Interpreter utilize interesting operators, here we use the *=. The 0% literal is type of Integer (2 bytes). Also there are some function with hashtag, which are get the output (for mArray) and can produce another array (here we use the #REV() function, to get the reverse order, as a copy of  the original one.


This is the example DIMVER12 in Info.gsb (exist in the setup file).

dim base 1, M(10) as integer=10
? M()
Mult=lambda (n as integer)-> {
      //dim base 0, M(n) as integer
      dim M(0 to n-1) as integer
      For i=0% to n-1 : m(i)=(i+1)*n : Next
      =M()
}
? mult(4)


dim p(1 to 5) as long=500
group alfa {
dim p(1 to 5) as decimal=500 ' try currency, double, single
}
Print alfa.p(1)
z1->(alfa)
Print z1=>p(1)


for z1 {
for i=1 to 5: .p(i)*=.0005*i: next
}


z=group(z1)
Print z.p()
Print type$(z.p(1))
m=z.p()#REV()
Print m
m+=10
L=m#val(0)
Print type$(L)
Print type$(m)="mArray"
Print m#sum()
list


There are more to discuss about Version 12, but wait the new beta releases, or the first normal release (when all string/numeric functions checked).

Version 12 has enumarations using string also. 

Enumeration Names {
a="hello"
b="hello two"
}


m=b
select case m
case a
? "is a"
case b
? " is b"
end select
? "done"
? m


Also now can be any numeric type (this is Decimal type to be used as 64bit unsigned Integer - from literal):

enum alfa {
onebig=0xFFFF_FFFF_FFFF_FFF0
otherBig=0x7FFF_FFFF_FFFF_FFFF
}
list




Κυριακή 8 Ιανουαρίου 2023

Version 12 (Rev 2)

The first release of version 12. This version has different expression evaluator.  The unary minus has  priority  like in VB6. 

1) Unary minus: -2^2 return -4 (previous versions return 4,  because unary minus processed before exponent (-4)^2.

? -2^-3^-4

-0,991479137495678

2) Previous versions return 0, because the second minus was not unary so we get 4-4 = 0. Now -2^2 return as -(2^2) and the same for the second -(2^2), so we have -4-4=-8

? -2^2-2^2

-8

Also there are more new things. But still the work is in progress

 Download Here

Κυριακή 25 Δεκεμβρίου 2022

Revision 25 Version 11

Revision 25 is the last revision for version 11. The new version 12 prepared now. This release remove two things:

1. Fix in lambda some fault code (introduced by last revision), the conv2 module in Info can be run.

2. Change of a system timer (now using one from Kernel32). The old one cause problems in Windows 10 (returning negative values earlier than 25 days, unexpected). So this bug removed (which logical is impossible to return negative value - the timer has a cycle of 49 days, so the timer beginning  from 0 at startup, has to run for the half of that duration, to became negative (as VB6 read it as signed Long - it is unsigned although, but the bits are the same, so when 32th bit turned to 1, we get  negative value by the casting to signed Long, which VB6 use). When the bug happen, the M2000 which just called not show the console and stay in a step before opening the console. So we have to close it from Task Manager.


About the 12th version (I do some additions and tomorrow expected to finish it) :

1. We have a new way to define/redefine local variables:

Double a, b=100 ,c

if variable a exist as long then change to double (the old value erased).

Version 11 has the Def double a, which works in Version 12. This statement if find a as local raise error.

2. We can use the new types string and variant..

The string type make string using names like the numbers. Until version 11 we have to use $ for the names of strings, so A$ is a string variable (a$ and A$ are the same). Now we can use A as string too (but isn't the same as A$, these are two different variable names). Functions like Mid$() now take A too if A is string type.

version 11 and 12

alfa("George")
alfa()
sub alfa(a$="World")
Print "Hello "+a$
end sub

Version 12

alfa("George")
alfa()
sub alfa(a as string="World")
Print "Hello "+a
end sub

Variant Type.

The variant type is a variable which can change types. Here we pass it by reference and change type in the Beta sub.

Version 11, no variant type, a variable can't change type (only for those we make in console, as global variables, can change types, and array items.

Module TestThis{
// when a variable get first value then the type locked
a=100
// if string has numeric value the this value change to number
a="12345"
Print a=12345
}
TestThis

This is the same for Version 12, but we have the Variant type, which do the job:

Variant a=100

Print a=100, type$(a)="Double"
a="Hello"
a+=" World"
Print a="Hello World", Type$(a)="String"
Beta(&a)
Print Type$(a)="mArray"
Sub Beta(&z as variant)
z=(1,2,3,4)
End sub

As you see, to use the Variant Type and String with same name as the numeric variables, there was a big step, because there are changes to evaluator, the IsExp and IsStrExp internal variables which use a look ahead function to find before execution if the code seams to be numerical expression (including comparisons and logical expressions) and  or seams to be string expression. So in Version 11 the expression a$+b$=c$+d$ and X>10  without checking if identifiers exist, looking the $ and the symbols + = (and others like this in other expressions) get the idea that is a numerical expression (this return a Boolean value, we have two comparisons and the AND - the knowing as logical gate AND). The look ahead procedure skip anything in parenthesis. So the A*(B+12)+C is like A*X+C for that system. Also M2000 execute all the subexpressions (even if the result for first operand is False). M2000 always execute an expression in the order we write, and process the subexpressions when the results computed. So 1*2*3+4*5*6, get 1 , find * and from that point see if there is an open parenthesis and if true then start a new IsExp, For version 12 the string variable can be found in an accepted numeric expression as the look ahead system found it: so a+a can be an addition for strings or numbers. I found a way to overcome this without breaking the normal look ahead system for numeric expressions. I have to handle the way in IsStrExp  using a bypass of the lookahead. For some code which written using for a parameter (from M2000 user code, the source) either type, string or numeric, I don't use the bypass and if isn't say string expression, the isExp do a better job and find it.

Version 12

function alfa {
// ="alfa"  // can't use "bare" this, we get error, expected numeric type
variant a="hello"
=a
}
Print alfa()+" World"

See that if we use ="alfa" for return value we get error (the look ahead function check if the result is compatible with function type (only by name, if  character $ exist). (we need to check this for the normal use of functions, this language designed for pupils). The Print function call IsExp first, which use the look ahead and find that this has a numeric something  (parenthesis skipped) at least, so begin to execute, and get the string type from alfa() and then take the symbol "+" and check to see what come next and find string literal and start to process a string expression, and at the end return the string value " World"  and at the  "+" checking that there are two operands of type string so do the addition and return the result (as a numeric result). At the returning I have to use a type checking, and if I can use the string value is ok, if not the return value is 0.

What happen to these statements: 

a="Hello"
z$=a
Print z$

The z$ expect a string expression. In Version 11 the look ahead works and see there is no string expression, so return error without process the expression. In Version 12, the expression processed, and the return type is string so it accepted. This is an error for Version 12 (and for Version 11), but in Version we don't have process of the expression after the Z$=.

a=10
z$=a

So now I will fix the Variants for Groups. I have to add arrays of specific types, Version 11 and smaller use variant type, so the name only used to get the number or the string from the value.

// version 11 and version 12
Dim A(2)
A(0):="hello",2
Link A() to A$()
Print A(0), A(1) ' print  0  for A(0) the hello isn't numeric
Print A$(0), A$(1) ' print 2 as string left justified
Print A() ' print automatic all elements in each tab position, finding types
Print A$() ' the same as before
A$(0)="12345"
Print A(0)=12345 ' convert to number each time we use A(0)
A(0)=12345
Print A(0)=12345 ' now we have number
Print A() ' print automatic all elements in each tab position, finding types
Print A$() ' the same as before

After the Version 12 complete with arrays of specific types like this Dim a() as string, b() as long I have to go to a new Interpreter using a Virtual Machine,  I have made the variable system look here



Τρίτη 13 Δεκεμβρίου 2022

Αναθεώρηση 20 Έκδοση 11

Έγιναν πολλές αλλαγές, οπότε εδώ θα γράψω μόνο για τις πιο σημαντικές.

1. Από αυτή την αναθεώρηση δουλεύουν και οι Long Long ακέραιοι (64 bit). Στα ελληνικά Μακρύς Μακρύς.


2. Μπήκε η δυνατότητα χρήσης εξωτερικών στοιχείων στις φόρμες. Έχουν ήδη περαστεί πέντε στοιχεία στο περιβάλλον της Μ2000, και έτσι προσθέτουμε το πρόθεμα Μ2000 και τελεία πριν το όνομα του στοιχείου (δείτε τα παραδείγματα). Μπορούμε να φτιάξουμε και πίνακες στοιχείων. Το ιδιαίτερο εδώ είναι ότι προς το παρόν λαμβάνουμε σε όλα τα γεγονότα τιμές με πέρασμα με αναφορά. Αυτό σημαίνει ότι θα βάζουμε το & πριν το όνομα μεταβλητής. Επίσης σημαίνει ότι αν πράγματι είναι με αναφορά τα παραδοτέα, από το αντικείμενο, θα επιστραφούν τιμές αν τις αλλάξουμε. Τα νέα στοιχεία δεν είναι δικά μου και αναφέρονται τα ονόματα αυτών που τα έφτιαξαν στο πηγαίο κώδικα, στο GitHub.

To ucChart, το ucPieChart είναι του Leandro Ascierto., το στοιχείο με τα πολλά σχήματα, είναι του Eduardo δείτε εδώ:  https://github.com/EduardoVB/ShapeEx,  το εκπληκτικό ctxNinePatch, του Vladimir Vissoultchev  (χρησιμοποιεί png αρχεία τύπου 9 patch.) και το "ταχύμετρο" του  Γερμανού Olaf.


Το στοιχείο ucChartArea, έχει γραφτεί στο κώδικα της Μ2000 και για το λόγο αυτό δίνουμε το όνομα M2000.ucChartArea; Αλλιώς αν ήταν "καταχωρημένιο" στον υπολογιστή θα μπορούσαμε να το φορτώνουμε από το σύστημα. Δείτε ότι χρειάζεται να δίνουμε τα στοιχεία σε Collection της VB6.  Οπότε υπάρχει μια συνάρτηση που δίνει αυτόματα δείκτη σε νέα λίστα.


function newCollection {
declare list1 Collection
=list1
}
flush


declare FORM1 form
declare Chart type "M2000.ucChartArea" form FORM1
Method FORM1,"move", 1000, 1000, 14000, 12000
Layer Form1 {Gradient 11, #7777aa}
With Chart,"BackColorOpacity", 0, "title", "Chart Example", "LinesCurve", true
Method Chart,"move", 1000, 1000, 12000, 10000


data  "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio"
Value1=newCollection()
while not empty
method Value1, "add", letter$
end while
Method Chart, "AddAxisItems", Value1
Value1=newCollection()
Data 2,5,7,-10, 5, 10
while not empty
method Value1, "add", number
end while
Method Chart, "AddLineSeries", "2007", Value1, color(255,0,0)
Data 8, 4, 45, -15, 9, 14
Value1=newCollection()
while not empty
method Value1, "add", number
end while
Method Chart, "AddLineSeries", "2008", Value1, color(0,0,255)
Value1=newCollection()
Data 14, 8, 16,4, 24, 3
while not empty
method Value1, "add", number
end while
Method Chart, "AddLineSeries", "2009", Value1, color(0,255,0)
function form1.click {
layer form1 {
Gradient random(7,14), #7777aa
refresh
}
}
function Chart.MouseMove(&A, &B, &X, &Y) {
Print A, B, X, Y
Refresh
}
function Chart.Click() {
Print "Thank you"
refresh
}
METHOD FORM1 "SHOW", 1


declare FORM1 NOTHING








3.'Εφτιαξα  Δυο νέες κλάσεις για πίνακες και λίστες με κλειδιά, που μπορούν να χρησιμοποιούν όποιο τύπο θέλουμε. Η διαφορά με τους πίνακες της Μ2000 είναι ότι εδώ έχουμε πίνακες με ένα τύπο. Πχ 1000 αριθμοί Long (Μακρύς) είναι 4000 bytes συν μια κρυφή δομή των 16 Bytes. Ενώ ο τωρινός πίνακας της Μ2000 είναι τύπου Variant,  με 16 Bytes για το κάθε στοιχείο, άρα 16000 bytes ή τρεις φορές περισσότερα σε σχέση με τους απλούς πίνακες. Υπάρχουν πολλοί λόγοι που φτιάχτηκαν αυτές οι δυο κλάσεις. Ίσως φτιάξω κατάλληλο interface για να γίνουν πολυδιάστατοι όπως της Μ2000. Προς το παρόν ένα αντικείμενο refArray έχει έναν κεντρικό πίνακα που παίρνει πίνακες. Ο κεντρικός είναι ας πούμε οι στήλες. Σε κάθε στήλη μπορούμε ή να βάλουμε μια τιμή (χωρίς πίνακα) ή ένα πίνακα. ενός τύπου, ή έναν Variant (γιατί και αυτό υπάρχει ως τύπος), ή ένα άλλο αντικείμενο, ακόμα και άλλα refArray. Το όνομα σημαίνει πίνακας με αναφορά, γιατί στην ουσία έχουμε έναν δείκτη στο αντικείμενο, και αυτό κρατάει όλους τους διαμορφωμένους πίνακες, σε τύπο και ποσότητηα. Έχει συνάρτηση αντιγραφής  To HashList παίρνει κλειδιά αλφαριθμητικά ή αριθμητικά (ή και τα δύο μαζί, αλλά ένα για κάθε στοιχείο). Αυτός ο τύπος λίστας χρησιμοποιεί το ref Attay. Προς το παρόν μόνο να αυξήσουμε μπορούμε το μέγεθος των πινάκων.


4. Ανανεώθηκε το QR Code (στο δικό του GutHub και εδώ στη Μ2000). Η έκδοση του QR Code με στρογγυλάδες είχε πρόβλημα. Τώρα λειτουργεί σωστά. Αυτή είναι η εικόνα που του έστειλα για να δει το πρόβλημα, και ανταποκρίθηκε γρήγορα, ο προγραμματιστής.