📄 library.txt
字号:
UC Library Extensions
UnderC comes with a 'pocket' implementation of the standard C++ libraries, which is a reasonably faithful subset. This documentation describes those UnderC functions and classes which are not part of the C++ standard.
UC Library
Builtin functions:
Most of these are standard C functions, but there are a few unique to the UnderC system which give you runtime access to the compiler. You may evaluate expressions, execute commands, compile code, etc.
* Expands the text in expr using the UnderC preprocessor, putting the result
into buff.
void uc_macro_subst(const char* expr, char* buff, int buffsize);
* Executes a UC #-command, like #l or #help.
uc_cmd() expects the name of the command, _without_ the hash,
e.g. uc_cmd("l fred.cpp") or uc_cmd("help").
void uc_cmd(const char* cmd);
* Evaluates any C++ expression or statement; will return non-zero if
unsuccessful.
int uc_exec(const char* expr);
* Copies the result of the last uc_exec() into a buffer; if passed a non-zero
value in 'ret' it will get the error string,
otherwise the string will contain the value of the expression evaluated
(generally anything which is a 'message')
If 'filename' isn't NULL, then it will contain the file at which the message
occured, and 'line' will receive the line number.
void uc_result_pos(int ret, char* buff, int buffsize, char* filename, int* line);
Examples
uc_exec() is fed a C++ statement as you would type it in, complete with semicolon if required. You may be evaluating a statement for its side-effects, or be declaring something, but sometimes it's necessary to see the result of an operation:
;> uc_exec("23+2;");
;> char buff[80];
;; uc_result_pos(0,buff,80,0,0);
;> buff;
(char*) "(int) 25
"
The result is in fact exactly what you would get from the interactive UnderC prompt, complete with expression type and line feed at the end. It's fairly straightforward to strip these elements away. In fact, the UnderC DLL exports a function called uc_eval() which does precisely this, as you can see from its code from dll_entry.cpp. Not very elegant, but it does the job:
CEXPORT int XAPI uc_eval(char *expr, char *res, int sz)
{
int iret = ext_uc_eval(expr,res,sz) != FAIL;
if (*res=='(') { // *add 1.1.4 strip out type!
char buff[EXPR_BUFF_SIZE];
char *p = res;
while(*p && *p != ')') p++;
p++;
strcpy(buff,p);
strcpy(res,buff);
}
// *fix 1.1.4 nasty extra line feed!
int len = strlen(res);
if (res[len-1]=='\n') res[len-1] = '\0';
return iret;
}
If the expression fails to compile, or has a run-time error, uc_exec() will return a non-zero value. uc_result_pos() can then be used to get the error message.
;> int line; char file[120];
;> uc_exec("23=2");
(int) -1
;> uc_result(-1,buff,80,file,&line); buff;
(char*) "Can't assign to a const type"
;> file; line;
(char*) "CON"
(int) 6
UnderC Extensions to C++
There are two new keywords, typeof and __declare.
typeof may be used wherever a type is expected, and gives the type of its expression, in analogy to the sizeof operator. GCC already implements typeof, so there's a fair chance of it being accepted in the standard one of these fine years.
;> string s = "hello";
;> typeof(s) t = "bonzo";
;> t;
(string) t = 'bonzo'
It is suprisingly useful in template functions.
__declare has often been proposed, but there's no consensus on its final name. It is used as a pseudo-type for declarations, and declares a variable using the type of the initializing expression:
;> __declare st = t;
;> st;
(string&) 'bonzo'
I usually define 'let' to mean '__declare'; please note that when declaring multiple items, each new variable has the type of its own initializer. Also note that the type of 'a' becomes 'double', not 'const double'! Currently, the type may not be further qualified (by 'const' or '&', say)
;> #define let __declare
;> let a = 3.2, i = 2;
;> a; i;
(double) a = 3.2
(int) i = 2
In this form it is very useful for declaring local variables implicitly, especially if they have complicated types. Here I'm avoiding explicitly declaring the iterator to be of type 'list<int>::iterator':
;> list<int> ls;
;> ls.push_back(10);
;> ls.push_back(20);
;> for(let i = ls.begin(); i != ls.end(); ++i) cout << *i << endl;
10
20
__declare may be used as as the return type in a function definition; the convention is that the _first_ return encountered defines the actual type.
;> __declare f(double x) { return x*x; }
;; f(2.3);
(double) 5.29
Again, this is would be useful for template functions. However, __declare doesn't currently work for functions that return objects. The reason is that such functions need a hidden reference argument to pass a temporary object which will be returned; other implementations of course can use different strategies, but I suspect it will also be a problem for them as well; the compiler needs a hint that an implicit type is really an object.
Of course, it makes no sense to use __declare in a function _declaration_, although Andrew Koenig seems to think this is a reason not to use __declare at all.
I think the consensus is that C++ is already a sufficiently complex beast, but small extensions like this can make the language more expressive. I certainly use 'let' a lot in interactive work, although I would think twice about using it in production code, since such code will probably be compiled properly at some point. (A sufficiently advanced IDE would be able to deduce the type of the initializer and substitute the full type for 'let')
UC Library
FOR_EACH (#include <for_each.h>)
It is very common to want to iterate over all elements in a collection. Taking the list of integers from the last example I can say:
;> int k;
;; FOR_EACH(k,ls) cout << k << endl;
10
20
FOR_EACH is a thin macro wrapper around some template trickery; any object which behaves like a container (that is, has begin(),end(), etc) can be iterated over. It's implementation is quite simple for any compiler which implements 'typeof' (like UC or GCC), but _can_ be done for a standard compiler as well (see <foreach2.h>):
#define FOR_EACH(v,c) for(_ForEach<typeof(c),typeof(v)> _fe(c,v); \
_fe.get(); _fe.next())
template <class C, class T>
struct _ForEach {
typename C::iterator m_it,m_end;
T& m_var;
_ForEach(C& c, T& t) : m_var(t)
{ m_it = c.begin(); m_end = c.end(); }
bool get() {
bool res = m_it != m_end;
if (res) m_var = *m_it;
return res;
}
void next() { ++m_it; }
};
This is a nice example of typeof being used to deduce the type parameters of a template class from the arguments, which is otherwise only possible in a roundabout and less efficient way. It keeps two iterators, m_it and m_end, which are initialized using the container's begin() and end() methods. A reference to the variable is kept, and 'm_var = *m_it' does the magic of copying the next value from the sequence. So FOR_EACH is not the most efficient way to iterate through containers of concrete types which might be expensive to copy; otherwise it is pretty fast.
At this point I must admit that the thing is a macro, and therefore Considered Evil. Many abuses of the C preprocessor convinced people that it was not a device to leave in the hands of children (who might be tempted to turn C++ into Pascal, etc). However, all the arguments against the occaisional well-behaved statement macro seem less than convincing to me. The lexical scope issue can be controlled by a naming convention such as all caps, and we all know now to watch out for side-effects when defining macros. FOR_EACH is well-behaved because the macro parameters appear precisely once in the macro definition, and expressions are quite safe for both arguments.
There is a gotcha, of course; the container expression must not return a temporary object, since it will probably go out of scope in the for-loop initialization, leading to strange results. (I say probably because this is not well-defined behaviour with different compilers; GCC at least considers it a compile-time error)
Still, I've used the idiom for some time now and have never got into serious trouble yet. It is particularly useful when in interactive mode; if you still have aesthetic objections you can always mentally expand FOR_EACH as a standard iterator begin/end loop.
DirPaths (#include <uc/dir.h>)
DirPaths is a simple class for iterating over all files matching some given file mask. Since it behaves like a container, it can be used with FOR_EACH:
;> DirPaths dp("*.uc");
;> string f;
;> FOR_EACH(f,dp) cout << f << endl;
test.uc
skeleton.uc
persist.uc
simple.uc
However, here is an example of a FOR_EACH no-no. We do not get the expected output from the following statement, because the DirPaths() object is temporary and gets destroyed before the loop can iterate:
;> FOR_EACH(f,DirPaths(*.h")) cout << f << endl;
;;
If you need more information about a file, DirPaths can also be used with DirInfo:
;> DirPaths hp("*.h");
;> DirInfo di;
;> FOR_EACH(di,hp) cout << di.name() << ' ' << di.size() << endl;
defs.h 1239
type.h 893
test-defs.h 1027
old-defs.h 92
Regular Expressions with rx++ (#include<rx++.h>)
rx++ is a simple class wrapper around the standard POSIX regular expression calls; for UnderC we're using John Lord's RX library under Windows, and the libc implementation under Linux. Although sometimes tricky to set up, regular expressions are a powerful means of searching and processing text, which AWK and Perl programmers have used very effectively. C++ programmers do not currently have a standard way of using them (although the next iteration of the standard library promises to rectify this, probably by using the BOOST libraries)
;> #include <rx++.h>
;> Regexp rx("dog");
;> char* str = "the dog sat on the mat; the dog went outside";
;> rx.match(str);
(bool) true
;> rx.matched();
(string) 'dog'
You may wish to directly access the matched position in the given string:
;> rx.index();
(int) 4
;> rx.end_match();
(int) 7
;> int s = rx.index(), e = rx.end_match();
;> char buff[80];
;> strncpy(buff,str+s,e-s);
(char*) "dog"
The full POSIX functionality is available. For example, regular expressions are a
powerful way to extract formated data such as dates. Anything inside escaped parentheses
(\(, \)) is a group, which can be extracted from the matched string using a one-based index to the Regexp::matched() method:
;> Regexp rdat("\([0-9]*\)/\([0-9]*\)/\([0-9]*\)");
;> rdat.match(dates);
(bool) true
;> rdat.matched(); // the whole matched expression
(string) '10/09/2003'
;> rdat.matched(1);
(string) '10'
;> rdat.matched(2);
(string) '09'
;> rdat.matched(3);
(string) '2003'
rx++.h doesn't have the most efficient implemention (in particular the string extraction in Regexp::matched() is expensive) but it makes using the POSIX calls less confusing.
Turtle Graphics (#include <turtle.h>)
I've always been intrigued with Turtle Graphics ever since I read Seymour Papert's book _Mindstorms_ about the MIT Logo Project. Logo was the first programming system designed explicitly for children, and provided users with a 'turtle', which was either a little triangle on the screen, or an actual programmable device with wheels. The Turtle has both orientation and position, and can be commanded to move, draw or turn.
Here is a sample session, with comments:
;> TG tg("test"); // creating a TG object makes a graphics window appear
;> tg.init(); // initialize this object
;> tg.go_to(50,50); // move the turtle to (50,50) (default maximum is 100)
;> tg.show(); // show the turtle explicitly
;> tg.draw(10); // draw by moving the turtle forward
;> tg.right(); // turn to the right
;> tg.draw(10); // and draw
This function draws a very attractive tree with a few lines of code. TG::show() isn't called, since actually displaying the turtle slows things down considerably. A utility class, TG_State, allows one to save a turtle graphics state and restore it:
void draw_tree(TG& tg, double l, double fact, double ang=45.0, double eps=0.1)
{
TG_State state(tg);
if (l < eps) return;
// Draw the line
tg.draw(l);
// Save state and go to the left
state.save();
tg.turn(ang);
draw_tree(tg,fact*l,fact,ang,eps);
// restore state and go to the right
state.restore();
tg.turn(-ang);
draw_tree(tg,fact*l,fact,ang,eps);
}
// a testing function which lets us explore the effect
// of angle on the tree
void do_tree_with_angle(TG& tg, double ang)
{
tg.init();
tg.go_to(50,10);
draw_tree(tg,20,0.7,ang,0.1);
}
Although this is currently only available under Windows, it would not be difficult to implement the UC graphics primitives for some other platform (using GTK+, for example). In the Windows version these are implemented as part of the UC core (uc_graphics.cpp) but obviously a shared library would do just as well.
The UnderC Reflection Interface (UCRI)
C++ Run-time Type Information (RTTI) is fairly limited compared to more dynamic languages such as Java. A C++ executable may optionally contain debug information, but this is primarily of interest only to debuggers, not standardized, and not usually available to the running program. And we accept that that the price of extra information is a larger executable. So RTTI is a compromise between the needs of a program to be aware of the actual types of objects at runtime, and our usual desire for lean and mean executables.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -