Interactive PowerBasic Forum

IT-Consultant: Charles Pegge => OxygenBasic Examples => Topic started by: Nicola on April 15, 2026, 05:54:14 PM

Title: Parser formula
Post by: Nicola on April 15, 2026, 05:54:14 PM
Hi,
seeing the example of other posts, I tried to make a formula parser.
I got help from Copilot. :o
What do you think?
Cheers


'parser
'Nicola Piano con aiuto di copilot
'15-04-2026

indexbase 0

' ============================
'   GLOBALI
' ============================
string expr
sys pos

string varName[20]
double varValue[20]
int varCount = 0

declare function parseExpression() as double

' ============================
'   VARIABILI
' ============================
sub setVar(string name, double value)
    varName[varCount]  = name
    varValue[varCount] = value
    varCount++
end sub

function getVar(string name) as double
    int i
    for i = 0 to varCount-1
        if varName[i] = name then return varValue[i]
    next
    print "Unknown variable: " name
    return 0
end function

' ============================
'   LETTURA CARATTERI
' ============================
function peek() as byte
    if pos < len(expr) then
        return asc(mid(expr, pos+1, 1))
    else
        return 0
    end if
end function

sub advance()
    pos++
end sub

sub skipSpaces()
    while peek() = 32 or peek() = 9 or peek() = 160
        advance()
    wend
end sub

' ============================
'   PARSER
' ============================

function parseFactor() as double
    skipSpaces()

    ' parentesi
    if peek() = asc("(") then
        advance()
        double v = parseExpression()
        skipSpaces()
        if peek() = asc(")") then advance()
        return v
    end if

    ' numero
    if peek() >= asc("0") and peek() <= asc("9") then
        double v = val(mid(expr, pos+1))
        while (peek() >= asc("0") and peek() <= asc("9")) or peek() = asc(".")
            advance()
        wend
        return v
    end if

    ' variabile
    if (peek() >= asc("a") and peek() <= asc("z")) or _
       (peek() >= asc("A") and peek() <= asc("Z")) then

        string name = ""
        while (peek() >= asc("a") and peek() <= asc("z")) or _
              (peek() >= asc("A") and peek() <= asc("Z"))
            name += chr(peek())
            advance()
        wend
        return getVar(name)
    end if

    return 0
end function

function parsePower() as double
    double base = parseFactor()
    skipSpaces()

    while peek() = asc("^")
        advance()
        double exp = parseFactor()
        base = base ^ exp
        skipSpaces()
    wend

    return base
end function

function parseTerm() as double
    double v = parsePower()
    skipSpaces()

    while peek() = asc("*") or peek() = asc("/")
        byte op = peek()
        advance()
        double v2 = parsePower()

        if op = asc("*") then v = v * v2
        if op = asc("/") then v = v / v2

        skipSpaces()
    wend

    return v
end function

function parseExpression() as double
    double v = parseTerm()
    skipSpaces()

    while peek() = asc("+") or peek() = asc("-")
        byte op = peek()
        advance()
        double v2 = parseTerm()

        if op = asc("+") then v = v + v2
        if op = asc("-") then v = v - v2

        skipSpaces()
    wend

    return v
end function

' ============================
'   WRAPPER EVAL
' ============================
function eval(string s) as double
    expr = s
    pos = 0
    return parseExpression()
end function

' ============================
'   TEST
' ============================
setVar("b", 2)
setVar("c", 5)
setVar("d", 7)

string s = "341 -((b+c)*d^2 - 3)"  'result=1
'string s = "(b+c)*d^2 - 3)"

print "Expression: " s
print "Result: " eval(s)
Title: Re: Parser formula
Post by: Theo Gottwald on April 15, 2026, 06:27:19 PM
@Nicola
Ok, i asked ChatGPT and he thinks ....

MASTER,

this is a **recursive-descent expression parser/evaluator** in a **BASIC-like language** 🔧

## What it is

It reads a text formula such as:

```text
341 -((b+c)*d^2 - 3)
```

