(Really basic) Interpreter

Share your advanced PureBasic knowledge/code with the community.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

(Really basic) Interpreter

Post by Trond »

Code updated For 5.20+

A really simple interpreter with 26 variables, a to z. It handles expressions with +, -, * and /. Multiply and divide is done before + and - of course. You can also use parenthesis to force a part of an expression to be evaluated first.

There are two legal statements: Assignment and output. An assignment statement has the form variable = expression.

The output statement has the form ?variable.

If people really want it I can probably also add support for:
Strict variable declaration (à la option explicit).
Multi-character variables
Math functions (sin, cos, whatever)
Boolean operators (that actually works, like AND and NOT.)

So put your vote if you want something done.

Note: It uses integers, no floats, so that 5/2 = 2. (Speaking of which: vote for a modulo operator if you want one.)

Code: Select all

OpenConsole()

#Ind = "    "
Global Look.s
Global Dim    Table.l(26)
Global Stream.s

;Logical not
;     Procedure.l Not(b.l)
;       !CMP  DWord [ESP], 0
;       !JZ   l_cl_0
;       !XOR  eax, eax
;       ProcedureReturn
;       cl_0:
;       !MOV  eax, 1
;       ProcedureReturn
;     EndProcedure

;Cleanup then end
Procedure Finish()
  PrintN("Ending.")
  Input()
  End
EndProcedure

Procedure.s VisibleToken(s.s)
  Select s.s
    Case #CR$
      s.s = "newline (CR)"
    Case #LF$
      s.s = "newline (LF)"
    Case ""
      s.s = "nothing"
    Default
      s.s = "'" + s.s + "'"
  EndSelect
  ProcedureReturn s.s
EndProcedure

;Report an error
Procedure Error(s.s)
  PrintN("Error: "+s+".")
EndProcedure

;Report an error and abort
Procedure Abort(s.s)
  Error(s.s)
  Finish()
EndProcedure

;Report what was expected
Procedure Expected(expected.s)
  Abort("Expected: " + expected + ", got '" + VisibleToken(Look) + "'")
EndProcedure

;Output a tabbed string
Procedure Emit(s.s)
  Print(#Ind + s.s)
EndProcedure

;Output a tabbed line with linefeed
Procedure EmitLn(s.s)
  Emit(s.s)
  PrintN("")
EndProcedure

;Read a character into Look
Procedure GetChar()
  Look = Left(Stream,1)
  Stream = Right(Stream, Len(Stream)-1)
EndProcedure

;Recognize whitespace
Procedure.b IsWhiteSpace(s.s)
  Select s.s
    Case " " :
    Case #TAB$ :
    Default
      ProcedureReturn 0
  EndSelect
  ProcedureReturn 1
EndProcedure

;Eat whitespace
Procedure EatWhite()
  While IsWhiteSpace(Look)
    GetChar()
  Wend
EndProcedure

;Match a specific input character
Procedure Match(s.s)
  If Look <> s
    Expected("'"+s+"'")
  Else
    GetChar()
    EatWhite()
  EndIf
EndProcedure

;Recognize and skip over a newline
Procedure Newline()
  If Look = #CR$
    GetChar()
    If Look = #LF$
      GetChar()
    EndIf
  EndIf
EndProcedure

;Recognize an alpha character
Procedure.b IsAlpha(s.s)
  ProcedureReturn Bool((Asc(UCase(s.s)) > 64 And Asc(UCase(s.s)) < 91))
EndProcedure

;Recognize an addop
Procedure.b IsAddop(s.s)
  ProcedureReturn Bool(s.s = "+" Or s.s = "-")
EndProcedure

;Recognize a mulop
Procedure.b IsMulop(s.s)
  ProcedureReturn Bool(s.s = "*" Or s.s = "/")
EndProcedure

;Recognize a decimal digit
Procedure.b IsDigit(s.s)
  ProcedureReturn Bool(Asc(UCase(s.s)) > 47 And Asc(UCase(s.s)) < 58)
EndProcedure

;Recognize an alphanumeric
Procedure.b IsAlphaNumeric(s.s)
  ProcedureReturn Bool(IsAlpha(s.s) Or IsDigit(s.s))
EndProcedure

;Get an identifier
Procedure.s GetName()
  If Not(IsAlpha(Look))
    Expected("Name")
  EndIf
  temp.s = UCase(Look)
  EatWhite()
  GetChar()
  EatWhite()
  ProcedureReturn temp.s
EndProcedure

;Get a number
Procedure.l GetNum()
  Value.l
  If Not(IsDigit(Look))
    Expected("Integer")
  EndIf
  While IsDigit(Look)
    Value = 10 * Value + Asc(Look)-Asc("0")
    GetChar()
  Wend
  ProcedureReturn Value
EndProcedure

;Read a line into the stream
Procedure ReadLn()
  Stream + Input()+#CR$ : PrintN("")
  GetChar()
  EatWhite()
EndProcedure

;Init
Procedure Init()
  ReadLn()
EndProcedure

;------------------------------

Declare.l Expression()

Procedure Outp()
  Shared Table()
  Match("?")
  EmitLn(Str(Table(Asc(GetName())-Asc("A"))))
EndProcedure

Procedure.l Factor()
  Value.l
  If Look = "("
    Match("(")
    Value = Expression()
    Match(")")
  ElseIf IsAlpha(Look)
    Value = Table(Asc(GetName())-Asc("A"))
  Else
    Value = GetNum()
  EndIf
  ProcedureReturn Value
EndProcedure

Procedure.l Term()
  Value.l
  Value.l = Factor()
  While IsMulop(Look)
    Select Look
      Case "*"
        Match("*")
        Value * Factor()
      Case "/"
        Match("/")
        Value / Factor()
    EndSelect
  Wend
  ProcedureReturn Value
EndProcedure

Procedure.l Expression()
  Value.l
  If IsAddop(Look)
    Value = 0
  Else
    Value = Term()
    While IsAddop(Look)
      Select Look
        Case "+"
          Match("+")
          Value + Term()
        Case "-"
          Match("-")
          Value - Term()
      EndSelect
    Wend
  EndIf
  ProcedureReturn Value
EndProcedure

Procedure Assignment()
  Name.s
  Name.s = GetName()
  Match("=")
  Table(Asc(Name.s)-Asc("A")) = Expression()
EndProcedure

Procedure Program()
  While Look <> "."
    Select Look
      Case "?": Outp()
      Default : Assignment()
    EndSelect
    Newline()
    ReadLn()
  Wend
EndProcedure

Init()
Program()
Finish()



sock91
New User
New User
Posts: 5
Joined: Fri Nov 25, 2005 11:17 am
Contact:

Voice

Post by sock91 »

Trond wrote:A really simple interpreter with 26 variables, a to z.
You sound rather stressed. :o)
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Why? :?:

