1259 lines
44 KiB
Plaintext
1259 lines
44 KiB
Plaintext
-- PROJECT: MATH_EVAL
|
|
-- VERSION: 1.1.0 3.12.2014 (created on LinuxMint 17, using Geany editor).
|
|
-- AUTHOR: Shian Lee
|
|
-- LANGUAGE: Euphoria 4.1.0 <http://openeuphoria.org/>
|
|
-- LICENSE: Free. Use at your own risk.
|
|
-- --------------------------------------------------------------------------
|
|
-- PURPOSE: a function which evaluate Euphoria's math expressions,
|
|
-- and returns atom as result, or an error message string.
|
|
-- typically used to calculate math expressions entered by the user.
|
|
-- math_eval supports functions, operators, variables & constants.
|
|
-- read the attached "math_eval.doc" user manual for details.
|
|
-- NOTE: * the code checks for errors so your program will not crash.
|
|
-- * expressions are evaluated in the same manner as Euphoria 4.1.
|
|
-- * sequences are not supported (it's used mainly in spreadsheets).
|
|
-- ==========================================================================
|
|
|
|
|
|
|
|
-- current version of math_eval, used by demo programs help:
|
|
public constant
|
|
MATH_EVAL_VERSION = "Expression Calculator 1.1.0, 3.12.2014, Shian Lee."
|
|
|
|
|
|
|
|
-- include Euphoria 4.1.0 library files:
|
|
include std/types.e -- useful types and constants, e.g. boolean().
|
|
include std/math.e -- math functions and math constants, e.g. abs().
|
|
include std/get.e -- value().
|
|
include std/convert.e -- to_number(), set_decimal_mark().
|
|
include std/text.e -- trim().
|
|
include std/sequence.e -- remove_all().
|
|
|
|
|
|
|
|
-- EUPHORIA OPERATORS & CONSTANT & FUNCTIONS CONSTANTS ======================
|
|
|
|
-- NOTE: functions, constants, operators - are all INDEXED. if you can't
|
|
-- implement one of them then you MUST NOT REMOVE IT; it's there
|
|
-- to be implemented later. instead you should replace the calculation
|
|
-- of functions with: return err_msg(ER_NOT_IMPLEMENTED, "func-name").
|
|
-- - this note is especially important for converting the syntax to
|
|
-- Euphoria 3.1.1.
|
|
-- - new functions/constants/operators may be appended TO THE END of a
|
|
-- list though - this will not break the existing code.
|
|
|
|
|
|
constant
|
|
-- parse_expr() keeps the index of functions, instead of the identifier
|
|
-- name itself, i.e. {FUNC_ID, func_ix}, e.g. "power" --> {'@', 25};
|
|
-- FUNC_ID must be integer, not part of the syntax, e.g. '@', 999, etc:
|
|
FUNC_ID = '@',
|
|
|
|
|
|
-- Euphoria 4 precedence lists (Euphoria 4.1 manual - 4.1.5 Precedence Chart):
|
|
-- all items in precedence list must be of "string" type:
|
|
PRECEDENCE_LISTS = { -- [index] description:
|
|
{}, -- (place holder) -- [1] parenthesis () (highest)
|
|
{}, -- (place holder) -- [2] function calls
|
|
{}, -- (place holder) -- [3] unary- unary+ not
|
|
{"*", "/"}, -- [4] * /
|
|
{"+", "-"}, -- [5] + -
|
|
{"<", ">", "<=", ">=", "=", "!="}, -- [6] < > <= >= = !=
|
|
{"and", "or", "xor"} -- [7] and or xor
|
|
},
|
|
|
|
|
|
-- all items in unary operators list must be of "string" type:
|
|
UNARY_OP_LIST = {"-", "+", "not"},
|
|
|
|
|
|
-- Euphoria 4 operators list (Euphoria 4.1 manual - 4.1.4 Expressions);
|
|
-- all items in op list must be of "string" type:
|
|
OP_LIST = { -- [index] description:
|
|
"*", -- [1] * PRECEDENCE_LISTS[4]
|
|
"/", -- [2] /
|
|
"+", -- [3] + PRECEDENCE_LISTS[5]
|
|
"-", -- [4] -
|
|
"<", -- [5] < PRECEDENCE_LISTS[6]
|
|
">", -- [6] >
|
|
"<=", -- [7] <=
|
|
">=", -- [8] >=
|
|
"=", -- [9] =
|
|
"!=", -- [10] !=
|
|
"and", -- [11] and PRECEDENCE_LISTS[7]
|
|
"or", -- [12] or
|
|
"xor", -- [13] xor
|
|
"not" -- [14] not PRECEDENCE_LISTS[3]
|
|
},
|
|
|
|
|
|
-- Euphoria 4 constants list (Euphoria 4.1 manual - 8.26.1 Math Constants);
|
|
-- these are two paralleled lists: constants names & constants values:
|
|
|
|
-- all items in const list must be of "string" type:
|
|
CONST_LIST = { -- [index]
|
|
"PI", -- [1]
|
|
"QUARTPI", -- [2]
|
|
"HALFPI", -- [3]
|
|
"TWOPI", -- [4]
|
|
"PISQR", -- [5]
|
|
"INVSQ2PI", -- [6]
|
|
"PHI", -- [7]
|
|
"E", -- [8]
|
|
"LN2", -- [9]
|
|
"INVLN2", -- [10]
|
|
"LN10", -- [11]
|
|
"INVLN10", -- [12]
|
|
"SQRT2", -- [13]
|
|
"HALFSQRT2", -- [14]
|
|
"SQRT3", -- [15]
|
|
"DEGREES_TO_RADIANS", -- [16]
|
|
"RADIANS_TO_DEGREES", -- [17]
|
|
"EULER_GAMMA", -- [18]
|
|
"SQRTE", -- [19]
|
|
"PINF", -- [20]
|
|
"MINF", -- [21]
|
|
"SQRT5" -- [22]
|
|
},
|
|
-- all items in const list values must be of atom type:
|
|
CONST_LIST_VALUES = { -- [index]
|
|
PI, -- [1]
|
|
QUARTPI, -- [2]
|
|
HALFPI, -- [3]
|
|
TWOPI, -- [4]
|
|
PISQR, -- [5]
|
|
INVSQ2PI, -- [6]
|
|
PHI, -- [7]
|
|
E, -- [8]
|
|
LN2, -- [9]
|
|
INVLN2, -- [10]
|
|
LN10, -- [11]
|
|
INVLN10, -- [12]
|
|
SQRT2, -- [13]
|
|
HALFSQRT2, -- [14]
|
|
SQRT3, -- [15]
|
|
DEGREES_TO_RADIANS, -- [16]
|
|
RADIANS_TO_DEGREES, -- [17]
|
|
EULER_GAMMA, -- [18]
|
|
SQRTE, -- [19]
|
|
PINF, -- [20]
|
|
MINF, -- [21]
|
|
SQRT5 -- [22]
|
|
},
|
|
|
|
|
|
-- Euphoria 4 functions list (+ your own documented functions...);
|
|
-- all items in functions list must be of "string" type:
|
|
FUNC_LIST = { -- [index] description:
|
|
"abs", -- [1] Eu 4.1 manual - 8.25.1 Sign and Comparisons:
|
|
"sign", -- [2]
|
|
"larger_of", -- [3]
|
|
"smaller_of", -- [4]
|
|
"remainder", -- [5] Eu 4.1 manual - 8.25.2 Roundings and Remainders:
|
|
"mod", -- [6]
|
|
"trunc", -- [7]
|
|
"frac", -- [8]
|
|
"intdiv", -- [9]
|
|
"floor", -- [10]
|
|
"ceil", -- [11]
|
|
"round", -- [12]
|
|
"arctan", -- [13] Eu 4.1 manual - 8.25.3 Trigonometry:
|
|
"tan", -- [14]
|
|
"cos", -- [15]
|
|
"sin", -- [16]
|
|
"arccos", -- [17]
|
|
"arcsin", -- [18]
|
|
"atan2", -- [19]
|
|
"rad2deg", -- [20]
|
|
"deg2rad", -- [21]
|
|
"log", -- [22] Eu 4.1 manual - 8.25.4 Logarithms and Powers:
|
|
"log10", -- [23]
|
|
"exp", -- [24]
|
|
"power", -- [25]
|
|
"sqrt", -- [26]
|
|
"fib", -- [27]
|
|
"cosh", -- [28] Eu 4.1 manual - 8.25.5 Hyperbolic Trigonometry:
|
|
"sinh", -- [29]
|
|
"tanh", -- [30]
|
|
"arcsinh", -- [31]
|
|
"arccosh", -- [32]
|
|
"arctanh", -- [33]
|
|
"and_bits", -- [34] Eu 4.1 manual - 8.25.7 Bitwise Operations:
|
|
"xor_bits", -- [35]
|
|
"or_bits", -- [36]
|
|
"not_bits", -- [37]
|
|
"shift_bits", -- [38]
|
|
"rotate_bits", -- [39]
|
|
"gcd", -- [40]
|
|
"approx", -- [41]
|
|
"powof2", -- [42]
|
|
"is_even", -- [43]
|
|
"rand", -- [44] Eu 4.1 manual - 8.27 Random Numbers:
|
|
"rand_range", -- [45]
|
|
"rnd", -- [46]
|
|
"rnd_1", -- [47]
|
|
"chance", -- [48]
|
|
"roll", -- [49]
|
|
"compare", -- [50] Eu 4.1 manual - 8.15.1 Equality
|
|
-- added in version 1.1.0:
|
|
"time" -- [51] Eu 4.1 manual - 8.4.5.1 time
|
|
}
|
|
|
|
|
|
|
|
-- OTHER CONSTANTS ==========================================================
|
|
|
|
constant
|
|
-- common error messages used by err_msg():
|
|
ER_NOT_IMPLEMENTED = "Sorry, function not implemented yet",
|
|
ER_SYNTAX_ERROR = "Syntax error in",
|
|
ER_DEV_BY_ZERO = "Division by zero in",
|
|
ER_UNDEFINED_RES = "Undefined result in",
|
|
ER_ILLEGAL_ARG = "Illegal argument in",
|
|
ER_BUG_IN_CODE = "Sorry... BUG in routine",
|
|
|
|
-- nul (ASCII-0) can be used as end-of-string marker where needed:
|
|
NUL = 0
|
|
|
|
|
|
|
|
-- TYPES & UTILITIES ========================================================
|
|
|
|
-- PURPOSE: test if s is a sequence of strings, e.g. {"xyz", "..."}.
|
|
-- NOTE: string is byte-characters from 0 to 255.
|
|
type seq_of_strings(sequence s)
|
|
for i = 1 to length(s) do
|
|
if not string(s[i]) then
|
|
return FALSE
|
|
end if
|
|
end for
|
|
return TRUE
|
|
end type
|
|
|
|
|
|
|
|
-- PURPOSE: test if s is a sequence of atoms and/or strings, e.g. {"4", -1.5}.
|
|
type seq_of_values(sequence s)
|
|
for i = 1 to length(s) do
|
|
if not atom(s[i]) then
|
|
if not string(s[i]) then
|
|
return FALSE
|
|
end if
|
|
end if
|
|
end for
|
|
return TRUE
|
|
end type
|
|
|
|
|
|
|
|
-- PURPOSE: test if s is a sequence of two sequences of the same length.
|
|
type paralleled_seq(sequence s)
|
|
if length(s) = 2 then
|
|
if sequence(s[1]) and sequence(s[2]) then
|
|
if length(s[1]) = length(s[2]) then
|
|
return TRUE
|
|
end if
|
|
end if
|
|
end if
|
|
return FALSE
|
|
end type
|
|
|
|
|
|
|
|
-- PURPOSE: identical to std/sequence.e - remove_dups(s, RD_INPLACE); just
|
|
-- much shorter, since not using sort(). See help for remove_dups().
|
|
function remove_dups_inplace(sequence source_data)
|
|
sequence lResult = {}
|
|
|
|
for i = 1 to length(source_data) do
|
|
if not find(source_data[i], lResult) then
|
|
lResult = append(lResult, source_data[i])
|
|
end if
|
|
end for
|
|
return lResult
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: construct a returned error message.
|
|
-- DETAILS: msg is the error text.
|
|
-- symbol is any element (function, operator, etc) to add. ""=none.
|
|
-- pos is position (column) of error. 0=none.
|
|
-- OUTPUT: error_occurred
|
|
-- RETURN: ascii_string; short error message.
|
|
-- EXAMPLE: st = err_msg("Arrays not supported in", "abs", 7)
|
|
-- --> st is "[Arrays not supported in 'abs' at position 7]"
|
|
-- SEEALSO: math_eval, parse_expr, set_local_variables, ER_ constants above.
|
|
constant
|
|
-- start and end of error message must be a single char:
|
|
ERR_MSG_HEAD = '[',
|
|
ERR_MSG_TAIL = ']'
|
|
boolean
|
|
error_occurred = FALSE -- reset for debug & also by set_local_variables().
|
|
function err_msg(ascii_string msg, ascii_string symbol = "", integer pos = 0)
|
|
ascii_string pos_str = ""
|
|
|
|
error_occurred = TRUE -- set error flag
|
|
|
|
if length(symbol) then
|
|
symbol = " '" & symbol & "'"
|
|
end if
|
|
if pos then
|
|
pos_str = sprintf(" at position %d", pos)
|
|
end if
|
|
|
|
return ERR_MSG_HEAD & msg & symbol & pos_str & ERR_MSG_TAIL
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: convert a string to number. supports binary, octal, decimal,
|
|
-- hexadecimal (0b, 0t, 0d, 0x, #) with decimal marker, and number
|
|
-- in scientific notation.
|
|
-- * eui supports decimal marker only for decimal, e.g. 1.2; 5.1e+2.
|
|
-- DETAILS: s is a *trimmed* string which represents a number.
|
|
-- RETURN: object; meaningless sequence on error; result atom on success.
|
|
-- NOTE: set_decimal_mark('.') is done by set_local_variables(), for
|
|
-- compatibility with 'eui' syntax.
|
|
-- EXAMPLE: x = string_to_number("0xFF") -- x is 255
|
|
-- SEEALSO: parse_expr, set_local_variables
|
|
function string_to_number(string s)
|
|
object r
|
|
integer len = length(s)
|
|
|
|
-- value() don't support '_'; to_number() don't support 0b_11 case, etc:
|
|
s = remove_all('_', s)
|
|
|
|
|
|
-- # 16 = Hexadecimal (with lower a-f and decimal marker support):
|
|
if len and s[1] = '#' then
|
|
r = to_number(s, -1)
|
|
|
|
-- 0b/0t/0d/0x Binary/Octal/Decimal/Hexadecimal numbers:
|
|
elsif len >= 2 and s[1] = '0' and find(s[2], "btdx") then
|
|
switch s[2] do
|
|
case 'b' then -- 0b 2 = Binary
|
|
s[2] = '!'
|
|
case 't' then -- 0t 8 = Octal
|
|
s[2] = '@'
|
|
case 'd' then -- 0d 10 = Decimal
|
|
s[2] = 32
|
|
case 'x' then -- 0x 16 = Hexadecimal
|
|
s[2] = '#'
|
|
end switch
|
|
|
|
r = to_number(tail(s), -1) -- ! Euphoria 3: tail(s) is s[2..len].
|
|
|
|
else -- Decimal or Number in scientific notation:
|
|
r = value(s, 1, GET_LONG_ANSWER)
|
|
if r[1] = GET_SUCCESS and r[3] = length(s) then
|
|
r = r[2]
|
|
end if
|
|
end if
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: set on enter - or - reset on exit local variables.
|
|
-- DETAILS: see 'boolean reset constants' below.
|
|
-- OUTPUT: error_occurred
|
|
-- SEEALSO: math_eval, get_identifiers, string_to_number, err_msg
|
|
constant
|
|
-- boolean reset constants:
|
|
ON_ENTER = FALSE, -- set local variables
|
|
ON_EXIT = TRUE -- reset local variables
|
|
integer
|
|
prev_decimal_mark
|
|
procedure set_local_variables(boolean reset)
|
|
if reset = ON_EXIT then
|
|
-- restore previous decimal mark ('.' or ','):
|
|
if prev_decimal_mark != '.' then
|
|
if set_decimal_mark(prev_decimal_mark) then
|
|
end if
|
|
end if
|
|
|
|
else -- reset = ON_ENTER:
|
|
-- to_number() is using '.' for compatibility with 'eui' syntax:
|
|
prev_decimal_mark = set_decimal_mark('.')
|
|
|
|
-- error flag *must* be reset *ON_ENTER* (not on exit), since
|
|
-- math_eval() is calling itself recursively and checks this flag:
|
|
error_occurred = FALSE
|
|
end if
|
|
end procedure
|
|
|
|
|
|
|
|
-- PURPOSE: convert sequence of comma-delimited-arguments to sequence of atoms.
|
|
-- DETAILS: e is sequence of comma-delimited-arguments, e.g. {5, ",", 20.1}
|
|
-- RETURN: sequence; result sequence or err_msg() string.
|
|
-- EXAMPLE: s = arguments_to_sequence({10.4, ",", -3, ",", 401})
|
|
-- --> s is {10.4, -3, 401}
|
|
-- SEEALSO: calc_func
|
|
function arguments_to_sequence(sequence e)
|
|
sequence atoms = {}
|
|
integer len = length(e)
|
|
|
|
-- check for valid arguments, e.g. {}, {1.3}, {5, ",", 20, ",", 3.1}:
|
|
if len = 0 then
|
|
return atoms
|
|
elsif remainder(len, 2) and atom(e[len]) then
|
|
for i = 1 to len - 1 by 2 do
|
|
if atom(e[i]) and equal(e[i + 1], ",") then
|
|
atoms = append(atoms, e[i])
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG)
|
|
end if
|
|
end for
|
|
|
|
return append(atoms, e[len])
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG)
|
|
end if
|
|
end function
|
|
|
|
|
|
|
|
-- CHUNK CALCULATION ========================================================
|
|
|
|
-- PURPOSE: if use_degrees is FALSE then Radians are passed to trigonometry
|
|
-- functions as tan(); if is TRUE then Degrees are passed to these
|
|
-- functions (therefor must be converted to Radians before use).
|
|
-- SEEALSO: math_eval, calc_func_1
|
|
boolean
|
|
use_degrees -- output by math_eval().
|
|
|
|
|
|
|
|
-- FUNCTIONS: calc_func_0, calc_func_1, calc_func_2, calc_func_x.
|
|
-- PURPOSE: calculate a function expression with 0 to 2 or 3+ arguments.
|
|
-- DETAILS: name is name of function, e.g. "abs".
|
|
-- ix is the function index in FUNC_LIST, e.g. 1.
|
|
-- a1, a2 are the function's arguments, by standard order;
|
|
-- a is sequence of arguments in case of calc_func_x().
|
|
-- RETURN: object; result atom or err_msg() string.
|
|
-- NOTE: calc_func_# separated into few routines for easier debugging.
|
|
-- EXAMPLE: x = calc_func_0("rnd" , 46) --> x might be 0.3343612548
|
|
-- x = calc_func_1("abs" , 1 , -7) --> x is 7
|
|
-- x = calc_func_2("power" , 25, 11, 3) --> x is 1331
|
|
-- x = calc_func_x("approx", 41, {9, 8, 0.5}) --> x is 1
|
|
-- SEEALSO: calc_func, calc_op, calc_unary_op, calc_expr
|
|
function calc_func_0(sequence name, integer ix)
|
|
atom r
|
|
|
|
switch ix do
|
|
case 46 then -- [46] "rnd"
|
|
r = rnd()
|
|
case 47 then -- [47] "rnd_1"
|
|
r = rnd_1()
|
|
case 51 then -- [51] "time"
|
|
r = time()
|
|
case else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end switch
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: see calc_func_0().
|
|
-- INPUT: use_degrees
|
|
function calc_func_1(sequence name, integer ix, atom a1)
|
|
atom r
|
|
|
|
-- first convert degrees to radians for trigonometry functions:
|
|
-- (see: Euphoria 4.1 Manual 8.25.3 Trigonometry).
|
|
if use_degrees then
|
|
switch ix do
|
|
-- [13] "arctan", [14] "tan", [15] "cos",
|
|
-- [16] "sin", [17] "arccos", [18] "arcsin",
|
|
case 13, 14, 15, 16, 17, 18 then
|
|
a1 = deg2rad(a1)
|
|
end switch
|
|
end if
|
|
|
|
|
|
switch ix do
|
|
case 1 then -- [1] "abs"
|
|
r = abs(a1)
|
|
case 2 then -- [2] "sign"
|
|
r = sign(a1)
|
|
case 7 then -- [7] "trunc"
|
|
r = trunc(a1)
|
|
case 8 then -- [8] "frac"
|
|
r = frac(a1)
|
|
case 10 then -- [10] "floor"
|
|
r = floor(a1)
|
|
case 11 then -- [11] "ceil"
|
|
r = ceil(a1)
|
|
case 13 then -- [13] "arctan"
|
|
r = arctan(a1)
|
|
case 14 then -- [14] "tan"
|
|
-- PI is infinite so we don't get Error/Zero result when a1 is 0/
|
|
-- 90/180/270/etc degrees; so convert a1 to degrees and round it:
|
|
r = remainder(round(abs(rad2deg(a1)), 1000000000), 180)
|
|
-- in case of 90, 270, 450 degrees, etc, then r is infinite:
|
|
if r = 90 then
|
|
return err_msg(ER_UNDEFINED_RES, name)
|
|
-- in case of 0, 180, 360, 540 degrees, etc, then r is 0:
|
|
elsif r != 0 then
|
|
r = tan(a1)
|
|
end if
|
|
case 15 then -- [15] "cos"
|
|
r = cos(a1)
|
|
case 16 then -- [16] "sin"
|
|
r = sin(a1)
|
|
case 17 then -- [17] "arccos"
|
|
if a1 >= -1 and a1 <= 1 then
|
|
r = arccos(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 18 then -- [18] "arcsin"
|
|
if a1 >= -1 and a1 <= 1 then
|
|
r = arcsin(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 20 then -- [20] "rad2deg"
|
|
r = rad2deg(a1)
|
|
case 21 then -- [21] "deg2rad"
|
|
r = deg2rad(a1)
|
|
case 22 then -- [22] "log"
|
|
if a1 > 0 then
|
|
r = log(a1)
|
|
else
|
|
return err_msg(ER_UNDEFINED_RES, name)
|
|
end if
|
|
case 23 then -- [23] "log10"
|
|
if a1 > 0 then
|
|
r = log10(a1)
|
|
else
|
|
return err_msg(ER_UNDEFINED_RES, name)
|
|
end if
|
|
case 24 then -- [24] "exp"
|
|
r = exp(a1)
|
|
case 26 then -- [26] "sqrt"
|
|
if a1 >= 0 then
|
|
r = sqrt(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 27 then -- [27] "fib"
|
|
if integer(a1) then
|
|
r = fib(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 28 then -- [28] "cosh"
|
|
r = cosh(a1)
|
|
case 29 then -- [29] "sinh"
|
|
r = sinh(a1)
|
|
case 30 then -- [30] "tanh"
|
|
r = tanh(a1)
|
|
case 31 then -- [31] "arcsinh"
|
|
r = arcsinh(a1)
|
|
case 32 then -- [32] "arccosh"
|
|
if a1 >= 1 then
|
|
r = arccosh(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 33 then -- [33] "arctanh"
|
|
if a1 > -1 and a1 < 1 then
|
|
r = arctanh(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 37 then -- [37] "not_bits"
|
|
r = not_bits(a1)
|
|
case 42 then -- [42] "powof2"
|
|
r = powof2(a1)
|
|
case 43 then -- [43] "is_even"
|
|
if integer(a1) then
|
|
r = is_even(a1)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
case 44 then -- [44] "rand"
|
|
r = rand(a1)
|
|
case else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end switch
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: see calc_func_0().
|
|
function calc_func_2(sequence name, integer ix, atom a1, atom a2)
|
|
atom r
|
|
|
|
-- first check for common errors:
|
|
switch ix do
|
|
-- [5] "remainder", [6] "mod", [9] "intdiv", [12] "round"
|
|
case 5, 6, 9, 12 then
|
|
if a2 = 0 then
|
|
return err_msg(ER_DEV_BY_ZERO, name)
|
|
end if
|
|
|
|
-- [38] "shift_bits", [39] "rotate_bits", [49] "roll"
|
|
case 38, 39, 49 then
|
|
if not integer(a2) then
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
end switch
|
|
|
|
|
|
switch ix do
|
|
case 3 then -- [3] "larger_of"
|
|
r = larger_of(a1, a2)
|
|
case 4 then -- [4] "smaller_of"
|
|
r = smaller_of(a1, a2)
|
|
case 5 then -- [5] "remainder"
|
|
r = remainder(a1, a2)
|
|
case 6 then -- [6] "mod"
|
|
r = mod(a1, a2)
|
|
case 9 then -- [9] "intdiv"
|
|
r = intdiv(a1, a2)
|
|
case 12 then -- [12] "round"
|
|
r = round(a1, a2)
|
|
case 19 then -- [19] "atan2"
|
|
r = atan2(a1, a2)
|
|
case 25 then -- [25] "power"
|
|
if a1 < 0 and not integer(a2) then
|
|
return err_msg(ER_UNDEFINED_RES, name)
|
|
elsif a1 = 0 and a2 < 0 then
|
|
return err_msg(ER_DEV_BY_ZERO, name)
|
|
elsif a1 = 0 and a2 = 0 then
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
else
|
|
r = power(a1, a2)
|
|
end if
|
|
case 34 then -- [34] "and_bits"
|
|
r = and_bits(a1, a2)
|
|
case 35 then -- [35] "xor_bits"
|
|
r = xor_bits(a1, a2)
|
|
case 36 then -- [36] "or_bits"
|
|
r = or_bits(a1, a2)
|
|
case 38 then -- [38] "shift_bits"
|
|
r = shift_bits(a1, a2)
|
|
case 39 then -- [39] "rotate_bits"
|
|
r = rotate_bits(a1, a2)
|
|
case 40 then -- [40] "gcd"
|
|
r = gcd(a1, a2)
|
|
case 45 then -- [45] "rand_range"
|
|
r = rand_range(a1, a2)
|
|
case 48 then -- [48] "chance"
|
|
r = chance(a1, a2)
|
|
case 49 then -- [49] "roll"
|
|
r = roll(a1, a2)
|
|
case 50 then -- [50] "compare"
|
|
r = compare(a1, a2)
|
|
case else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end switch
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: see calc_func_0().
|
|
function calc_func_x(sequence name, integer ix, sequence a)
|
|
atom r
|
|
integer len = length(a)
|
|
|
|
if ix = 41 and len = 3 then -- [41] "approx"
|
|
r = approx(a[1], a[2], a[3]) -- (no error occurs if a[3] is negative)
|
|
else
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
end if
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: calculate a function expression and return the result.
|
|
-- DETAILS: ix is the function index in FUNC_LIST.
|
|
-- 'e' is arguments for function, separated by ",".
|
|
-- RETURN: object; result atom or err_msg() string.
|
|
-- EXAMPLE: x = calc_func(25, {11, ",", 3}) --> x is 1331
|
|
-- SEEALSO: calc_func_0, calc_op, calc_unary_op, calc_expr
|
|
function calc_func(integer ix, sequence e)
|
|
sequence name = FUNC_LIST[ix], a = arguments_to_sequence(e)
|
|
|
|
if error_occurred then -- arguments_to_sequence() error.
|
|
return err_msg(ER_ILLEGAL_ARG, name)
|
|
else
|
|
switch length(a) do
|
|
case 0 then
|
|
return calc_func_0(name, ix)
|
|
case 1 then
|
|
return calc_func_1(name, ix, a[1])
|
|
case 2 then
|
|
return calc_func_2(name, ix, a[1], a[2])
|
|
case else -- 3+ arguments:
|
|
return calc_func_x(name, ix, a)
|
|
end switch
|
|
end if
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: calculate an operator expression and return the result.
|
|
-- DETAILS: 'e' is expression with two atoms & one operator, e.g. {5, "*", -1}.
|
|
-- RETURN: object; result atom or err_msg() string.
|
|
-- EXAMPLE: x = calc_op({5, "*", -1.5}) --> x is -7.5
|
|
-- SEEALSO: calc_func, calc_unary_op, calc_expr
|
|
function calc_op(sequence e)
|
|
sequence op = e[2]
|
|
atom r, a1, a2
|
|
|
|
if atom(e[1]) and atom(e[3]) then
|
|
a1 = e[1]
|
|
a2 = e[3]
|
|
else
|
|
return err_msg(ER_SYNTAX_ERROR, op)
|
|
end if
|
|
|
|
|
|
-- calc operator and two atoms, e.g. {2, "*", 1.5}:
|
|
switch find(op, OP_LIST) do -- [index] operator:
|
|
case 1 then -- [1] *
|
|
r = a1 * a2
|
|
case 2 then -- [2] /
|
|
if a2 = 0 then
|
|
return err_msg(ER_DEV_BY_ZERO, op)
|
|
else
|
|
r = a1 / a2
|
|
end if
|
|
case 3 then -- [3] +
|
|
r = a1 + a2
|
|
case 4 then -- [4] -
|
|
r = a1 - a2
|
|
case 5 then -- [5] <
|
|
r = (a1 < a2)
|
|
case 6 then -- [6] >
|
|
r = (a1 > a2)
|
|
case 7 then -- [7] <=
|
|
r = (a1 <= a2)
|
|
case 8 then -- [8] >=
|
|
r = (a1 >= a2)
|
|
case 9 then -- [9] =
|
|
r = (a1 = a2)
|
|
case 10 then -- [10] !=
|
|
r = (a1 != a2)
|
|
case 11 then -- [11] and
|
|
r = (a1 and a2)
|
|
case 12 then -- [12] or
|
|
r = (a1 or a2)
|
|
case 13 then -- [13] xor
|
|
r = (a1 xor a2)
|
|
case else
|
|
return err_msg(ER_NOT_IMPLEMENTED, op) -- helper for debug.
|
|
end switch
|
|
|
|
return r
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: calculate all cases of unary-/+ and "not" in expression, only if
|
|
-- the next item is an atom.
|
|
-- DETAILS: 'e' is expression, see example below.
|
|
-- RETURN: sequence; modified expression.
|
|
-- NOTE: cases such as -(...) or -func(...) are handled in calc_expr();
|
|
-- this function calculates atoms, e.g. '* - 15'; '( + 6'; etc.
|
|
-- EXAMPLE: s = calc_unary_op({"-", 5, "*", "-", "(", 6, "/", 2, ")"})
|
|
-- --> s is {-5, "*", "-", "(", 6, "/", 2, ")"}
|
|
-- SEEALSO: calc_expr, calc_op, calc_func
|
|
constant
|
|
ST_CHRS = {"(", ","}
|
|
function calc_unary_op(sequence e)
|
|
integer st, en,
|
|
idx = 2,
|
|
len = 1 + length(e)
|
|
|
|
e = {"+"} & e -- temporarily prepend an operator.
|
|
|
|
-- calculate unary-/+/not and one atom each loop, e.g. {"*", "-", 15}:
|
|
while idx < len do
|
|
if sequence(e[idx]) then
|
|
if find(e[idx], UNARY_OP_LIST) then
|
|
st = idx - 1
|
|
en = idx + 1
|
|
|
|
if sequence(e[st]) and atom(e[en]) then
|
|
if find(e[st], OP_LIST & ST_CHRS) then
|
|
switch e[idx] do
|
|
case "-" then
|
|
e[en] = -e[en]
|
|
case "not" then
|
|
e[en] = not e[en]
|
|
-- case "+" then
|
|
-- just remove the sign.
|
|
end switch
|
|
|
|
-- remove the sign:
|
|
e = remove(e, idx) -- ! Euphoria 3: e = e[1..st] & e[en..$]
|
|
len -= 1
|
|
end if
|
|
end if
|
|
end if
|
|
end if
|
|
|
|
idx += 1
|
|
end while
|
|
|
|
-- remove temporarily prepended operator:
|
|
return tail(e) -- ! Euphoria 3: return e[2..$].
|
|
end function
|
|
|
|
|
|
|
|
-- MAIN =====================================================================
|
|
|
|
-- PURPOSE: calculate a full expression and return the result.
|
|
-- DETAILS: 'e' sequence is returned by parse_expr().
|
|
-- RETURN: sequence; result atom, e.g. {-2.5}; or err_msg() string.
|
|
-- NOTE: expressions are evaluated from left-to-right, using Euphoria's
|
|
-- precedence order (see PRECEDENCE_LISTS constant).
|
|
-- EXAMPLE: s = calc_expr({1, "+", 2, "*", {FUNC_ID, 1}, "(", -11, ")"})
|
|
-- --> s is {23}
|
|
-- i.e. s is {1 + 2 * abs(-11)}
|
|
-- SEEALSO: parse_expr, math_eval, calc_op, calc_unary_op, calc_func
|
|
function calc_expr(sequence e)
|
|
integer st, en, idx
|
|
object obj
|
|
|
|
|
|
-- make sure expression enclosed with parenthesis to get final result;
|
|
e = {"("} & e & {")"}
|
|
|
|
|
|
-- calculate all expressions found within parenthesis (precedence-1):
|
|
while TRUE do
|
|
|
|
-- find the most inner ")":
|
|
en = find(")", e)
|
|
if en = 0 then
|
|
if find("(", e) then
|
|
return err_msg("Missing ')'")
|
|
else
|
|
exit
|
|
end if
|
|
end if
|
|
|
|
-- find the most inner "(":
|
|
st = 0
|
|
for i = en - 1 to 1 by -1 do
|
|
if equal(e[i], "(") then
|
|
st = i
|
|
exit
|
|
end if
|
|
end for
|
|
if st = 0 then
|
|
return err_msg("Missing '('")
|
|
end if
|
|
|
|
|
|
-- calculate chunks of the expression according to precedence order;
|
|
-- the idea is to shrink each chunk into atom, e.g. 5 * 10 --> 50:
|
|
for precedence = 4 to 7 do
|
|
idx = st + 1
|
|
|
|
while idx < en do
|
|
if atom(e[idx]) then -- atom can't be operator...
|
|
idx += 1
|
|
elsif find(e[idx], PRECEDENCE_LISTS[precedence]) then
|
|
obj = calc_op(e[idx - 1..idx + 1])
|
|
|
|
-- remove two atoms and operator; keep only new atom:
|
|
if atom(obj) then
|
|
-- ! Euphoria 3: e = e[1..idx - 2] & obj & e[idx + 2..$]
|
|
e = replace(e, obj, idx - 1, idx + 1)
|
|
en -= 2
|
|
else
|
|
return obj -- return err_msg()
|
|
end if
|
|
else
|
|
idx += 1
|
|
end if
|
|
end while
|
|
end for -- precedence
|
|
|
|
|
|
-- helper for debug:
|
|
if not (equal(e[st], "(") and equal(e[en], ")")) then
|
|
return err_msg(ER_BUG_IN_CODE, "calc_expr")
|
|
|
|
-- first: calculate a function using the result atom(s), or
|
|
-- calculate a function without arguments such as rnd():
|
|
elsif st > 1 and sequence(e[st - 1]) and e[st - 1][1] = FUNC_ID then
|
|
obj = calc_func(e[st - 1][2], e[st + 1..en - 1])
|
|
|
|
-- remove the function-id and (); keep only result atom:
|
|
if atom(obj) then
|
|
-- ! Euphoria 3: e = e[1..st - 2] & obj & e[en + 1..$]
|
|
e = replace(e, obj, st - 1, en)
|
|
else
|
|
return obj -- return err_msg()
|
|
end if
|
|
|
|
-- then: operator result must be a single atom:
|
|
elsif st + 2 = en and atom(e[st + 1]) then
|
|
-- remove the () and keep only the result atom:
|
|
-- ! Euphoria 3: e = e[1..st - 1] & e[st + 1] & e[en + 1..$]
|
|
e = replace(e, e[st + 1], st, en)
|
|
|
|
-- when only () remains:
|
|
elsif st + 1 = en then
|
|
return err_msg("Missing expression inside", "( )")
|
|
|
|
-- invalid syntax returned inside ( ), e.g. "1not-2" or "(1not-2)":
|
|
else
|
|
if st = 1 then -- the first '(' is prefixed to the expression.
|
|
return err_msg(ER_SYNTAX_ERROR & " expression")
|
|
else
|
|
return err_msg(ER_SYNTAX_ERROR, "( )")
|
|
end if
|
|
end if
|
|
|
|
|
|
-- finally, re-calc unary+/-/not for atoms, since the ( ) and
|
|
-- function name removed, i.e. for -(1 + 2) or -abs(4), etc:
|
|
e = calc_unary_op(e)
|
|
|
|
end while -- found expression within parenthesis
|
|
|
|
return e
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: parse string expression into a sequence of initial arguments.
|
|
-- DETAILS: expr, var_names, var_values are same as in function math_eval().
|
|
-- get_id = TRUE used by get_identifiers() to keep identifier names.
|
|
-- RETURN: sequence; sequence of arguments, or err_msg() string.
|
|
-- * if get_id = TRUE then arguments are only identifiers *names*.
|
|
-- EXAMPLE: s = parse_expr("( a + 5 * abs(c) )", {"a", "c"}, {11.2, -4})
|
|
-- --> s is {"(", 11.2, "+", 5, "*", {'@', 1}, "(", -4, ")", ")"}
|
|
-- SEEALSO: math_eval, get_identifiers, calc_unary_op, calc_expr,
|
|
-- string_to_number
|
|
function parse_expr(ascii_string expr, sequence var_names, sequence var_values,
|
|
boolean get_id = FALSE)
|
|
integer idx, chr, st, en, func_ix
|
|
sequence str, e = {}, id = {}
|
|
object get_value
|
|
atom num
|
|
|
|
|
|
-- NUL is end-of-string marker and placeholder for 'next-chr':
|
|
expr &= NUL
|
|
|
|
|
|
-- parse expression into sequence:
|
|
idx = 1
|
|
while TRUE do
|
|
|
|
chr = expr[idx]
|
|
|
|
-- first check for end-of-string marker:
|
|
if chr = NUL then
|
|
exit
|
|
|
|
-- ignore and skip blanks and control characters ('\t', etc):
|
|
elsif chr <= 32 or chr = 127 then
|
|
idx += 1
|
|
|
|
-- append parenthesis, comma, and symbolic operators:
|
|
elsif find(chr, "(),*/+-=<>!") then
|
|
if expr[idx + 1] = '=' and find(chr, "<>!") then
|
|
-- append ambiguous operators "<=", ">=", "!=":
|
|
e = append(e, chr & '=')
|
|
idx += 2
|
|
else
|
|
e = append(e, {chr})
|
|
idx += 1
|
|
end if
|
|
|
|
-- get and append number's value, e.g. 10.5 (# for hexadecimal):
|
|
elsif (chr >= '0' and chr <= '9') or chr = '.' or chr = '#' then
|
|
st = idx
|
|
en = idx
|
|
idx += 1
|
|
while TRUE do
|
|
chr = expr[idx]
|
|
if (chr >= '0' and chr <= '9') or
|
|
(chr >= 'a' and chr <= 'z') or
|
|
(chr >= 'A' and chr <= 'Z') or chr = '.' or chr = '_' or
|
|
((chr = '-' or chr = '+') and find(expr[idx - 1], "eE"))
|
|
then
|
|
en = idx
|
|
idx += 1
|
|
else
|
|
get_value = string_to_number(expr[st..en])
|
|
if atom(get_value) then
|
|
e = append(e, get_value)
|
|
exit
|
|
else
|
|
return err_msg("Invalid number", expr[st..en], st)
|
|
end if
|
|
end if
|
|
end while
|
|
|
|
-- get and append variable's/constant's value, operator's string,
|
|
-- and function's index:
|
|
elsif (chr >= 'a' and chr <= 'z') or
|
|
(chr >= 'A' and chr <= 'Z') or chr = '_'
|
|
then
|
|
st = idx
|
|
en = idx
|
|
idx += 1
|
|
while TRUE do
|
|
chr = expr[idx]
|
|
if (chr >= 'a' and chr <= 'z') or
|
|
(chr >= 'A' and chr <= 'Z') or
|
|
(chr >= '0' and chr <= '9') or chr = '_'
|
|
then
|
|
en = idx
|
|
idx += 1
|
|
else
|
|
str = expr[st..en]
|
|
|
|
-- get_identifiers() keeps identifier name (not value):
|
|
if get_id then
|
|
id = append(id, str)
|
|
|
|
-- append the value of variable:
|
|
elsif find(str, var_names) then
|
|
num = var_values[find(str, var_names)]
|
|
e = append(e, num)
|
|
|
|
-- append the value of constant:
|
|
elsif find(str, CONST_LIST) then
|
|
num = CONST_LIST_VALUES[find(str, CONST_LIST)]
|
|
e = append(e, num)
|
|
|
|
-- append operator string ("and", "or", "xor", "not"):
|
|
elsif find(str, OP_LIST) then
|
|
e = append(e, str)
|
|
|
|
-- append the index of function, e.g. {'@', 15}:
|
|
else
|
|
func_ix = find(str, FUNC_LIST)
|
|
if func_ix then
|
|
e = append(e, {FUNC_ID, func_ix})
|
|
else
|
|
return err_msg("Invalid identifier", str, st)
|
|
end if
|
|
end if
|
|
|
|
exit
|
|
end if
|
|
end while
|
|
|
|
-- unsupported character found, such as '&':
|
|
else
|
|
return err_msg("Invalid character", {chr}, idx)
|
|
end if
|
|
|
|
end while -- idx
|
|
|
|
|
|
-- get_identifiers() needs only the identifiers names:
|
|
if get_id then
|
|
return id
|
|
|
|
-- math_eval() needs the parsed expression:
|
|
else
|
|
-- do the initial unary+/-/not calculation for atoms, e.g. {"-", 15};
|
|
-- note that -(1 + 2) or -abs(4), etc, is handled in calc_expr():
|
|
e = calc_unary_op(e)
|
|
|
|
return e
|
|
end if
|
|
end function
|
|
|
|
|
|
|
|
-- PUBLIC FUNCTIONS =========================================================
|
|
|
|
-- PURPOSE: evaluate a math expression. see "math_eval.doc" user manual.
|
|
-- DETAILS: expr is any Euphoria math expression string - see example below.
|
|
-- use_deg is FALSE=Radians/TRUE=Degrees for trigonometry functions.
|
|
-- var is optional variables sequences {{...}, {...}}:
|
|
-- var[1] is var_names, it's the variable names;
|
|
-- var[2] is var_values, it's paralleled to var_names, as it is the
|
|
-- variable's value, which is an atom or a string-expression
|
|
-- representing an atom, e.g.:
|
|
-- var = { {"R", "Si" , "tol", "fact"} --> [1] var_names
|
|
-- {6 , "-(abs(-1))", 12.5 , ".3"} } --> [2] var_values
|
|
-- INPUT: error_occurred
|
|
-- OUTPUT: use_degrees
|
|
-- RETURN: object; atom as result, or err_msg() string.
|
|
-- EXAMPLE: x = math_eval("( a + 5 * abs(c) )", 0, {{"a", "c"}, {11.2, -4}})
|
|
-- --> x is 31.2
|
|
-- i.e. x is (11.2 + 5 * abs(-4))
|
|
-- SEEALSO: get_identifiers, parse_expr, calc_expr, err_msg, set_local_variables
|
|
public function math_eval(ascii_string expr, boolean use_deg = FALSE,
|
|
paralleled_seq var = {{}, {}})
|
|
object e
|
|
seq_of_strings var_names = var[1]
|
|
seq_of_values var_values = var[2]
|
|
|
|
set_local_variables(ON_ENTER)
|
|
|
|
|
|
-- convert strings in 'var_values' to atoms, e.g. "-1.5*2" --> -3:
|
|
for i = 1 to length(var_names) do
|
|
var_names[i] = trim(var_names[i]) -- ( trim() works per *string* ).
|
|
|
|
if find(var_names[i], FUNC_LIST & CONST_LIST & OP_LIST) then
|
|
e = err_msg("Illegal variable name", var_names[i])
|
|
exit
|
|
|
|
elsif sequence(var_values[i]) then
|
|
-- (this supports also previous variables in a string-expression):
|
|
e = math_eval(var_values[i], use_deg, {
|
|
head(var_names, i - 1), -- ! Euphoria 3: var_names[1..i - 1],
|
|
head(var_values, i - 1) -- ! Euphoria 3: var_values[1..i - 1]
|
|
})
|
|
|
|
if atom(e) then
|
|
var_values[i] = e
|
|
else -- e = err_msg(...):
|
|
-- ! Euphoria 3: tail(e) is e[2..$]
|
|
e = ERR_MSG_HEAD & "'" & var_names[i] & "': " & tail(e)
|
|
exit
|
|
end if
|
|
end if
|
|
end for
|
|
|
|
|
|
if error_occurred then
|
|
-- e = err_msg(...)
|
|
|
|
-- parse 'expr' to its initial arguments:
|
|
else
|
|
e = parse_expr(expr, var_names, var_values)
|
|
|
|
if error_occurred then
|
|
-- e = err_msg(...)
|
|
elsif length(e) = 0 then
|
|
e = err_msg("Empty expression")
|
|
|
|
-- calculate the expressions:
|
|
else
|
|
use_degrees = use_deg -- output to local variable
|
|
e = calc_expr(e)
|
|
|
|
if error_occurred then
|
|
-- e = err_msg(...)
|
|
elsif length(e) = 1 then
|
|
e = e[1] -- the final result must be a single atom
|
|
else
|
|
e = err_msg(ER_BUG_IN_CODE, "math_eval") -- helper for debug
|
|
end if
|
|
end if
|
|
end if
|
|
|
|
|
|
-- return atom as result; or error message string:
|
|
set_local_variables(ON_EXIT)
|
|
return e
|
|
end function
|
|
|
|
|
|
|
|
-- PURPOSE: extract alphanumeric identifiers from a string expression.
|
|
-- - it can be useful for getting variable names, which is the
|
|
-- default behavior, and prompt the user for their values.
|
|
-- DETAILS: expr is same as in function math_eval().
|
|
-- list is the identifier-list to return, or all of them.
|
|
-- INPUT: error_occurred
|
|
-- RETURN: sequence; A sequence of length two consisting of:
|
|
-- * an integer, the return status. This is any of:
|
|
-- GET_ID_SUCCESS -- expression was read successfully
|
|
-- GET_ID_FAIL -- expression is not syntactically correct
|
|
--
|
|
-- * - if GET_ID_SUCCESS: a sequence which contains 4 sub-sequences
|
|
-- for each type of identifier; or a sequence which contains
|
|
-- only one type of identifier. see public indexes below.
|
|
-- - if GET_ID_FAIL: err_msg() string.
|
|
-- EXAMPLE: s = get_identifiers("( PI / not a + 5 * abs(c) )", GET_ID_ALL)
|
|
-- --> s is {GET_ID_SUCCESS, {{"not"}, {"abs"}, {"PI"}, {"a", "c"}}}
|
|
-- SEEALSO: math_eval, parse_expr, set_local_variables
|
|
public constant
|
|
-- return status integers:
|
|
GET_ID_SUCCESS = 0, -- expression was read successfully
|
|
GET_ID_FAIL = 1 -- expression is not syntactically correct
|
|
public constant
|
|
-- return sequence indexes (list) - from 1 to 4:
|
|
GET_ID_ALL = 0,
|
|
GET_ID_OPERATORS = 1,
|
|
GET_ID_FUNCTIONS = 2,
|
|
GET_ID_CONSTANTS = 3,
|
|
GET_ID_VARIABLES = 4
|
|
public function get_identifiers(ascii_string expr, integer list = GET_ID_VARIABLES)
|
|
sequence e, id = repeat({}, 4)
|
|
object str
|
|
integer idx
|
|
|
|
set_local_variables(ON_ENTER)
|
|
|
|
|
|
-- parse expression and keep the identifier names (not values):
|
|
e = parse_expr(expr, {}, {}, TRUE)
|
|
|
|
|
|
-- id is err_msg():
|
|
if error_occurred then
|
|
id = {GET_ID_FAIL, e}
|
|
|
|
-- id is identifiers list(s):
|
|
else
|
|
e = remove_dups_inplace(e) -- remove duplicate identifiers
|
|
|
|
-- store identifier names in 'id':
|
|
for i = 1 to length(e) do
|
|
str = e[i]
|
|
|
|
if find(str, OP_LIST) then -- operator name, e.g. "or"
|
|
idx = GET_ID_OPERATORS
|
|
elsif find(str, FUNC_LIST) then -- function name, e.g. "abs"
|
|
idx = GET_ID_FUNCTIONS
|
|
elsif find(str, CONST_LIST) then -- constant name, e.g. "PI"
|
|
idx = GET_ID_CONSTANTS
|
|
else -- variable name, e.g. "xyz"
|
|
idx = GET_ID_VARIABLES
|
|
end if
|
|
|
|
id[idx] = append(id[idx], str) -- append identifier to list
|
|
end for
|
|
|
|
-- id is all lists, or just a single list:
|
|
if list != GET_ID_ALL then
|
|
id = id[list]
|
|
end if
|
|
|
|
id = {GET_ID_SUCCESS, id}
|
|
end if
|
|
|
|
|
|
-- return success or fail sequence:
|
|
set_local_variables(ON_EXIT)
|
|
return id
|
|
end function
|
|
|
|
|
|
|
|
-- End of file.
|