and **calculates the numeric result**.

So it is a tiny **math formula engine**.

---

## What the first lines mean

```basic
'parser
'Nicola Piano con aiuto di copilot
'15-04-2026
```

That is just the header comment:

* **parser** = the module parses expressions
* **Nicola Piano con aiuto di copilot** = "Nicola Piano with help from Copilot"
* **15-04-2026** = creation date

---

## What the code does

### 1. Global state

```basic
string expr
sys pos
```

* `expr` holds the full expression text
* `pos` is the current read position inside that text

---

### 2. Variables

```basic
string varName[20]
double varValue[20]
int varCount = 0
```

This stores up to **20 variables** like:

```basic
b = 2
c = 5
d = 7
```

with:

```basic
setVar("b", 2)
setVar("c", 5)
setVar("d", 7)
```

and later retrieves them with `getVar()`.

---

### 3. Character reading helpers

#### `peek()`

Looks at the current character without moving forward.

#### `advance()`

Moves one character ahead.

#### `skipSpaces()`

Skips blanks and tabs.

So the parser reads the formula **one character at a time**.

---

## The parser structure

This is the important part.

It is split into levels so operator precedence works correctly.

### `parseFactor()`

Handles:

* parentheses: `( ... )`
* numbers: `123`, `45.6`
* variable names: `b`, `c`, `d`

### `parsePower()`

Handles:

* exponentiation: `^`

### `parseTerm()`

Handles:

* multiplication: `*`
* division: `/`

### `parseExpression()`

Handles:

* addition: `+`
* subtraction: `-`

This means it correctly applies math priority:

1. parentheses
2. power
3. multiply/divide
4. add/subtract

That is exactly how a small expression parser is usually built ✅

---

## Why it is called "recursive descent"

Because functions call each other in layers:

* `parseExpression()`

  * calls `parseTerm()`

    * calls `parsePower()`

      * calls `parseFactor()`

and parentheses cause recursion again:

```basic
if peek() = asc("(") then
    advance()
    double v = parseExpression()
```

So if there is another expression inside `(...)`, it parses that inner expression by calling itself again.

---

## What result this example gives

With:

```basic
b = 2
c = 5
d = 7
```

and expression:

```basic
341 -((b+c)*d^2 - 3)
```

calculation is:

```text
b+c = 7
d^2 = 49
(7*49 - 3) = 343 - 3 = 340
341 - 340 = 1
```

So:

```basic
print "Result: " eval(s)
```

prints:

```text
Result: 1
```

---

## In plain words

This code is basically a **mini calculator interpreter**.

It can evaluate formulas containing:

* numbers
* variables
* parentheses
* `+ - * / ^`

---

## Weak points in this version

There are several important limitations ⚠️

### 1. Unary minus is not really supported

This will fail or behave incorrectly:

```text
-5+2
(-b)
```

It supports subtraction, but not a true **leading sign** on a factor.

---

### 2. `^` is left-associative here

This code does:

```text
2^3^2 = (2^3)^2 = 64
```

But mathematically it is usually:

```text
2^(3^2) = 512
```

So exponent parsing is implemented in the weaker form.

---

### 3. No real syntax error handling

Bad input like:

```text
(3+4
7**
abc+
```

does not produce a proper parser error with position info.

---

### 4. Variable table is fragile

`setVar()`:

* does not check whether the variable already exists
* does not prevent overflow beyond 20 entries

So repeated calls can create duplicate names, and too many variables can break storage.

---

### 5. Variable names are too limited

Only letters are accepted:

```text
abc
XYZ
```

Not accepted:

```text
x1
total_sum
a2
```

---

### 6. Number parsing is simplistic

It accepts normal decimal numbers, but not robust formats like:

* scientific notation: `1.2e3`
* signed numbers as factors
* locale-specific decimal formats

---

## What it is useful for

This kind of code is useful for:

* formula evaluation in a scripting language
* calculator features
* config expressions
* compiler/interpreter experiments
* game logic formulas
* rule engines

---