By the way, here's a version with strict type checking and multi-character variables if anyone wants to see how easy it is to extend the simple one. Use dim varname to declare a variable and prn <expression> to print the value of an expression (can be a single variable).

Code: Select all

OpenConsole()

Structure SVariable
  Name.s
  Value.l
EndStructure

#Ind = "    "
Global Look.s
NewList Variables.SVariable()
Global Stream.s

;Logical not
Procedure.l Not(b.l) 
  !CMP  DWord [ESP], 0 
  !JZ   l_cl_0 
  !XOR  eax, eax 
  ProcedureReturn 
  cl_0: 
  !MOV  eax, 1 
  ProcedureReturn 
EndProcedure 

;Cleanup then end
Procedure Finish()
  PrintN("Ending.")
  Input()
  End
EndProcedure

Procedure.s VisibleToken(s.s)
  Select s.s
    Case #CR$
      s.s = "newline (CR)"
    Case #LF$
      s.s = "newline (LF)"
    Case ""
      s.s = "nothing"
    Default
      s.s = "'" + s.s + "'"
  EndSelect
  ProcedureReturn s.s
EndProcedure

;Report an error
Procedure Error(s.s)
  PrintN("Error: " + s + ".")
EndProcedure

;Report an error and abort
Procedure Abort(s.s)
  Error(s.s)
  Finish()
EndProcedure 

;Report what was expected
Procedure Expected(expected.s)
  Abort("Expected: " + expected + ", got '" + VisibleToken(Look) + "'")
EndProcedure

;Report an undeclared identifier and abort
Procedure Undeclared(ident.s)
  Abort("Undeclared identifier '" + ident + "'")
EndProcedure

;Report a redeclared identifier and abort
Procedure Redeclared(ident.s)
  Abort("This identifier is alredy declared: '" + ident + "'")
EndProcedure

