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)
@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.
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...
'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
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