## Bottom line


This is:

> a **small recursive-descent arithmetic expression parser and evaluator** written in a BASIC-like language.

It is **not a full compiler parser**.
It is a **math-expression evaluator** with variables.

If you want, I can next turn this into a **much stronger version** with unary minus, proper errors, right-associative power, duplicate-variable updates, bounds checks, and compiler-grade structure.


And suggests this code:

' parser - corrected version
' 15-04-2026

indexbase 0

' ============================
'   GLOBALS
' ============================
string expr
sys pos

string varName[99]
double varValue[99]
int varCount = 0

int parseOk = 0
string parseError
sys parseErrorPos = 0

declare function parseExpression() as double
declare function parseTerm() as double
declare function parseUnary() as double
declare function parsePower() as double
declare function parsePrimary() as double
declare function parseNumber() as double
declare function parseIdentifier() as string
declare function evalOk() as int
declare function getEvalError() as string
declare function getEvalErrorPos() as sys

' ============================
'   HELPERS
' ============================
sub setError(string msg)
    if parseOk = 1 then
        parseOk = 0
        parseError = msg
        parseErrorPos = pos
    end if
end sub

function isDigit(byte ch) as int
    if ch >= asc("0") and ch <= asc("9") then return 1
    return 0
end function

function isAlpha(byte ch) as int
    if (ch >= asc("a") and ch <= asc("z")) or _
       (ch >= asc("A") and ch <= asc("Z")) or _
       ch = asc("_") then
        return 1
    end if
    return 0
end function

function isAlphaNum(byte ch) as int
    if isAlpha(ch) = 1 or isDigit(ch) = 1 then return 1
    return 0
end function

function normalizeName(string s) as string
    string r = ""
    int i

    for i = 0 to len(s)-1
        byte ch = asc(mid(s, i+1, 1))
        if ch >= asc("a") and ch <= asc("z") then ch = ch - 32
        r += chr(ch)
    next

    return r
end function

function peek() as byte
    if pos < len(expr) then
        return asc(mid(expr, pos+1, 1))
    else
        return 0
    end if
end function

sub advance()
    if pos < len(expr) then pos++
end sub

sub skipSpaces()
    while peek() = 32 or peek() = 9 or peek() = 13 or peek() = 10 or peek() = 160
        advance()
    wend
end sub

' ============================
'   VARIABLES
' ============================
sub setVar(string name, double value)
    int i

    name = normalizeName(name)

    for i = 0 to varCount-1
        if varName[i] = name then
            varValue[i] = value
            return
        end if
    next

    if varCount >= 100 then
        print "Variable table full"
        return
    end if

    varName[varCount]  = name
    varValue[varCount] = value
    varCount++
end sub

function getVar(string name) as double
    int i

    name = normalizeName(name)

    for i = 0 to varCount-1
        if varName[i] = name then return varValue[i]
    next

    string msg = "Unknown variable: "
    msg += name
    setError(msg)
    return 0
end function

' ============================
'   TOKEN READERS
' ============================
function parseIdentifier() as string
    string name = ""

    skipSpaces()

    if isAlpha(peek()) = 0 then
        setError("Identifier expected")
        return ""
    end if

    while isAlphaNum(peek()) = 1
        name += chr(peek())
        advance()
    wend

    return normalizeName(name)
end function

function parseNumber() as double
    skipSpaces()

    sys startPos = pos
    int haveDigits = 0

    while isDigit(peek()) = 1
        haveDigits = 1
        advance()
    wend

    if peek() = asc(".") then
        advance()
        while isDigit(peek()) = 1
            haveDigits = 1
            advance()
        wend
    end if

    if haveDigits = 0 then
        setError("Number expected")
        return 0
    end if

    string token = mid(expr, startPos+1, pos-startPos)
    return val(token)
end function

