Usegrammer, is the combination of "user" and "programmer". The "superuser" is implied as all Linux users are "superuser" of their own system, and it conveys the special status as in the Tron universe.
It is made possible by Phoscript, a lightweight Forth derivative that can be implemented as a shell script within any known programming language, thus making it easier for users to make modifications to an existing program, to enhance its functionalities, as well as help project teams solve one of its most pressing problems, both in the free software and corporate world — finding manpower to sustain or expand development activities.
Underlying this is the "programming language fragmentation" problem — the emergence of mutually incompatible programming languages over the past few decades results in the fragmentation of programmer communities, reducing the number of programmers able to work on some older projects, written in older programming languages.
This seemingly intractable problem actually has a very simple solution — so simple that it usually causes disbelief in less experienced programmers, as well as dismay amongst more experienced programmers, who for better or worse, naturally invoke some kind of defense mechanisms such as, "this is new to me, so I had better attack it so as not to embarrass myself when questioned about it."
We illustrate the significance of "Usegrammers" with 2 relatively little known C++ projects:
- Open Dynamics Engine (ODE)
- Bullet Open Scene Graph (BTOSG) https://github.com/miguelleitao/btosg
ODE and BTOSG are low level physics simulation libraries that can be used in games and simulation applications.
Phoscript Tutorials gives a list of tutorials with sample code of Phoscript implemented on various platforms and programming languages.
SymForth is SymEngine with Forth (Phoscript) wrapper, &mdahs; a fast symbolic manipulation library, written in C++, now with Reverse Polish Notation support. It consists of samples of fundamental data structure and execution framework that will be deployed in the current project.
Readers are encouraged to refer to the articles mentioned above as fundamental principles of Phoscript code in this project.
In the original car.cpp
in miguelleitao/btosg
, lines 197 to 203 create a 4 wheeled car, as shown in figure 1:
We have duplicated lines 197 to 203 with modifications (lines 221 to 250) , in order to create 3 additional cars as shown in figure 2:
Our primary goal is to create a text input window in the application, where user (programmer) (hence "Usegrammer) can enter commands in Phoscript (Forth like Reverse Polish Notation), to FULLY CONTROL and MANIPULATE all objects and elements of the application (program), much like how a Unix shell is able to control and manipulate all elements in the operating system.
The modifications to car.cpp
so far aim to verify if BTOSG code structure is flexible and robust enough to create a new object easily. Subsequemtly we will introduce Phoscript constructs like those available in ODE example and other articles in Phoscript Tutorials, to achieve a fully functional programmable shell within the program itself.
A Forth like Reverse Polish Notation expression (RPNX) is typically a space delimited list of words, e.g.
... a b c d ...
Compared to a LISP statement which includes an opening and closing bracket, RPNX is slightly more economical, but we shall explore the more fundamental significance of their differences later.
As such, "simplifying C++ code using RPNX" may convey two categories of features, according to the levels of expertise of the user or programmer.
For novice programmers, simplifying one C++ statement or a block of C++ statements to an RPNX word (a special term referring to a token in Forth terminology) or a list of RPNX words, is the most obvious, trivial and yet practical benefit.
For expert programmers, representing C++ statements using RPNX has many metaprogramming benefits, which are of great significance.
Let us illustrate this with dsSetViewpoint (xyz,hpr)
in line 222 of demo_buggy.cpp
from Open Dynamics Engine (0.16):
Line 222 has been moved to line 378 in our version of demo_buggy.cpp
as we have inserted quite a number of lines to implement Phoscript.
The Phoscript equivalent of line 378 is sm_proc( hpr, xyz, "svp:" )
in line 374, as shown in figure 3.
sm_proc()
is implemented as a recursive variadic function, so as to take a variable number of parameters as shown in figures 4 and 5. The prefix sm_
stands for "stack machine".
Firstly, the order of parameters in dsSetViewpoint(xyz,hpr)
has been reversed:
sm_proc( hpr, xyz, "svp:" );
This is in accordance to the convention of stack machine, i.e. "last in first out". Later we will demonstrate that the LIFO order can be changed to FIFO using some metaprogramming tricks, i.e. We wish to rewrite the parameters of sm_proc()
to follow C++ conventions, for reasons also to be disclosed later:
sm_proc( "dsSetViewpoint(", xyz, hpr, ")" );
// 2020-08-08 Phos code
sm_proc( hpr, xyz, "svp:" );
// 2020-08-08 original ODE code
// dsSetViewpoint (xyz,hpr);
// sm_proc recursive variadic
template <typename T>
void sm_proc(T t)
{
// std::cout << t << std::endl ;
std::cout << t << ' ' ;
std::cout << typeid(t).name() << ' ' ;
std::cout << std::is_function<decltype(t)>::value << ' ';
std::cout << " is_string " << is_string<decltype(t)>::value << ' ';
std::cout << sm_typeof(t) <<std::endl ;
if ( (is_string<decltype(t)>::value)==1 ) {
// std::cout << " last char " << t.back() <<std::endl ;
// std::cout << " last char " <<std::endl ;
std::string s_t(t);
std::cout << " s_t " << s_t << " last char " << sm_lastchar(s_t) <<std::endl ;
}
else sm_lastchar(t); // sm_S.push(t);
}
template<typename T, typename... Args>
void sm_proc(T t, Args... args) // recursive variadic function
{
std::cout << t << ' ' ;
std::cout << typeid(t).name() << ' ' ;
std::cout << std::is_function<decltype(t)>::value << ' ';
std::cout << " is_string " << is_string<decltype(t)>::value << ' ';
std::cout << sm_typeof(t) <<std::endl ;
// if (1) {
if ( (is_string<decltype(t)>::value)==1 ) {
// std::cout << " last char " << t.back() <<std::endl ;
// std::cout << " last char " <<std::endl ;
std::cout << " last char " << sm_lastchar(t) <<std::endl ;
}
else sm_lastchar(t); // sm_S.push(t);
sm_proc(args...) ;
}