;Output a tabbed string
Procedure Emit(s.s)
  Print(#Ind)
  ConsoleColor(7, 1)
  Print(s.s) 
  ConsoleColor(7, 0)
EndProcedure

;Output a tabbed line with linefeed
Procedure EmitLn(s.s)
  Emit(s.s)
  PrintN("")
EndProcedure

;Read a character into Look
Procedure GetChar()
  Look = Left(Stream,1)
  Stream = Right(Stream, Len(Stream)-1)
EndProcedure

;Recognize whitespace 
Procedure.b IsWhiteSpace(s.s)
  Select s.s
    Case " "
    Case #TAB$
    Default
      ProcedureReturn 0
  EndSelect
  ProcedureReturn 1
EndProcedure

;Eat whitespace
Procedure EatWhite()
  While IsWhiteSpace(Look)
    GetChar()
  Wend
EndProcedure

;Match a specific input character
Procedure Match(s.s)
  If Look <> s
    Expected("'"+s+"'")
  Else
    GetChar()
    EatWhite()
  EndIf
EndProcedure

;Recognize and skip over a newline
Procedure Newline()
  If Look = #CR$
    GetChar()
    If Look = #LF$
      GetChar()
    EndIf
  EndIf
EndProcedure

;Recognize an alpha character 
Procedure.b IsAlpha(s.s) 
  ProcedureReturn (Asc(UCase(s.s)) > 64 And Asc(UCase(s.s)) < 91) 
EndProcedure 

;Recognize an addop 
Procedure.b IsAddop(s.s) 
  ProcedureReturn (s.s = "+" Or s.s = "-") 
EndProcedure 

;Recognize a mulop 
Procedure.b IsMulop(s.s) 
  ProcedureReturn (s.s = "*" Or s.s = "/") 
EndProcedure 

;Recognize a decimal digit 
Procedure.b IsDigit(s.s) 
  ProcedureReturn (Asc(UCase(s.s)) > 47 And Asc(UCase(s.s)) < 58) 
EndProcedure 

;Recognize an alphanumeric 
Procedure.b IsAlphaNumeric(s.s) 
  ProcedureReturn IsAlpha(s.s) Or IsDigit(s.s) 
EndProcedure 

;Get an identifier 
Procedure.s GetName()
  temp.s
  If Not(IsAlpha(Look))
    Expected("Name")
  EndIf
  While IsAlphaNumeric(Look)
    temp + UCase(Look)
    GetChar()
  Wend
  EatWhite()
  ProcedureReturn temp
EndProcedure 

;Get a number
Procedure.l GetNum()
  Value.l
  If Not(IsDigit(Look))
    Expected("Integer")
  EndIf
  While IsDigit(Look)
    Value = 10 * Value + Asc(Look)-Asc("0")
    GetChar()
  Wend
  ProcedureReturn Value
EndProcedure

;Read a line into the stream
Procedure ReadLn()
  Stream + Input()+#CR$ : PrintN("")
  GetChar()
  EatWhite()
EndProcedure

;Init
Procedure Init()
  ReadLn()
EndProcedure

;------------------------------ 

Procedure VarDim(Name.s)
  ForEach Variables()
    If Variables()\Name = Name
      Redeclared(Name)
    EndIf
  Next
  AddElement(Variables())
  Variables()\Name = Name
  Variables()\Value = 0
EndProcedure

Procedure.l VarValue(Name.s)
  ForEach Variables()
    If Variables()\Name = Name
      ProcedureReturn Variables()\Value
    EndIf
  Next
  Undeclared(Name)
EndProcedure

Procedure VarSet(Name.s, Value.l)
  ForEach Variables()
    If Variables()\Name = Name
      Variables()\Value = Value
      ProcedureReturn
    EndIf
  Next
  Undeclared(Name)
EndProcedure

;------------------------------ 

Declare.l Expression()

Procedure Outp()
  ;Name.s = GetName()
  EmitLn(Str(Expression()))
EndProcedure 

Procedure.l Factor()
  Value.l
  If Look = "(" 
    Match("(")
    Value = Expression()
    Match(")")
  ElseIf IsAlpha(Look)
    Value = VarValue(GetName())
  Else
    Value = GetNum()
  EndIf
  ProcedureReturn Value
EndProcedure

Procedure.l Term() 
  Value.l 
  Value.l = Factor() 
  While IsMulop(Look) 
    Select Look 
      Case "*" 
        Match("*") 
        Value * Factor() 
      Case "/" 
        Match("/") 
        Value / Factor() 
    EndSelect 
  Wend 
  ProcedureReturn Value 
EndProcedure 

Procedure.l Expression() 
  Value.l 
  If IsAddop(Look) 
    Value = 0 
  Else 
    Value = Term() 
    While IsAddop(Look) 
      Select Look 
        Case "+" 
          Match("+") 
          Value + Term() 
        Case "-" 
          Match("-") 
          Value - Term() 
      EndSelect 
    Wend 
  EndIf 
  ProcedureReturn Value 
EndProcedure 

Procedure Declaration()
  Name.s = GetName()
  VarDim(Name.s)
EndProcedure

Procedure Assignment(Name.s)
  Match("=")
  VarSet(Name, Expression())
EndProcedure 

Procedure Program()
  Repeat
    Identifier.s = GetName()
    Select Identifier
      Case "PRN"   : Outp()
      Case "DIM"   : Declaration()
      Default      : Assignment(Identifier)
    EndSelect
    EatWhite()
    Newline()
    ReadLn()
  ForEver
EndProcedure

Init() 
Program() 
Finish()
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

Looks interesting.

Might it require some library or something?

I get "Line 9 invalid name: same as external command"

cheers
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Because you've got some conflicting userlib. The code doesn't require anything but the standard libs.
Post Reply