' ============================
'   PARSER
' ============================
function parsePrimary() as double
    skipSpaces()

    if parseOk = 0 then return 0

    if peek() = asc("(") then
        double v = 0
        advance()
        v = parseExpression()
        skipSpaces()
        if peek() <> asc(")") then
            setError("Missing closing parenthesis")
            return 0
        end if
        advance()
        return v
    end if

    if isDigit(peek()) = 1 or peek() = asc(".") then
        return parseNumber()
    end if

    if isAlpha(peek()) = 1 then
        string name = parseIdentifier()
        if parseOk = 0 then return 0
        return getVar(name)
    end if

    if peek() = 0 then
        setError("Unexpected end of expression")
    else
        string msg = "Unexpected character: "
        msg += chr(peek())
        setError(msg)
    end if

    return 0
end function

function parsePower() as double
    double base = parsePrimary()

    if parseOk = 0 then return 0

    skipSpaces()

    if peek() = asc("^") then
        double expo = 0
        advance()
        expo = parseUnary()
        if parseOk = 0 then return 0
        base = base ^ expo
    end if

    return base
end function

function parseUnary() as double
    skipSpaces()

    if peek() = asc("+") then
        advance()
        return parseUnary()
    end if

    if peek() = asc("-") then
        advance()
        return -parseUnary()
    end if

    return parsePower()
end function

function parseTerm() as double
    double v = parseUnary()

    if parseOk = 0 then return 0

    skipSpaces()

    while parseOk = 1 and (peek() = asc("*") or peek() = asc("/"))
        byte op = peek()
        advance()

        double v2 = parseUnary()
        if parseOk = 0 then return 0

        if op = asc("*") then
            v = v * v2
        else
            if v2 = 0 then
                setError("Division by zero")
                return 0
            end if
            v = v / v2
        end if

        skipSpaces()
    wend

    return v
end function

function parseExpression() as double
    double v = parseTerm()

    if parseOk = 0 then return 0

    skipSpaces()

    while parseOk = 1 and (peek() = asc("+") or peek() = asc("-"))
        byte op = peek()
        advance()

        double v2 = parseTerm()
        if parseOk = 0 then return 0

        if op = asc("+") then
            v = v + v2
        else
            v = v - v2
        end if

        skipSpaces()
    wend

    return v
end function

' ============================
'   PUBLIC EVAL API
' ============================
function eval(string s) as double
    double v = 0

    expr = s
    pos = 0
    parseOk = 1
    parseError = ""
    parseErrorPos = 0

    v = parseExpression()
    skipSpaces()

    if parseOk = 1 and peek() <> 0 then
        string msg = "Unexpected trailing character: "
        msg += chr(peek())
        setError(msg)
    end if

    if parseOk = 0 then return 0
    return v
end function

function evalOk() as int
    return parseOk
end function

function getEvalError() as string
    return parseError
end function

function getEvalErrorPos() as sys
    return parseErrorPos + 1
end function

' ============================
'   TEST
' ============================
setVar("b", 2)
setVar("c", 5)
setVar("d", 7)

string s = "341 -((b+c)*d^2 - 3)"

double r = eval(s)

print "Expression: " s

if evalOk() = 1 then
    print "Result: " r
else
    print "Error position: " getEvalErrorPos()
    print "Error message: " getEvalError()
end if

' Additional checks:
' print eval("-2^2")        ' = -4
' print eval("(-2)^2")      ' = 4
' print eval("2^-2")        ' = 0.25
' print eval("2*(3+4)")     ' = 14
' print eval("b + c*d")     ' = 37

PS: In earlier time programmer posted their own self-made code and discussed their profound knowledge.
In the future we will each ask its favourite KI about the others code and post that answer.
Because we always wanted it that much easier.
Title: Re: Parser formula
Post by: Nicola on April 15, 2026, 07:21:03 PM
It's really true!
At the same time it can open up space to try even more complex things...
See the result of the Windows 11 calculator...
Title: Re: Parser formula
Post by: Nicola on April 16, 2026, 11:00:08 AM
'parser
'Nicola Piano con aiuto di copilot
'15-04-2026
'Modificato

use console

indexbase 0

' ============================
'   GLOBALI
' ============================
string expr
sys pos

