ANEW --OPG-- \ Wil Baden 2000-05-15
\ *******************************************************************
\ * OPG Formula Translation *
\ *******************************************************************
\ If there are redefinitions of things
\ you already have, ignore them for now (it won't hurt) and
\ later comment them out.
\ If there are redefinitions you can't tolerate, fix them
\ and let me know.
\ No logic changes since 1997, except:
\ 2000-05-15 Use `|...|` for normal Forth. (Changed
\ from `{...}` because in the FSL, `{` is used for arrays,
\ in SwiftForth, `{...}` is used for commentary, and in MPE,
\ `{...}` is used for local variables.)
\ This is a implementation of Formula Translation. It will
\ translate Fortran-style assignments `varname=expr` and
\ expressions `expr` to Forth.
\ GLOSSARY
\ ?? Accept-Char-for-Formula Apply-Operators
\ Callable Code-Operation FAILURE Get-Formula
\ Is+or- Is-D-or-E Is-a-Number Is-an-Identifier
\ LET Memorable NEXT-CHAR Op-Code
\ Op-Fetch Op-Literal Op-Pop Op-Push Op-Stack
\ Op-Stack-Size Op-Store Op-Top Operator-Precedence
\ Parenthesis-Count Replace-Last-Char SUCCESS
\ Translate-Expression Translate-Formula
\ Translate-Operand-Operator Translate-Operation
\ Word-Holder
\ /GLOSSARY
\ There is just one end-user word `LET`. The formula is
\ terminated by `:`. (`LET` and `:` have been adopted from
\ Basic.)
\ It can be used compiling or interpreting. It is not
\ state-smart.
\ An segment between |bars| will be treated as normal Forth.
\ The resulting translations are the natural expansions.
\ LET a-b-c-d:
\ a F@ b F@ F- c F@ F- d F@ F-
\ LET a*b-c*d:
\ a F@ b F@ F* c F@ d F@ F* F-
\ LET (a-b)*(c-d):
\ a F@ b F@ F- c F@ d F@ F- F*
\ LET x = -1:
\ -1.E x F!
\ LET x = (-b - SQRT (b * |FDUP| - 4*a*c)) / (2*a):
\ b F@ FNEGATE b F@ FDUP F*
\ 4.E a F@ F* c F@ F* F- FSQRT F-
\ 2.E a F@ F* F/ x F!
\ If a function doesn't begin with `F` it will first look for
\ it with `F` prefixed.
\ All numbers are floating point. Variables begin with a
\ letter, continue with letters and digits, and are not
\ followed by a left parenthesis mark. Function-calls have the
\ same form but are followed by a left parenthesis mark.
\ The operators are:
\ + - * / ** or ^
\ Assignments are made with `=`. Multiple arguments of a
\ function are separated by commas.
\ Spaces are deleted before translation, except between `|`
\ and `|`.
\ Variable `DEBUG` on will show code being translated.
\ This program uses Julian V. Noble's concept but not his
\ implementation.
\ Thanks to Marcel Hendrix for his ideas for extending the system.
\ Examples of Use
\ Operator Precedence goes through the expression putting out
\ operands as it reaches them and saving operators. Operators
\ are put out when an operator of less or equal precedence is
\ reached. Thus higher precedence is performed before lower
\ precedence.
\ FVARIABLE a FVARIABLE b FVARIABLE c
\ FVARIABLE x FVARIABLE w
\ : TEST0 CR LET b+c: FE.
\ CR LET b-c: FE.
\ CR LET 10000000*(b-c)/(b+c): FE.
\ ;
\ LET b = 3:
\ LET c = 4:
\ TEST0
\ : TEST1 LET a = b*c-3.17e-5/TANH(w)+ABS(x): CR LET a: F. ;
\ LET w = 1.e-3: LET x = -2.5: CR CR test1
\ FVARIABLE HALFPI
\ LET HALF PI = 2*ATAN(1):
\ LET HALF PI + |FDUP|: F.
\ FVARIABLE disc ( Used for discriminant )
\ : QUADRATICROOT ( F: a b c -- r1 r2 )
\ c F! b F! a F! \ Pickup coefficients.
\ LET disc = SQRT(b*b-4*a*c): \ Set discriminant.
\ LET (-b+disc)/(2*a), (-b-disc)/(2*a):
\ \ Put values on f-stack.
\ ;
\ ( Solve x*x-3*x+2 ) LET QUADRATIC ROOT (1,-3, 2) : F. F.
\ ( Find goldenratio ) LET MAX(QUADRA TICROOT (1,-1,-1)) : F.
\ ( You can also write ) 1.E -1.E -1.E QUADRATICROOT FMAX F.
\ : FACTORIAL ( n -- )( F: -- r )
\ LET w = 1: LET x = 1:
\ 0 ?DO LET w = w * x: LET x = x + 1: LOOP
\ LET w: ;
\ ( Another way )
\ : FACTORIAL ( n -- )( F: -- r )
\ LET w = 1: 0 ?DO LET w = w * | I 1+ S>D D>F |: LOOP
\ LET w: ;
\ 6 FACTORIAL F. ( or ) LET FACTORIAL(|6|): F.
\ Formula Translation using Operator Precedence Grammar
VARIABLE BUG 0 BUG ! ( This is a common name. )
( `BUG` occurs in one place below. Change it here and there. )
\ Common usage, especially with me. Comment out what you already have.
: /SPLIT ( a m b n -- b n a m-n ) DUP >R 2SWAP R> - ;
: ANDIF S" DUP IF DROP " EVALUATE ; IMMEDIATE
: BOUNDS ( str len -- str+len str ) over + SWAP ;
: NOT ( x -- flag ) S" 0= " EVALUATE ; IMMEDIATE
: OFF 0 SWAP ! ;
: ON TRUE SWAP ! ;
: ORIF S" DUP 0= IF DROP " EVALUATE ; IMMEDIATE
: PLACE ( str len addr -- )
2dup 2>R CHAR+ SWAP chars MOVE 2R> C! ;
: IS-ALPHA ( char -- flag ) 32 OR [char] a - 26 U< ;
: IS-DIGIT ( char -- flag ) [char] 0 - 10 U< ;
: IS-ALNUM ( char -- flag )
dup IS-ALPHA ORIF dup IS-DIGIT THEN NIP ;
\ Elementary Tools
\ FAILURE ( -- )
\ False exit.
\ SUCCESS ( -- )
\ True exit.
\ ?? ( x "aword" -- )
\ _x_ `IF` _aword_ `THEN`
: FAILURE S" FALSE EXIT " EVALUATE ; IMMEDIATE
: SUCCESS S" TRUE EXIT " EVALUATE ; IMMEDIATE
: ?? ( x "word" -- )
POSTPONE IF
BL WORD COUNT EVALUATE \ Or: PARSE-WORD EVALUATE
POSTPONE THEN
; IMMEDIATE
\ *******************************************************************
\ * Character Handling *
\ *******************************************************************
\ NEXT-CHAR ( -- char or 0 for EOL or negative for EOF )
\ Get character from input stream. Used in `Get-Formula`.
\ Replace-Last-Char ( str len char -- str len )
\ Replace last character in a string. Used in `Op-Literal`
\ and `Accept-Char-for-Formula`.
\ Is+or- ( char -- flag )
\ Test _char_ for `+` or `-`. Used in `Is-a-Number`. `[+-]`
\ Is-D-or-E ( char -- flag )
\ Test _char_ for `D`, `E`, `d`, or `e`. Used in
\ `Is-a-Number` and `Op-Literal`. `[DEde]`
: NEXT-CHAR ( -- char or 0 for EOL or negative for EOF )
SOURCE >IN @ > ( addr flag)
IF >IN @ CHARS + C@ 1 >IN +!
ELSE DROP REFILL 0= ( )
THEN ;
: Replace-Last-Char ( str len char -- str len )
>R 2DUP CHARS + R> SWAP C! ;
: Is+or- ( char -- flag ) DUP [CHAR] + = SWAP [CHAR] - = OR ;
: Is-D-or-E ( char -- flag ) 32 OR [CHAR] d - 2 U< ;
\ *******************************************************************
\ * Is-a-Number *
\ *******************************************************************
\ Is-a-Number ( str len -- str' len' flag )
\ This awful-looking code walks through syntax for a number.
\ Used in `Translate-Operand-Operator`.
\ Regular Expression
\ [+-]?[0-9]*([.][0-9]*)?([DEde](([-+][0-9])?[0-9]*)?
: Is-a-Number ( str len -- str' len' flag )
DUP 0= ?? FAILURE
\ [-+] Any sign.
OVER C@ Is+or- IF
1 /STRING
DUP 0= ?? FAILURE
THEN
\ [.]?[0-9] Begins with digit or decimal point and digit.
OVER C@ IS-DIGIT ORIF OVER C@ [CHAR] . = THEN 0= ?? FAILURE
OVER C@ [CHAR] . = IF
DUP 1 = ?? FAILURE
OVER CHAR+ C@ IS-DIGIT NOT ?? FAILURE
THEN
\ [0-9]* Any digits.
BEGIN OVER C@ IS-DIGIT
WHILE 1 /STRING DUP 0= ?? SUCCESS
REPEAT
\ [.][0-9]* Decimal point and any digits
OVER C@ [CHAR] . = IF
BEGIN
1 /STRING DUP 0= ?? SUCCESS
OVER C@ IS-DIGIT NOT
UNTIL
THEN
\ [DEde](([-+][0-9])?[0-9]*)? Exponent, sign and digits.
OVER C@ Is-D-or-E IF
1 /STRING DUP 0= ?? SUCCESS
OVER C@ Is+or- IF
1 /STRING
DUP 0= ?? FAILURE
OVER C@ IS-DIGIT NOT ?? FAILURE
THEN
\ [0-9]*
BEGIN DUP 0= ?? SUCCESS
OVER C@ IS-DIGIT
WHILE 1 /STRING REPEAT
THEN
SUCCESS ;
\ *******************************************************************
\ * Is-an-Identifier *
\ *******************************************************************
\ Is-an-Identifier ( str len -- str' len' flag )
\ An identifier is a letter followed by letters and digits.
\ Used in `Translate-Operand-Operator` and
\ `Translate-Formula`.
: Is-an-Identifier ( str len -- str' len' flag )
DUP 0= ?? FAILURE
OVER C@ IS-ALPHA NOT ?? FAILURE
BEGIN 1 /STRING
DUP 0= ?? SUCCESS
OVER C@ IS-ALNUM NOT
UNTIL
SUCCESS ;
\ *******************************************************************
\ * Op-Stack Operations *
\ *******************************************************************
\ Op-Stack-Size ( -- n )
\ Maximum size of `Op-Stack`. Used in `Op-Push`.
\ Op-Stack ( -- addr)
\ Stack to hold operators.
\ Op-Push ( op -- )
\ Push _op_ on top of `Op-Stack`.
\ Op-Top ( -- op )
\ The operator on top of `Op-Stack`.
\ Op-Pop ( -- )
\ Remove top of `Op-Stack`.
30 CONSTANT Op-Stack-Size
CREATE Op-Stack Op-Stack-Size 1+ CELLS ALLOT
: Op-Push ( op -- )
Op-Stack @ Op-Stack-Size CELLS < NOT
ABORT" Too Many Elements -- Increase Op-Stack-Size "
1 CELLS Op-Stack +! Op-Stack DUP @ + !
;
: Op-Top ( -- op ) Op-Stack DUP @ + @ ;
: Op-Pop ( -- ) -1 CELLS Op-Stack +! ;
\ *******************************************************************
\ * Application Tools *
\ *******************************************************************
\ Parenthesis-Count ( -- addr )
\ Tally for parentheses.
\ Word-Holder ( -- addr )
\ Buffer for name when modifying it.
\ Memorable ( str len -- )
\ Look up variable. Used in `Op-Store` and `Op-Fetch`.
\ Callable ( str len -- str' len' )
\ Look up function. Used in `Code-Operation`.
\ Translate-Operation ( addr len -- )
\ Translate operation. [Can't think of better explanation.]
\ Op-Store ( str len -- )( F: r -- )
\ Make assignment. Used in `Translate-Formula`.
\ Op-Fetch ( str len -- )( F: -- r )
\ Pick up variable. Used in `Translate-Operand-Operator`.
\ Op-Literal ( str len -- )( F: -- r )
\ Take care of literal. Used in `Translate-Operand-Operator`.
VARIABLE Parenthesis-Count
1 CONSTANT Left-Paren
2 CONSTANT Right-Paren
8 CONSTANT Negation
9 CONSTANT Function-Call
10 CONSTANT Op-Dummy
CREATE Word-Holder 32 CHARS ALLOT
: Memorable ( str len -- )
31 MIN Word-Holder PLACE ( )
Word-Holder FIND 0= IF
COUNT TYPE SPACE TRUE ABORT" Not Found "
THEN
DROP ;
: Callable ( str len -- str' len' )
OVER C@ [CHAR] F = NOT IF
2DUP 30 MIN DUP 1+ Word-Holder C!
Word-Holder CHAR+ PLACE ( . .)
[CHAR] F Word-Holder CHAR+ C!
Word-Holder FIND NIP IF
2DROP Word-Holder COUNT
THEN
THEN ;
: Translate-Operation ( addr len -- )
BUG @ IF 2DUP TYPE SPACE THEN
EVALUATE ;
: Op-Store ( str len -- )( F: r -- )
2DUP Memorable Translate-Operation
S" F! " Translate-Operation ;
: Op-Fetch ( str len -- )( F: -- r )
2DUP Memorable Translate-Operation
S" F@ " Translate-Operation ;
VARIABLE Literal-State
: Op-Literal ( str len -- )( F: -- r )
Literal-State OFF
Word-Holder 0 2SWAP CHARS BOUNDS ?DO
I C@ Is-D-or-E IF Literal-State ON THEN
I C@ Replace-Last-Char 1+
1 CHARS +LOOP
Literal-State @ 0= IF
[CHAR] E Replace-Last-Char 1+
THEN
Translate-Operation ;
\ *******************************************************************
\ * Operators *
\ *******************************************************************
\ Op-Code ( str len -- str len code )
\ Pick up code for operator. Used in `Apply-Operators`.
\ Operator-Precedence ( code -- precedence )
\ Get the precedence of an operator. Used in
\ `Apply-Operators`.
\ Code-Operation ( code -- )
\ Determine what to do with the operator. Used in
\ `Apply-Operators`.
\ Apply-Operators ( str len -- str' len' )
\ Use operator precedence to select operators.
\ Used in `Translate-Operand-Operator`.
: Op-Code ( str len -- str len code )
DUP 0= IF 0
ELSE CASE OVER C@
[CHAR] ) OF 2 ENDOF
[CHAR] + OF 3 ENDOF
[CHAR] - OF 4 ENDOF
[CHAR] * OF 5 ENDOF
[CHAR] / OF 6 ENDOF
[CHAR] ^ OF 7 ENDOF
[CHAR] , OF 0 ENDOF
DUP . EMIT
TRUE ABORT" Illegal Operator "
0 ENDCASE
THEN ;
: Operator-Precedence ( code -- precedence )
CASE -1 OF -1 ENDOF \ Bottom Mark
0 OF 2 ENDOF \ Termination or Comma
1 OF 1 ENDOF \ Left Paren
2 OF 1 ENDOF \ Right Paren
3 OF 3 ENDOF \ Plus
4 OF 3 ENDOF \ Minus
5 OF 4 ENDOF \ Times
6 OF 4 ENDOF \ Divide
7 OF 5 ENDOF \ Power
8 OF 3 ENDOF \ Negation
9 OF 1 ENDOF \ Function-Call
10 OF 0 ENDOF \ Dummy
DROP TRUE ABORT" Invalid Operation "
0 ENDCASE ;
: Code-Operation ( code -- )
CASE 1 OF 0 -1 Parenthesis-Count +! ENDOF
2 OF 0 ENDOF
3 OF S" F+ " ENDOF
4 OF S" F- " ENDOF
5 OF S" F* " ENDOF
6 OF S" F/ " ENDOF
7 OF S" F** " ENDOF
8 OF S" FNEGATE " ENDOF
9 OF Op-Pop Op-Top Op-Pop Op-Top
-1 Parenthesis-Count +!
Callable
ENDOF
DROP TRUE ABORT" Invalid Operator "
0 ENDCASE ( addr k)
?DUP ?? Translate-Operation ;
: Apply-Operators ( str len -- str' len' )
BEGIN Op-Code ( str len code)
DUP 2SWAP 2>R ( code code)( R: str len)
>R Operator-Precedence >R ( )( R: . . . precedence)
BEGIN Op-Top Operator-Precedence R@ < NOT
WHILE Op-Top Code-Operation Op-Pop
REPEAT
R> DROP R> 2R> ( code str len)( R: )
DUP IF 1 /STRING THEN
ROT ( str len code)
DUP Right-Paren =
WHILE DROP Op-Pop REPEAT
?DUP ?? Op-Push ;
\ *******************************************************************
\ * Translate Operand and Operator *
\ *******************************************************************
\ Translate-Operand-Operator ( str len -- str' len' )
\ Pick up an operand and an operator. Used in
\ `Translate-Expression`.
: Translate-Operand-Operator ( str len -- str' len' )
\ Is it a variable or function-call?
2DUP Is-an-Identifier IF ( a n a+k n-k)
DUP ANDIF OVER C@ [CHAR] ( = THEN IF
\ It's a function-call.
Op-Dummy Op-Push
/SPLIT ( a+k n-k a k)
Op-Push Op-Push Function-Call Op-Push ( a+k n-k)
1 Parenthesis-Count +!
1 /STRING
ELSE
\ It's a variable.
2>R R@ - Op-Fetch 2R>
Apply-Operators
THEN
EXIT
THEN 2DROP ( str len)
\ Is it a number?
2DUP Is-a-Number IF ( a n a+k n-k)
2>R R@ - Op-Literal 2R>
Apply-Operators
EXIT
THEN 2DROP ( str len)
\ Is it a left paren?
OVER C@ [CHAR] ( = IF
Op-Dummy Op-Push Left-Paren Op-Push
1 Parenthesis-Count +!
1 /STRING
EXIT
THEN
\ Is it a lonely minus sign?
OVER C@ [CHAR] - = IF
Negation Op-Push
1 /STRING
EXIT
THEN
\ Is it a lonely plus sign?
OVER C@ [CHAR] + = ANDIF DUP 1 > THEN IF
1 /STRING
EXIT
THEN
\ Is it normal Forth?
OVER C@ [CHAR] | = IF
1 /STRING
2DUP [CHAR] | SCAN /SPLIT
2SWAP 2>R Translate-Operation 2R>
DUP IF 1 /STRING THEN
Apply-Operators
EXIT
THEN
\ Oops.
CR TYPE CR
TRUE ABORT" Illegal Operand " ;
\ *******************************************************************
\ * Translate Formula *
\ *******************************************************************
\ Translate-Formula ( str len -- )
\ Translate the formula. Used in `LET`.
\ Translate-Expression ( str len -- )
\ Translate the expression. Used in `Translate-Formula`.
: Translate-Expression ( str len -- )
BEGIN DUP WHILE
Translate-Operand-Operator
REPEAT 2DROP
Parenthesis-Count @ ABORT" Unmatched Parens " ;
: Translate-Formula ( str len -- )
0 Op-Stack ! 0 Parenthesis-Count !
2DUP Is-an-Identifier ( str len str' len' flag)
ANDIF DUP
ANDIF OVER C@ [CHAR] = =
THEN THEN IF ( str len str' len')
/SPLIT Op-Push Op-Push -1 Op-Push ( str' len')
1 /STRING
Translate-Expression ( )
Op-Top -1 = NOT ABORT" Invalid Expression "
Op-Pop Op-Top Op-Pop Op-Top Op-Store
ELSE 2DROP ( str len)
-1 Op-Push
Translate-Expression ( )
THEN
Op-Stack @ 1 CELLS = NOT ABORT" Invalid Formula " ;
\ *******************************************************************
\ * Get Formula *
\ *******************************************************************
\ Get-Formula ( "multi-lines" -- addr len )
\ Get formula from the input stream. Used in `LET`.
\ Accept-Char-for-Formula ( str length char -- str length' )
\ Accept _char_ for the formula. Used in `Get-Formula`.
255 CONSTANT Formula-Length
CREATE Formula Formula-Length 1+ CHARS ALLOT
VARIABLE Keep-Spaces
: Accept-Char-for-Formula ( str length char -- str length' )
OVER Formula-Length > ABORT" Formula Length Overflow "
CASE
[CHAR] | OF [CHAR] | Replace-Last-Char 1+
Keep-Spaces DUP @ NOT SWAP !
ENDOF
[CHAR] * OF DUP
ANDIF 2DUP 1- CHARS + C@ [CHAR] * = THEN
IF 1- [CHAR] ^ ELSE [CHAR] * THEN
Replace-Last-Char 1+
ENDOF
Replace-Last-Char 1+
0 ENDCASE ;
: Get-Formula ( "multi-lines" -- addr len )
Keep-Spaces OFF
Formula 0 ( addr len)
BEGIN NEXT-CHAR ( addr len char)
DUP 0< ABORT" End of File "
DUP [CHAR] : = NOT
WHILE DUP BL >
ORIF DUP BL = Keep-Spaces @ AND THEN
IF Accept-Char-for-Formula
ELSE DROP
THEN ( addr len)
REPEAT DROP ;
\ *******************************************************************
\ * LET *
\ *******************************************************************
\ LET ( "formula:" -- )( F: -- | values )
\ Translate `varname=expr` or `expr`.
: LET ( "formula:" -- )( F: -- | values )
Get-Formula Translate-Formula ; IMMEDIATE
\\ End of Operator Precedence Grammar