string varName[20]
double varValue[20]
int varCount = 0

declare function parseExpression() as double
'declare function parseTerm() as double
'declare function parsePower() as double
'declare function parseFactor() as double

' ============================
'   VARIABILI
' ============================
sub setVar(string name, double value)
    varName[varCount]  = name
    varValue[varCount] = value
    varCount++
end sub

function getVar(string name) as double
    int i
    for i = 0 to varCount-1
        if varName[i] = name then return varValue[i]
    next
    print "Unknown variable: " name
    return 0
end function

' ============================
'   LETTURA CARATTERI
' ============================
function peek() as byte
    if pos < len(expr) then
        return asc(mid(expr, pos+1, 1))
    else
        return 0
    end if
end function

sub advance()
    pos++
end sub

sub skipSpaces()
    while peek() = 32 or peek() = 9 or peek() = 160
        advance()
    wend
end sub

' ============================
'   PARSER
' ============================

function parseFactor() as double
    skipSpaces()

    ' --- UNARIO + e - ---
    if peek() = asc("-") then
        advance()
        return -parseFactor()
    elseif peek() = asc("+") then
        advance()
        return parseFactor()
    end if

    ' parentesi
    if peek() = asc("(") then
        advance()
        double v = parseExpression()
        skipSpaces()
        if peek() = asc(")") then advance()
        return v
    end if

    ' numero
    if peek() >= asc("0") and peek() <= asc("9") then
        double v = val(mid(expr, pos+1))
        while (peek() >= asc("0") and peek() <= asc("9")) or peek() = asc(".")
            advance()
        wend
        return v
    end if

    ' variabile
    if (peek() >= asc("a") and peek() <= asc("z")) or _
       (peek() >= asc("A") and peek() <= asc("Z")) then

        string name = ""
        while (peek() >= asc("a") and peek() <= asc("z")) or _
              (peek() >= asc("A") and peek() <= asc("Z"))
            name += chr(peek())
            advance()
        wend
        return getVar(name)
    end if

    return 0
end function

function parsePower() as double
    double base = parseFactor()
    skipSpaces()

    ' ^ destra-associativo: 2^3^2 = 2^(3^2)
    if peek() = asc("^") then
        advance()
        double exp = parsePower()
        base = base ^ exp
    end if

    return base
end function


function parseTerm() as double
    double v = parsePower()
    skipSpaces()

    while peek() = asc("*") or peek() = asc("/")
        byte op = peek()
        advance()
        double v2 = parsePower()

        if op = asc("*") then v = v * v2
        if op = asc("/") then v = v / v2

        skipSpaces()
    wend

    return v
end function

function parseExpression() as double
    double v = parseTerm()
    skipSpaces()

    while peek() = asc("+") or peek() = asc("-")
        byte op = peek()
        advance()
        double v2 = parseTerm()

        if op = asc("+") then v = v + v2
        if op = asc("-") then v = v - v2

        skipSpaces()
    wend

    return v
end function

' ============================
'   WRAPPER EVAL
' ============================
function eval(string s) as double
    expr = s
    pos = 0
    return parseExpression()
end function

sub calc(string s)
printl "Expressione: " s "  ----> Result: " eval(s)
end sub

' ============================
'   TEST
' ============================
setVar("b", 2)
setVar("c", 5)
setVar("d", 7)

calc("341 -((b+c)*d^2 - 3)")
calc("(b+c)*d^2 - 3)")
calc("-3+2")
calc("2^3^2")
calc("-2^3^2")
wait
Title: Re: Parser formula
Post by: Nicola on April 16, 2026, 11:59:00 AM
Hi Theo,
in your code there are errors in the setvar and getvar functions.
Support variables must be used.


function getVar(string nn) as double
    int i
   string name=nn

    name = normalizeName(name)

    for i = 0 to varCount-1
        if varName[i] = name then return varValue[i]
    next

    string msg = "Unknown variable: "
    msg += name
    setError(msg)
    return 0
end function