changeset 108:288fe1029e1f trunk

[svn r112] Fixed 'case 1,2,3:' style case statements. Fixed a bunch of bugs with return/break/continue in loops. Fixed support for the DMDFE hidden implicit return value variable. This can be needed for some foreach statements where the loop body is converted to a nested delegate, but also possibly returns from the function. Added std.math to phobos. Added AA runtime support code, done ground work for implementing AAs. Several other bugfixes.
author lindquist
date Tue, 20 Nov 2007 05:29:20 +0100
parents 3efbcc81ba45
children 5ab8e92611f9
files dmd/expression.c dmd/expression.h dmd/mtype.c dmd/statement.c dmd/statement.h gen/arrays.cpp gen/arrays.h gen/functions.cpp gen/irstate.cpp gen/irstate.h gen/statements.cpp gen/toir.cpp gen/tollvm.cpp llvmdc.kdevelop.filelist lphobos/build.sh lphobos/internal/aaA.d lphobos/phobos.d lphobos/std/format.d lphobos/std/math.d lphobos/std/string.d lphobos/typeinfos2.d test/aa1.d test/bug70.d test/bug71.d test/bug72.d test/bug73.d test/switch2.d
diffstat 27 files changed, 8677 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/dmd/expression.c	Tue Nov 20 00:02:35 2007 +0100
+++ b/dmd/expression.c	Tue Nov 20 05:29:20 2007 +0100
@@ -5870,6 +5870,7 @@
 	{
 	    VarExp *dve = (VarExp *)e1;
 	    FuncDeclaration *f = dve->var->isFuncDeclaration();
+        VarDeclaration *v = dve->var->isVarDeclaration();
 
 	    if (f && f->isNested())
 	    {	Expression *e;
@@ -5878,6 +5879,10 @@
 		e = e->semantic(sc);
 		return e;
 	    }
+        else if (v)
+        {
+        v->llvmNeedsStorage = true;
+        }
 	}
 	else if (e1->op == TOKarray)
 	{
@@ -7543,6 +7548,7 @@
 AddExp::AddExp(Loc loc, Expression *e1, Expression *e2)
 	: BinExp(loc, TOKadd, sizeof(AddExp), e1, e2)
 {
+    llvmFieldIndex = false;
 }
 
 Expression *AddExp::semantic(Scope *sc)
--- a/dmd/expression.h	Tue Nov 20 00:02:35 2007 +0100
+++ b/dmd/expression.h	Tue Nov 20 05:29:20 2007 +0100
@@ -1121,6 +1121,9 @@
     Identifier *opId_r();
 
     elem *toElem(IRState *irs);
+
+    // LLVMDC
+    bool llvmFieldIndex;
 };
 
 struct MinExp : BinExp
--- a/dmd/mtype.c	Tue Nov 20 00:02:35 2007 +0100
+++ b/dmd/mtype.c	Tue Nov 20 05:29:20 2007 +0100
@@ -4309,6 +4309,11 @@
 	b = new AddrExp(e->loc, e);
 	b->type = e->type->pointerTo();
 	b = new AddExp(e->loc, b, new IntegerExp(e->loc, v->offset, Type::tint32));
+#if IN_LLVM
+    // LLVMDC modification
+    // this is *essential*
+    ((AddExp*)b)->llvmFieldIndex = true;
+#endif
 	b->type = v->type->pointerTo();
 	e = new PtrExp(e->loc, b);
 	e->type = v->type;
--- a/dmd/statement.c	Tue Nov 20 00:02:35 2007 +0100
+++ b/dmd/statement.c	Tue Nov 20 05:29:20 2007 +0100
@@ -1437,7 +1437,7 @@
 		exps->push(aggr);
 		size_t keysize = taa->key->size();
 		keysize = (keysize + 3) & ~3;
-		exps->push(new IntegerExp(0, keysize, Type::tint32));
+		exps->push(new IntegerExp(0, keysize, Type::tsize_t));
 		exps->push(flde);
 		e = new CallExp(loc, ec, exps);
 		e->type = Type::tindex;	// don't run semantic() on e
--- a/dmd/statement.h	Tue Nov 20 00:02:35 2007 +0100
+++ b/dmd/statement.h	Tue Nov 20 05:29:20 2007 +0100
@@ -46,6 +46,7 @@
 struct TryCatchStatement;
 struct HdrGenState;
 struct InterState;
+struct CaseStatement;
 
 enum TOK;
 
@@ -114,6 +115,7 @@
     virtual CompoundStatement *isCompoundStatement() { return NULL; }
     virtual ReturnStatement *isReturnStatement() { return NULL; }
     virtual IfStatement *isIfStatement() { return NULL; }
+    virtual CaseStatement* isCaseStatement() { return NULL; }
 };
 
 struct ExpStatement : Statement
@@ -436,6 +438,8 @@
     Statement *inlineScan(InlineScanState *iss);
 
     void toIR(IRState *irs);
+
+    CaseStatement* isCaseStatement() { return this; }
 };
 
 struct DefaultStatement : Statement
--- a/gen/arrays.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/arrays.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -369,7 +369,7 @@
     return ret;
 }
 
-void DtoArrayCopy(DSliceValue* dst, DSliceValue* src)
+void DtoArrayCopySlices(DSliceValue* dst, DSliceValue* src)
 {
     llvm::Type* arrty = llvm::PointerType::get(llvm::Type::Int8Ty);
 
@@ -390,6 +390,25 @@
     new llvm::CallInst(fn, llargs.begin(), llargs.end(), "", gIR->scopebb());
 }
 
+void DtoArrayCopyToSlice(DSliceValue* dst, DValue* src)
+{
+    llvm::Type* arrty = llvm::PointerType::get(llvm::Type::Int8Ty);
+
+    llvm::Value* sz1;
+    llvm::Value* dstarr = new llvm::BitCastInst(get_slice_ptr(dst,sz1),arrty,"tmp",gIR->scopebb());
+    llvm::Value* srcarr = new llvm::BitCastInst(DtoArrayPtr(src),arrty,"tmp",gIR->scopebb());
+
+    llvm::Function* fn = (global.params.is64bit) ? LLVM_DeclareMemCpy64() : LLVM_DeclareMemCpy32();
+    std::vector<llvm::Value*> llargs;
+    llargs.resize(4);
+    llargs[0] = dstarr;
+    llargs[1] = srcarr;
+    llargs[2] = sz1;
+    llargs[3] = llvm::ConstantInt::get(llvm::Type::Int32Ty, 0, false);
+
+    new llvm::CallInst(fn, llargs.begin(), llargs.end(), "", gIR->scopebb());
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////
 void DtoStaticArrayCopy(llvm::Value* dst, llvm::Value* src)
 {
--- a/gen/arrays.h	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/arrays.h	Tue Nov 20 05:29:20 2007 +0100
@@ -10,7 +10,8 @@
 llvm::Constant* DtoConstSlice(llvm::Constant* dim, llvm::Constant* ptr);
 llvm::Constant* DtoConstStaticArray(const llvm::Type* t, llvm::Constant* c);
 
-void DtoArrayCopy(DSliceValue* dst, DSliceValue* src);
+void DtoArrayCopySlices(DSliceValue* dst, DSliceValue* src);
+void DtoArrayCopyToSlice(DSliceValue* dst, DValue* src);
 
 void DtoArrayInit(llvm::Value* l, llvm::Value* r);
 void DtoArrayInit(llvm::Value* ptr, llvm::Value* dim, llvm::Value* val);
--- a/gen/functions.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/functions.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -378,6 +378,8 @@
     }
     if (f->llvmUsesThis) {
         iarg->setName("this");
+        fdecl->llvmThisVar = iarg;
+        assert(fdecl->llvmThisVar);
         ++iarg;
     }
     int varargs = -1;
@@ -426,6 +428,8 @@
     if (fd->llvmDefined) return;
     fd->llvmDefined = true;
 
+    assert(fd->llvmDeclared);
+
     Logger::println("DtoDefineFunc(%s)", fd->toPrettyChars());
     LOG_SCOPE;
 
@@ -474,6 +478,7 @@
             assert(fd->llvmIRFunc);
             gIR->functions.push_back(fd->llvmIRFunc);
 
+            /* // moved to declaration
             // this handling
             if (f->llvmUsesThis) {
                 Logger::println("uses this");
@@ -483,6 +488,7 @@
                     fd->llvmThisVar = func->arg_begin();
                 assert(fd->llvmThisVar != 0);
             }
+            */
 
             if (fd->isMain())
                 gIR->emitMain = true;
@@ -497,6 +503,12 @@
                 f->llvmAllocaPoint = new llvm::BitCastInst(llvm::ConstantInt::get(llvm::Type::Int32Ty,0,false),llvm::Type::Int32Ty,"alloca point",gIR->scopebb());
                 gIR->func()->allocapoint = f->llvmAllocaPoint;
 
+                // need result variable? (not nested)
+                if (fd->vresult && !fd->vresult->nestedref) {
+                    Logger::println("non-nested vresult value");
+                    fd->vresult->llvmValue = new llvm::AllocaInst(DtoType(fd->vresult->type),"function_vresult",f->llvmAllocaPoint);
+                }
+
                 // give arguments storage
                 size_t n = Argument::dim(f->parameters);
                 for (int i=0; i < n; ++i) {
@@ -524,7 +536,14 @@
 
                 llvm::Value* parentNested = NULL;
                 if (FuncDeclaration* fd2 = fd->toParent()->isFuncDeclaration()) {
-                    parentNested = fd2->llvmNested;
+                    if (!fd->isStatic())
+                        parentNested = fd2->llvmNested;
+                }
+
+                // need result variable? (nested)
+                if (fd->vresult && fd->vresult->nestedref) {
+                    Logger::println("nested vresult value: %s", fd->vresult->toChars());
+                    fd->llvmNestedVars.insert(fd->vresult);
                 }
 
                 // construct nested variables struct
--- a/gen/irstate.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/irstate.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -83,10 +83,6 @@
 
 llvm::BasicBlock* IRState::scopebb()
 {
-    return scopebegin();
-}
-llvm::BasicBlock* IRState::scopebegin()
-{
     IRScope& s = scope();
     assert(s.begin);
     return s.begin;
--- a/gen/irstate.h	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/irstate.h	Tue Nov 20 05:29:20 2007 +0100
@@ -158,9 +158,8 @@
     // basic block scopes
     std::vector<IRScope> scopes;
     IRScope& scope();
-    llvm::BasicBlock* scopebegin();
+    llvm::BasicBlock* scopebb();
     llvm::BasicBlock* scopeend();
-    llvm::BasicBlock* scopebb();
     bool scopereturned();
 
     // loop blocks
--- a/gen/statements.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/statements.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -163,7 +163,7 @@
         Logger::cout() << "if conditional: " << *cond_val << '\n';
         cond_val = DtoBoolean(cond_val);
     }
-    llvm::Value* ifgoback = new llvm::BranchInst(ifbb, elsebb, cond_val, gIR->scopebegin());
+    llvm::Value* ifgoback = new llvm::BranchInst(ifbb, elsebb, cond_val, gIR->scopebb());
 
     // replace current scope
     gIR->scope() = IRScope(ifbb,elsebb);
@@ -171,7 +171,7 @@
     // do scoped statements
     ifbody->toIR(p);
     if (!gIR->scopereturned()) {
-        new llvm::BranchInst(endbb,gIR->scopebegin());
+        new llvm::BranchInst(endbb,gIR->scopebb());
     }
 
     if (elsebody) {
@@ -179,7 +179,7 @@
         gIR->scope() = IRScope(elsebb,endbb);
         elsebody->toIR(p);
         if (!gIR->scopereturned()) {
-            new llvm::BranchInst(endbb,gIR->scopebegin());
+            new llvm::BranchInst(endbb,gIR->scopebb());
         }
     }
 
@@ -199,14 +199,14 @@
     llvm::BasicBlock* beginbb = 0;
 
     // remove useless branches by clearing and reusing the current basicblock
-    llvm::BasicBlock* bb = p->scopebegin();
+    llvm::BasicBlock* bb = p->scopebb();
     if (bb->empty()) {
         beginbb = bb;
     }
     else {
         assert(!p->scopereturned());
         beginbb = new llvm::BasicBlock("scope", p->topfunc(), oldend);
-        new llvm::BranchInst(beginbb, p->scopebegin());
+        new llvm::BranchInst(beginbb, p->scopebb());
     }
     llvm::BasicBlock* endbb = new llvm::BasicBlock("endscope", p->topfunc(), oldend);
 
@@ -234,7 +234,7 @@
 
     // move into the while block
     p->ir->CreateBr(whilebb);
-    //new llvm::BranchInst(whilebb, gIR->scopebegin());
+    //new llvm::BranchInst(whilebb, gIR->scopebb());
 
     // replace current scope
     gIR->scope() = IRScope(whilebb,endbb);
@@ -256,7 +256,8 @@
     p->loopbbs.pop_back();
 
     // loop
-    new llvm::BranchInst(whilebb, gIR->scopebegin());
+    if (!gIR->scopereturned())
+        new llvm::BranchInst(whilebb, gIR->scopebb());
 
     // rewrite the scope
     gIR->scope() = IRScope(endbb,oldend);
@@ -276,13 +277,16 @@
     llvm::BasicBlock* endbb = new llvm::BasicBlock("enddowhile", gIR->topfunc(), oldend);
 
     // move into the while block
-    new llvm::BranchInst(dowhilebb, gIR->scopebegin());
+    assert(!gIR->scopereturned());
+    new llvm::BranchInst(dowhilebb, gIR->scopebb());
 
     // replace current scope
     gIR->scope() = IRScope(dowhilebb,endbb);
 
     // do-while body code
+    p->loopbbs.push_back(IRScope(dowhilebb,endbb));
     body->toIR(p);
+    p->loopbbs.pop_back();
 
     // create the condition
     DValue* cond_e = condition->toElem(p);
@@ -290,7 +294,7 @@
     delete cond_e;
 
     // conditional branch
-    llvm::Value* ifbreak = new llvm::BranchInst(dowhilebb, endbb, cond_val, gIR->scopebegin());
+    llvm::Value* ifbreak = new llvm::BranchInst(dowhilebb, endbb, cond_val, gIR->scopebb());
 
     // rewrite the scope
     gIR->scope() = IRScope(endbb,oldend);
@@ -316,7 +320,7 @@
     init->toIR(p);
 
     // move into the for condition block, ie. start the loop
-    new llvm::BranchInst(forbb, gIR->scopebegin());
+    new llvm::BranchInst(forbb, gIR->scopebb());
 
     p->loopbbs.push_back(IRScope(forincbb,endbb));
 
@@ -338,7 +342,8 @@
     body->toIR(p);
 
     // move into the for increment block
-    new llvm::BranchInst(forincbb, gIR->scopebegin());
+    if (!gIR->scopereturned())
+        new llvm::BranchInst(forincbb, gIR->scopebb());
     gIR->scope() = IRScope(forincbb, endbb);
 
     // increment
@@ -348,7 +353,8 @@
     }
 
     // loop
-    new llvm::BranchInst(forbb, gIR->scopebegin());
+    if (!gIR->scopereturned())
+        new llvm::BranchInst(forbb, gIR->scopebb());
 
     p->loopbbs.pop_back();
 
@@ -368,7 +374,7 @@
         assert(0);
     }
     else {
-        new llvm::BranchInst(gIR->loopbbs.back().end, gIR->scopebegin());
+        new llvm::BranchInst(gIR->loopbbs.back().end, gIR->scopebb());
     }
 }
 
@@ -384,7 +390,7 @@
         assert(0);
     }
     else {
-        new llvm::BranchInst(gIR->loopbbs.back().begin, gIR->scopebegin());
+        new llvm::BranchInst(gIR->loopbbs.back().begin, gIR->scopebb());
     }
 }
 
@@ -527,25 +533,33 @@
     llvm::BasicBlock* oldend = gIR->scopeend();
 
     // collect the needed cases
-    typedef std::pair<llvm::BasicBlock*, llvm::ConstantInt*> CasePair;
+    typedef std::pair<llvm::BasicBlock*, std::vector<llvm::ConstantInt*> > CasePair;
     std::vector<CasePair> vcases;
+    std::vector<Statement*> vbodies;
     for (int i=0; i<cases->dim; ++i)
     {
         CaseStatement* cs = (CaseStatement*)cases->data[i];
 
-        // get the case value
-        DValue* e = cs->exp->toElem(p);
-        DConstValue* ce = e->isConst();
-        assert(ce);
-        llvm::ConstantInt* ec = isaConstantInt(ce->c);
-        assert(ec);
-        delete e;
-
         // create the case bb with a nice label
         std::string lblname("case"+std::string(cs->exp->toChars()));
         llvm::BasicBlock* bb = new llvm::BasicBlock(lblname, p->topfunc(), oldend);
 
-        vcases.push_back(CasePair(bb,ec));
+        std::vector<llvm::ConstantInt*> tmp;
+        CaseStatement* last;
+        do {
+            // get the case value
+            DValue* e = cs->exp->toElem(p);
+            DConstValue* ce = e->isConst();
+            assert(ce);
+            llvm::ConstantInt* ec = isaConstantInt(ce->c);
+            assert(ec);
+            tmp.push_back(ec);
+            last = cs;
+        }
+        while (cs = cs->statement->isCaseStatement());
+
+        vcases.push_back(CasePair(bb, tmp));
+        vbodies.push_back(last->statement);
     }
 
     // default
@@ -566,7 +580,11 @@
     size_t n = vcases.size();
     for (size_t i=0; i<n; ++i)
     {
-        si->addCase(vcases[i].second, vcases[i].first);
+        size_t nc = vcases[i].second.size();
+        for (size_t j=0; j<nc; ++j)
+        {
+            si->addCase(vcases[i].second[j], vcases[i].first);
+        }
     }
 
     // insert case statements
@@ -576,7 +594,7 @@
         p->scope() = IRScope(vcases[i].first,nextbb);
 
         p->loopbbs.push_back(IRScope(p->scopebb(),endbb));
-        static_cast<CaseStatement*>(cases->data[i])->statement->toIR(p);
+        vbodies[i]->toIR(p);
         p->loopbbs.pop_back();
 
         llvm::BasicBlock* curbb = p->scopebb();
@@ -606,6 +624,15 @@
 }
 
 //////////////////////////////////////////////////////////////////////////////
+void CaseStatement::toIR(IRState* p)
+{
+    Logger::println("CaseStatement::toIR(): %s", toChars());
+    LOG_SCOPE;
+
+    assert(0);
+}
+
+//////////////////////////////////////////////////////////////////////////////
 
 void UnrolledLoopStatement::toIR(IRState* p)
 {
@@ -735,7 +762,7 @@
     }
     new llvm::BranchInst(bodybb, endbb, done, p->scopebb());
 
-    // body
+    // init body
     p->scope() = IRScope(bodybb,nextbb);
 
     // get value for this iteration
@@ -750,12 +777,10 @@
         DValue* dst = new DVarValue(value->type, valvar, true);
         DValue* src = new DVarValue(value->type, value->llvmValue, true);
         DtoAssign(dst, src);
-        delete dst;
-        delete src;
         value->llvmValue = valvar;
     }
 
-    // body
+    // emit body
     p->loopbbs.push_back(IRScope(nextbb,endbb));
     body->toIR(p);
     p->loopbbs.pop_back();
@@ -860,7 +885,7 @@
 //STUBST(ReturnStatement);
 //STUBST(ContinueStatement);
 STUBST(DefaultStatement);
-STUBST(CaseStatement);
+//STUBST(CaseStatement);
 //STUBST(SwitchStatement);
 STUBST(SwitchErrorStatement);
 STUBST(Statement);
--- a/gen/toir.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/toir.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -488,7 +488,9 @@
             DtoAssign(l, r);
     }
 
-    return l;
+    if (l->isSlice() || l->isComplex())
+        return l;
+    return new DImValue(type, l->getRVal());
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -507,7 +509,8 @@
     Type* e2type = DtoDType(e2->type);
 
     if (e1type != e2type) {
-        if (e1type->ty == Tpointer && e1next && e1next->ty == Tstruct) {
+        if (llvmFieldIndex) {
+            assert(e1type->ty == Tpointer && e1next && e1next->ty == Tstruct);
             Logger::println("add to AddrExp of struct");
             assert(r->isConst());
             llvm::ConstantInt* cofs = llvm::cast<llvm::ConstantInt>(r->isConst()->c);
@@ -1001,8 +1004,9 @@
                 llargs[j] = DtoArgument(llfnty->getParamType(j), fnarg, (Expression*)arguments->data[i]);
                 // this hack is necessary :/
                 if (dfn && dfn->func && dfn->func->llvmRunTimeHack) {
-                    Logger::println("llvmRunTimeHack==true");
+                    Logger::println("llvmRunTimeHack==true - force casting argument");
                     if (llargs[j]->getType() != llfnty->getParamType(j)) {
+                        Logger::cout() << "from: " << *llargs[j]->getType() << " to: " << *llfnty->getParamType(j);
                         llargs[j] = DtoBitCast(llargs[j], llfnty->getParamType(j));
                     }
                 }
@@ -1155,12 +1159,15 @@
 
 DValue* AddrExp::toElem(IRState* p)
 {
-    Logger::print("AddrExp::toElem: %s | %s\n", toChars(), type->toChars());
+    Logger::println("AddrExp::toElem: %s | %s", toChars(), type->toChars());
     LOG_SCOPE;
     DValue* v = e1->toElem(p);
-    if (v->isField())
+    if (v->isField()) {
+        Logger::println("is field");
         return v;
+    }
     else if (DFuncValue* fv = v->isFunc()) {
+        Logger::println("is func");
         //Logger::println("FuncDeclaration");
         FuncDeclaration* fd = fv->func;
         assert(fd);
@@ -1169,8 +1176,10 @@
         return new DFuncValue(fd, fd->llvmValue);
     }
     else if (DImValue* im = v->isIm()) {
+        Logger::println("is immediate");
         return v;
     }
+    Logger::println("is nothing special");
     return new DFieldValue(type, v->getLVal(), false);
 }
 
@@ -1178,7 +1187,7 @@
 
 DValue* PtrExp::toElem(IRState* p)
 {
-    Logger::print("PtrExp::toElem: %s | %s\n", toChars(), type->toChars());
+    Logger::println("PtrExp::toElem: %s | %s", toChars(), type->toChars());
     LOG_SCOPE;
 
     DValue* a = e1->toElem(p);
@@ -1552,7 +1561,7 @@
 
     Type* t = DtoDType(e1->type);
     Type* e2t = DtoDType(e2->type);
-    assert(t == e2t);
+    //assert(t == e2t);
 
     llvm::Value* eval = 0;
 
@@ -1571,7 +1580,12 @@
         default:
             assert(0);
         }
-        eval = new llvm::ICmpInst(cmpop, l->getRVal(), r->getRVal(), "tmp", p->scopebb());
+        llvm::Value* lv = l->getRVal();
+        llvm::Value* rv = r->getRVal();
+        if (rv->getType() != lv->getType()) {
+            rv = DtoBitCast(rv, lv->getType());
+        }
+        eval = new llvm::ICmpInst(cmpop, lv, rv, "tmp", p->scopebb());
     }
     else if (t->iscomplex())
     {
@@ -2212,7 +2226,7 @@
     if (fd->isNested()) Logger::println("nested");
     Logger::println("kind = %s\n", fd->kind());
 
-    DtoForceDeclareDsymbol(fd);
+    DtoForceDefineDsymbol(fd);
 
     bool temp = false;
     llvm::Value* lval = NULL;
--- a/gen/tollvm.cpp	Tue Nov 20 00:02:35 2007 +0100
+++ b/gen/tollvm.cpp	Tue Nov 20 05:29:20 2007 +0100
@@ -174,6 +174,13 @@
         return DtoType(bt);
     }
 
+    // associative arrays
+    case Taarray:
+    {
+        // TODO this is a kludge
+        return llvm::PointerType::get(llvm::Type::Int8Ty);
+    }
+
     default:
         printf("trying to convert unknown type with value %d\n", t->ty);
         assert(0);
@@ -796,7 +803,15 @@
 
     // on this stack
     if (fd == f) {
-        llvm::Value* v = DtoGEPi(vd->llvmValue,0,unsigned(vd->llvmNestedIndex),"tmp");
+        llvm::Value* vdv = vd->llvmValue;
+        if (!vdv)
+        {
+            Logger::println(":o null vd->llvmValue for: %s", vd->toChars());
+            vdv = fd->llvmNested;
+            assert(vdv);
+        }
+        assert(vd->llvmNestedIndex != ~0);
+        llvm::Value* v = DtoGEPi(vdv,0,unsigned(vd->llvmNestedIndex),"tmp");
         if (vd->isParameter() && (vd->isRef() || vd->isOut() || DtoIsPassedByRef(vd->type))) {
             Logger::cout() << "1267 loading: " << *v << '\n';
             v = gIR->ir->CreateLoad(v,"tmp");
@@ -859,7 +874,7 @@
         // lhs is slice
         if (DSliceValue* s = lhs->isSlice()) {
             if (DSliceValue* s2 = rhs->isSlice()) {
-                DtoArrayCopy(s, s2);
+                DtoArrayCopySlices(s, s2);
             }
             else if (t->next == t2) {
                 if (s->len)
@@ -867,8 +882,9 @@
                 else
                     DtoArrayInit(s->ptr, rhs->getRVal());
             }
-            else
-            assert(rhs->inPlace());
+            else {
+                DtoArrayCopyToSlice(s, rhs);
+            }
         }
         // rhs is slice
         else if (DSliceValue* s = rhs->isSlice()) {
@@ -926,8 +942,15 @@
             DtoComplexAssign(dst, rhs->getRVal());
     }
     else {
+        llvm::Value* l;
+        if (DLRValue* lr = lhs->isLRValue()) {
+            l = lr->getLVal();
+            rhs = DtoCast(rhs, lr->getLType());
+        }
+        else {
+            l = lhs->getLVal();
+        }
         llvm::Value* r = rhs->getRVal();
-        llvm::Value* l = lhs->getLVal();
         Logger::cout() << "assign\nlhs: " << *l << "rhs: " << *r << '\n';
         const llvm::Type* lit = l->getType()->getContainedType(0);
         if (r->getType() != lit) { // :(
@@ -1124,6 +1147,7 @@
 DValue* DtoCast(DValue* val, Type* to)
 {
     Type* fromtype = DtoDType(val->getType());
+    Logger::println("Casting from '%s' to '%s'", fromtype->toChars(), to->toChars());
     if (fromtype->isintegral()) {
         return DtoCastInt(val, to);
     }
--- a/llvmdc.kdevelop.filelist	Tue Nov 20 00:02:35 2007 +0100
+++ b/llvmdc.kdevelop.filelist	Tue Nov 20 05:29:20 2007 +0100
@@ -142,6 +142,7 @@
 lphobos/internal
 lphobos/internal/aApply.d
 lphobos/internal/aApplyR.d
+lphobos/internal/aaA.d
 lphobos/internal/adi.d
 lphobos/internal/arrays.d
 lphobos/internal/cmath2.d
@@ -181,6 +182,7 @@
 lphobos/std/ctype.d
 lphobos/std/format.d
 lphobos/std/intrinsic.d
+lphobos/std/math.d
 lphobos/std/moduleinit.d
 lphobos/std/outofmemory.d
 lphobos/std/stdarg.d
@@ -229,6 +231,7 @@
 runalltests.d
 test
 test/a.d
+test/aa1.d
 test/alignment.d
 test/alloca1.d
 test/arrayinit.d
@@ -310,6 +313,9 @@
 test/bug66.d
 test/bug7.d
 test/bug70.d
+test/bug71.d
+test/bug72.d
+test/bug73.d
 test/bug8.d
 test/bug9.d
 test/c.d
@@ -401,6 +407,7 @@
 test/structs6.d
 test/structs7.d
 test/switch1.d
+test/switch2.d
 test/sync1.d
 test/templ1.d
 test/templ2.d
--- a/lphobos/build.sh	Tue Nov 20 00:02:35 2007 +0100
+++ b/lphobos/build.sh	Tue Nov 20 05:29:20 2007 +0100
@@ -20,7 +20,7 @@
 
 
 echo "compiling typeinfo 1"
-rebuild typeinfos1.d -c -oqobj -dc=llvmdc-posix -v || exit 1
+rebuild typeinfos1.d -c -oqobj -dc=llvmdc-posix || exit 1
 llvm-link -f -o=../lib/llvmdcore.bc `ls obj/typeinfo1.*.bc` ../lib/llvmdcore.bc || exit 1
 
 echo "compiling typeinfo 2"
@@ -33,10 +33,12 @@
 llvm-link -f -o=../lib/llvmdcore.bc obj/aApply.bc obj/aApplyR.bc ../lib/llvmdcore.bc || exit 1
 
 echo "compiling array runtime support"
-llvmdc internal/qsort2.d -c -odobj || exit
+llvmdc internal/qsort2.d -c -odobj || exit 1
 llvm-link -f -o=../lib/llvmdcore.bc obj/qsort2.bc ../lib/llvmdcore.bc || exit 1
-llvmdc internal/adi.d -c -odobj || exit
+llvmdc internal/adi.d -c -odobj || exit 1
 llvm-link -f -o=../lib/llvmdcore.bc obj/adi.bc ../lib/llvmdcore.bc || exit 1
+llvmdc internal/aaA.d -c -odobj || exit 1
+llvm-link -f -o=../lib/llvmdcore.bc obj/aaA.bc ../lib/llvmdcore.bc || exit 1
 
 echo "compiling object implementation"
 llvmdc internal/objectimpl.d -c -odobj || exit 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lphobos/internal/aaA.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,821 @@
+//_ aaA.d
+
+/**
+ * Part of the D programming language runtime library.
+ * Implementation of associative arrays.
+ */
+
+/*
+ *  Copyright (C) 2000-2007 by Digital Mars, www.digitalmars.com
+ *  Written by Walter Bright
+ *
+ *  This software is provided 'as-is', without any express or implied
+ *  warranty. In no event will the authors be held liable for any damages
+ *  arising from the use of this software.
+ *
+ *  Permission is granted to anyone to use this software for any purpose,
+ *  including commercial applications, and to alter it and redistribute it
+ *  freely, subject to the following restrictions:
+ *
+ *  o  The origin of this software must not be misrepresented; you must not
+ *     claim that you wrote the original software. If you use this software
+ *     in a product, an acknowledgment in the product documentation would be
+ *     appreciated but is not required.
+ *  o  Altered source versions must be plainly marked as such, and must not
+ *     be misrepresented as being the original software.
+ *  o  This notice may not be removed or altered from any source
+ *     distribution.
+ */
+
+
+//import std.stdio;
+import std.c.stdarg;
+import std.c.stdio;
+import std.c.stdlib;
+import std.c.string;
+import std.string;
+
+import std.outofmemory;
+
+// Auto-rehash and pre-allocate - Dave Fladebo
+
+static size_t[] prime_list = [
+    97UL,         389UL,
+    1543UL,       6151UL,
+    24593UL,      98317UL,
+    393241UL,     1572869UL,
+    6291469UL,    25165843UL,
+    100663319UL,  402653189UL,
+    1610612741UL, 4294967291UL
+];
+
+/* This is the type of the return value for dynamic arrays.
+ * It should be a type that is returned in registers.
+ * Although DMD will return types of Array in registers,
+ * gcc will not, so we instead use a 'long'.
+ */
+alias long ArrayRet_t;
+
+struct Array
+{
+    size_t length;
+    void* ptr;
+}
+
+struct aaA
+{
+    aaA *left;
+    aaA *right;
+    hash_t hash;
+    /* key   */
+    /* value */
+}
+
+struct BB
+{
+    aaA*[] b;
+    size_t nodes;	// total number of aaA nodes
+}
+
+/* This is the type actually seen by the programmer, although
+ * it is completely opaque.
+ */
+
+struct AA
+{
+    BB* a;
+}
+
+/**********************************
+ * Align to next pointer boundary, so that
+ * GC won't be faced with misaligned pointers
+ * in value.
+ */
+
+size_t aligntsize(size_t tsize)
+{
+    // Is pointer alignment on the x64 4 bytes or 8?
+    return (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
+}
+
+extern (C):
+
+/*************************************************
+ * Invariant for aa.
+ */
+
+/+
+void _aaInvAh(aaA*[] aa)
+{
+    for (size_t i = 0; i < aa.length; i++)
+    {
+	if (aa[i])
+	    _aaInvAh_x(aa[i]);
+    }
+}
+
+private int _aaCmpAh_x(aaA *e1, aaA *e2)
+{   int c;
+
+    c = e1.hash - e2.hash;
+    if (c == 0)
+    {
+	c = e1.key.length - e2.key.length;
+	if (c == 0)
+	    c = memcmp((char *)e1.key, (char *)e2.key, e1.key.length);
+    }
+    return c;
+}
+
+private void _aaInvAh_x(aaA *e)
+{
+    hash_t key_hash;
+    aaA *e1;
+    aaA *e2;
+
+    key_hash = getHash(e.key);
+    assert(key_hash == e.hash);
+
+    while (1)
+    {   int c;
+
+	e1 = e.left;
+	if (e1)
+	{
+	    _aaInvAh_x(e1);		// ordinary recursion
+	    do
+	    {
+		c = _aaCmpAh_x(e1, e);
+		assert(c < 0);
+		e1 = e1.right;
+	    } while (e1 != null);
+	}
+
+	e2 = e.right;
+	if (e2)
+	{
+	    do
+	    {
+		c = _aaCmpAh_x(e, e2);
+		assert(c < 0);
+		e2 = e2.left;
+	    } while (e2 != null);
+	    e = e.right;		// tail recursion
+	}
+	else
+	    break;
+    }
+}
++/
+
+/****************************************************
+ * Determine number of entries in associative array.
+ */
+
+size_t _aaLen(AA aa)
+    in
+    {
+	//printf("_aaLen()+\n");
+	//_aaInv(aa);
+    }
+    out (result)
+    {
+	size_t len = 0;
+
+	void _aaLen_x(aaA* ex)
+	{
+	    auto e = ex;
+	    len++;
+
+	    while (1)
+	    {
+		if (e.right)
+		    _aaLen_x(e.right);
+		e = e.left;
+		if (!e)
+		    break;
+		len++;
+	    }
+	}
+
+	if (aa.a)
+	{
+	    foreach (e; aa.a.b)
+	    {
+		if (e)
+		    _aaLen_x(e);
+	    }
+	}
+	assert(len == result);
+
+	//printf("_aaLen()-\n");
+    }
+    body
+    {
+	return aa.a ? aa.a.nodes : 0;
+    }
+
+
+/*************************************************
+ * Get pointer to value in associative array indexed by key.
+ * Add entry for key if it is not already there.
+ */
+
+void* _aaGet(AA* aa, TypeInfo keyti, size_t valuesize, ...)
+    in
+    {
+	assert(aa);
+    }
+    out (result)
+    {
+	assert(result);
+	assert(aa.a);
+	assert(aa.a.b.length);
+	//assert(_aaInAh(*aa.a, key));
+    }
+    body
+    {
+	auto pkey = cast(void *)(&valuesize + 1);
+	size_t i;
+	aaA* e;
+	auto keysize = aligntsize(keyti.tsize());
+
+	if (!aa.a)
+	    aa.a = new BB();
+
+	if (!aa.a.b.length)
+	{
+	    alias aaA *pa;
+	    auto len = prime_list[0];
+
+	    aa.a.b = new pa[len];
+	}
+
+	auto key_hash = keyti.getHash(pkey);
+	//printf("hash = %d\n", key_hash);
+	i = key_hash % aa.a.b.length;
+	auto pe = &aa.a.b[i];
+	while ((e = *pe) != null)
+	{
+	    if (key_hash == e.hash)
+	    {
+		auto c = keyti.compare(pkey, e + 1);
+		if (c == 0)
+		    goto Lret;
+		pe = (c < 0) ? &e.left : &e.right;
+	    }
+	    else
+		pe = (key_hash < e.hash) ? &e.left : &e.right;
+	}
+
+	// Not found, create new elem
+	//printf("create new one\n");
+	e = cast(aaA *) cast(void*) new void[aaA.sizeof + keysize + valuesize];
+	memcpy(e + 1, pkey, keysize);
+	e.hash = key_hash;
+	*pe = e;
+
+	auto nodes = ++aa.a.nodes;
+	//printf("length = %d, nodes = %d\n", (*aa.a).length, nodes);
+	if (nodes > aa.a.b.length * 4)
+	{
+	    _aaRehash(aa,keyti);
+	}
+
+    Lret:
+	return cast(void *)(e + 1) + keysize;
+    }
+
+
+/*************************************************
+ * Get pointer to value in associative array indexed by key.
+ * Returns null if it is not already there.
+ */
+
+void* _aaGetRvalue(AA aa, TypeInfo keyti, size_t valuesize, ...)
+    {
+	//printf("_aaGetRvalue(valuesize = %u)\n", valuesize);
+	if (!aa.a)
+	    return null;
+
+	auto pkey = cast(void *)(&valuesize + 1);
+	auto keysize = aligntsize(keyti.tsize());
+	auto len = aa.a.b.length;
+
+	if (len)
+	{
+	    auto key_hash = keyti.getHash(pkey);
+	    //printf("hash = %d\n", key_hash);
+	    size_t i = key_hash % len;
+	    auto e = aa.a.b[i];
+	    while (e != null)
+	    {
+		if (key_hash == e.hash)
+		{
+		    auto c = keyti.compare(pkey, e + 1);
+		    if (c == 0)
+			return cast(void *)(e + 1) + keysize;
+		    e = (c < 0) ? e.left : e.right;
+		}
+		else
+		    e = (key_hash < e.hash) ? e.left : e.right;
+	    }
+	}
+	return null;	// not found, caller will throw exception
+    }
+
+
+/*************************************************
+ * Determine if key is in aa.
+ * Returns:
+ *	null	not in aa
+ *	!=null	in aa, return pointer to value
+ */
+
+void* _aaIn(AA aa, TypeInfo keyti, ...)
+    in
+    {
+    }
+    out (result)
+    {
+	//assert(result == 0 || result == 1);
+    }
+    body
+    {
+	if (aa.a)
+	{
+	    auto pkey = cast(void *)(&keyti + 1);
+
+	    //printf("_aaIn(), .length = %d, .ptr = %x\n", aa.a.length, cast(uint)aa.a.ptr);
+	    auto len = aa.a.b.length;
+
+	    if (len)
+	    {
+		auto key_hash = keyti.getHash(pkey);
+		//printf("hash = %d\n", key_hash);
+		size_t i = key_hash % len;
+		auto e = aa.a.b[i];
+		while (e != null)
+		{
+		    if (key_hash == e.hash)
+		    {
+			auto c = keyti.compare(pkey, e + 1);
+			if (c == 0)
+			    return cast(void *)(e + 1) + aligntsize(keyti.tsize());
+			e = (c < 0) ? e.left : e.right;
+		    }
+		    else
+			e = (key_hash < e.hash) ? e.left : e.right;
+		}
+	    }
+	}
+
+	// Not found
+	return null;
+    }
+
+
+/*************************************************
+ * Delete key entry in aa[].
+ * If key is not in aa[], do nothing.
+ */
+
+void _aaDel(AA aa, TypeInfo keyti, ...)
+    {
+	auto pkey = cast(void *)(&keyti + 1);
+	aaA* e;
+
+	if (aa.a && aa.a.b.length)
+	{
+	    auto key_hash = keyti.getHash(pkey);
+	    //printf("hash = %d\n", key_hash);
+	    size_t i = key_hash % aa.a.b.length;
+	    auto pe = &aa.a.b[i];
+	    while ((e = *pe) != null)	// null means not found
+	    {
+		if (key_hash == e.hash)
+		{
+		    auto c = keyti.compare(pkey, e + 1);
+		    if (c == 0)
+		    {
+			if (!e.left && !e.right)
+			{
+			    *pe = null;
+			}
+			else if (e.left && !e.right)
+			{
+			    *pe = e.left;
+			     e.left = null;
+			}
+			else if (!e.left && e.right)
+			{
+			    *pe = e.right;
+			     e.right = null;
+			}
+			else
+			{
+			    *pe = e.left;
+			    e.left = null;
+			    do
+				pe = &(*pe).right;
+			    while (*pe);
+			    *pe = e.right;
+			    e.right = null;
+			}
+
+			aa.a.nodes--;
+
+			// Should notify GC that e can be free'd now
+			break;
+		    }
+		    pe = (c < 0) ? &e.left : &e.right;
+		}
+		else
+		    pe = (key_hash < e.hash) ? &e.left : &e.right;
+	    }
+	}
+    }
+
+
+/********************************************
+ * Produce array of values from aa.
+ */
+
+ArrayRet_t _aaValues(AA aa, size_t keysize, size_t valuesize)
+    in
+    {
+	assert(keysize == aligntsize(keysize));
+    }
+    body
+    {
+	size_t resi;
+	Array a;
+
+	void _aaValues_x(aaA* e)
+	{
+	    do
+	    {
+		memcpy(a.ptr + resi * valuesize,
+		       cast(byte*)e + aaA.sizeof + keysize,
+		       valuesize);
+		resi++;
+		if (e.left)
+		{   if (!e.right)
+		    {	e = e.left;
+			continue;
+		    }
+		    _aaValues_x(e.left);
+		}
+		e = e.right;
+	    } while (e != null);
+	}
+
+	if (aa.a)
+	{
+	    a.length = _aaLen(aa);
+	    a.ptr = (new void[a.length * valuesize]).ptr;
+	    resi = 0;
+	    foreach (e; aa.a.b)
+	    {
+		if (e)
+		    _aaValues_x(e);
+	    }
+	    assert(resi == a.length);
+	}
+	return *cast(ArrayRet_t*)(&a);
+    }
+
+
+/********************************************
+ * Rehash an array.
+ */
+
+void* _aaRehash(AA* paa, TypeInfo keyti)
+    in
+    {
+	//_aaInvAh(paa);
+    }
+    out (result)
+    {
+	//_aaInvAh(result);
+    }
+    body
+    {
+	BB newb;
+
+	void _aaRehash_x(aaA* olde)
+	{
+	    while (1)
+	    {
+		auto left = olde.left;
+		auto right = olde.right;
+		olde.left = null;
+		olde.right = null;
+
+		aaA* e;
+
+		//printf("rehash %p\n", olde);
+		auto key_hash = olde.hash;
+		size_t i = key_hash % newb.b.length;
+		auto pe = &newb.b[i];
+		while ((e = *pe) != null)
+		{
+		    //printf("\te = %p, e.left = %p, e.right = %p\n", e, e.left, e.right);
+		    assert(e.left != e);
+		    assert(e.right != e);
+		    if (key_hash == e.hash)
+		    {
+			auto c = keyti.compare(olde + 1, e + 1);
+			assert(c != 0);
+			pe = (c < 0) ? &e.left : &e.right;
+		    }
+		    else
+			pe = (key_hash < e.hash) ? &e.left : &e.right;
+		}
+		*pe = olde;
+
+		if (right)
+		{
+		    if (!left)
+		    {	olde = right;
+			continue;
+		    }
+		    _aaRehash_x(right);
+		}
+		if (!left)
+		    break;
+		olde = left;
+	    }
+	}
+
+	//printf("Rehash\n");
+	if (paa.a)
+	{
+	    auto aa = paa.a;
+	    auto len = _aaLen(*paa);
+	    if (len)
+	    {   size_t i;
+
+		for (i = 0; i < prime_list.length - 1; i++)
+		{
+		    if (len <= prime_list[i])
+			break;
+		}
+		len = prime_list[i];
+		newb.b = new aaA*[len];
+
+		foreach (e; aa.b)
+		{
+		    if (e)
+			_aaRehash_x(e);
+		}
+
+		newb.nodes = aa.nodes;
+	    }
+
+	    *paa.a = newb;
+	}
+	return (*paa).a;
+    }
+
+
+/********************************************
+ * Produce array of N byte keys from aa.
+ */
+
+ArrayRet_t _aaKeys(AA aa, size_t keysize)
+    {
+	byte[] res;
+	size_t resi;
+
+	void _aaKeys_x(aaA* e)
+	{
+	    do
+	    {
+		memcpy(&res[resi * keysize], cast(byte*)(e + 1), keysize);
+		resi++;
+		if (e.left)
+		{   if (!e.right)
+		    {	e = e.left;
+			continue;
+		    }
+		    _aaKeys_x(e.left);
+		}
+		e = e.right;
+	    } while (e != null);
+	}
+
+	auto len = _aaLen(aa);
+	if (!len)
+	    return 0;
+	res = cast(byte[])new void[len * keysize];
+	resi = 0;
+	foreach (e; aa.a.b)
+	{
+	    if (e)
+		_aaKeys_x(e);
+	}
+	assert(resi == len);
+
+	Array a;
+	a.length = len;
+	a.ptr = res.ptr;
+	return *cast(ArrayRet_t*)(&a);
+    }
+
+
+/**********************************************
+ * 'apply' for associative arrays - to support foreach
+ */
+
+// dg is D, but _aaApply() is C
+extern (D) typedef int delegate(void *) dg_t;
+
+int _aaApply(AA aa, size_t keysize, dg_t dg)
+in
+{
+    assert(aligntsize(keysize) == keysize);
+}
+body
+{   int result;
+
+    //printf("_aaApply(aa = x%llx, keysize = %d, dg = x%llx)\n", aa.a, keysize, dg);
+
+    int treewalker(aaA* e)
+    {	int result;
+
+	do
+	{
+	    //printf("treewalker(e = %p, dg = x%llx)\n", e, dg);
+	    result = dg(cast(void *)(e + 1) + keysize);
+	    if (result)
+		break;
+	    if (e.right)
+	    {	if (!e.left)
+		{
+		    e = e.right;
+		    continue;
+		}
+		result = treewalker(e.right);
+		if (result)
+		    break;
+	    }
+	    e = e.left;
+	} while (e);
+
+	return result;
+    }
+
+    if (aa.a)
+    {
+	foreach (e; aa.a.b)
+	{
+	    if (e)
+	    {
+		result = treewalker(e);
+		if (result)
+		    break;
+	    }
+	}
+    }
+    return result;
+}
+
+// dg is D, but _aaApply2() is C
+extern (D) typedef int delegate(void *, void *) dg2_t;
+
+int _aaApply2(AA aa, size_t keysize, dg2_t dg)
+in
+{
+    assert(aligntsize(keysize) == keysize);
+}
+body
+{   int result;
+
+    //printf("_aaApply(aa = x%llx, keysize = %d, dg = x%llx)\n", aa.a, keysize, dg);
+
+    int treewalker(aaA* e)
+    {	int result;
+
+	do
+	{
+	    //printf("treewalker(e = %p, dg = x%llx)\n", e, dg);
+	    result = dg(cast(void *)(e + 1), cast(void *)(e + 1) + keysize);
+	    if (result)
+		break;
+	    if (e.right)
+	    {	if (!e.left)
+		{
+		    e = e.right;
+		    continue;
+		}
+		result = treewalker(e.right);
+		if (result)
+		    break;
+	    }
+	    e = e.left;
+	} while (e);
+
+	return result;
+    }
+
+    if (aa.a)
+    {
+	foreach (e; aa.a.b)
+	{
+	    if (e)
+	    {
+		result = treewalker(e);
+		if (result)
+		    break;
+	    }
+	}
+    }
+    return result;
+}
+
+
+/***********************************
+ * Construct an associative array of type ti from
+ * length pairs of key/value pairs.
+ */
+
+extern (C)
+BB* _d_assocarrayliteralT(TypeInfo_AssociativeArray ti, size_t length, ...)
+{
+    auto valuesize = ti.next.tsize();		// value size
+    auto keyti = ti.key;
+    auto keysize = keyti.tsize();		// key size
+    BB* result;
+
+    //printf("_d_assocarrayliteralT(keysize = %d, valuesize = %d, length = %d)\n", keysize, valuesize, length);
+    //writefln("tivalue = %s", ti.next.classinfo.name);
+    if (length == 0 || valuesize == 0 || keysize == 0)
+    {
+	;
+    }
+    else
+    {
+	va_list q;
+	va_start!(size_t)(q, length);
+
+	result = new BB();
+	size_t i;
+
+	for (i = 0; i < prime_list.length - 1; i++)
+	{
+	    if (length <= prime_list[i])
+		break;
+	}
+	auto len = prime_list[i];
+	result.b = new aaA*[len];
+
+	size_t keystacksize   = (keysize   + int.sizeof - 1) & ~(int.sizeof - 1);
+	size_t valuestacksize = (valuesize + int.sizeof - 1) & ~(int.sizeof - 1);
+
+	size_t keytsize = aligntsize(keysize);
+
+	for (size_t j = 0; j < length; j++)
+	{   void* pkey = q;
+	    q += keystacksize;
+	    void* pvalue = q;
+	    q += valuestacksize;
+	    aaA* e;
+
+	    auto key_hash = keyti.getHash(pkey);
+	    //printf("hash = %d\n", key_hash);
+	    i = key_hash % len;
+	    auto pe = &result.b[i];
+	    while (1)
+	    {
+		e = *pe;
+		if (!e)
+		{
+		    // Not found, create new elem
+		    //printf("create new one\n");
+		    e = cast(aaA *) cast(void*) new void[aaA.sizeof + keytsize + valuesize];
+		    memcpy(e + 1, pkey, keysize);
+		    e.hash = key_hash;
+		    *pe = e;
+		    result.nodes++;
+		    break;
+		}
+		if (key_hash == e.hash)
+		{
+		    auto c = keyti.compare(pkey, e + 1);
+		    if (c == 0)
+			break;
+		    pe = (c < 0) ? &e.left : &e.right;
+		}
+		else
+		    pe = (key_hash < e.hash) ? &e.left : &e.right;
+	    }
+	    memcpy(cast(void *)(e + 1) + keytsize, pvalue, valuesize);
+	}
+
+	va_end(q);
+    }
+    return result;
+}
+
+
--- a/lphobos/phobos.d	Tue Nov 20 00:02:35 2007 +0100
+++ b/lphobos/phobos.d	Tue Nov 20 05:29:20 2007 +0100
@@ -4,6 +4,7 @@
 std.array,
 std.ctype,
 std.intrinsic,
+std.math,
 std.moduleinit,
 std.outofmemory,
 std.stdint,
@@ -12,6 +13,9 @@
 std.uni,
 std.utf,
 
+//std.format,
+//std.string,
+
 std.c.fenv,
 std.c.locale,
 std.c.math,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lphobos/std/format.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,1563 @@
+
+// Written in the D programming language.
+
+/**
+ * This module implements the workhorse functionality for string and I/O formatting.
+ * It's comparable to C99's vsprintf().
+ *
+ * Macros:
+ *	WIKI = Phobos/StdFormat
+ */
+
+/*
+ *  Copyright (C) 2004-2006 by Digital Mars, www.digitalmars.com
+ *  Written by Walter Bright
+ *  Modified for LLVMDC by Tomas Lindquist Olsen
+ *
+ *  This software is provided 'as-is', without any express or implied
+ *  warranty. In no event will the authors be held liable for any damages
+ *  arising from the use of this software.
+ *
+ *  Permission is granted to anyone to use this software for any purpose,
+ *  including commercial applications, and to alter it and redistribute it
+ *  freely, subject to the following restrictions:
+ *
+ *  o  The origin of this software must not be misrepresented; you must not
+ *     claim that you wrote the original software. If you use this software
+ *     in a product, an acknowledgment in the product documentation would be
+ *     appreciated but is not required.
+ *  o  Altered source versions must be plainly marked as such, and must not
+ *     be misrepresented as being the original software.
+ *  o  This notice may not be removed or altered from any source
+ *     distribution.
+ */
+
+module std.format;
+
+//debug=format;		// uncomment to turn on debugging printf's
+
+import std.stdarg;	// caller will need va_list
+
+private import std.utf;
+private import std.c.stdlib;
+private import std.c.string;
+private import std.string;
+
+version (Windows)
+{
+    version (DigitalMars)
+    {
+	version = DigitalMarsC;
+    }
+}
+
+version (DigitalMarsC)
+{
+    // This is DMC's internal floating point formatting function
+    extern (C)
+    {
+	extern char* function(int c, int flags, int precision, real* pdval,
+	    char* buf, int* psl, int width) __pfloatfmt;
+    }
+}
+else
+{
+    // Use C99 snprintf
+    extern (C) int snprintf(char* s, size_t n, char* format, ...);
+}
+
+/**********************************************************************
+ * Signals a mismatch between a format and its corresponding argument.
+ */
+class FormatError : Error
+{
+  private:
+
+    this()
+    {
+	super("std.format");
+    }
+
+    this(char[] msg)
+    {
+	super("std.format " ~ msg);
+    }
+}
+
+
+enum Mangle : char
+{
+    Tvoid     = 'v',
+    Tbool     = 'b',
+    Tbyte     = 'g',
+    Tubyte    = 'h',
+    Tshort    = 's',
+    Tushort   = 't',
+    Tint      = 'i',
+    Tuint     = 'k',
+    Tlong     = 'l',
+    Tulong    = 'm',
+    Tfloat    = 'f',
+    Tdouble   = 'd',
+    Treal     = 'e',
+
+    Tifloat   = 'o',
+    Tidouble  = 'p',
+    Tireal    = 'j',
+    Tcfloat   = 'q',
+    Tcdouble  = 'r',
+    Tcreal    = 'c',
+
+    Tchar     = 'a',
+    Twchar    = 'u',
+    Tdchar    = 'w',
+
+    Tarray    = 'A',
+    Tsarray   = 'G',
+    Taarray   = 'H',
+    Tpointer  = 'P',
+    Tfunction = 'F',
+    Tident    = 'I',
+    Tclass    = 'C',
+    Tstruct   = 'S',
+    Tenum     = 'E',
+    Ttypedef  = 'T',
+    Tdelegate = 'D',
+
+    Tconst    = 'x',
+    Tinvariant = 'y',
+}
+
+// return the TypeInfo for a primitive type and null otherwise.
+// This is required since for arrays of ints we only have the mangled
+// char to work from. If arrays always subclassed TypeInfo_Array this
+// routine could go away.
+private TypeInfo primitiveTypeInfo(Mangle m) 
+{
+  TypeInfo ti;
+
+  switch (m)
+    {
+    case Mangle.Tvoid:
+      ti = typeid(void);break;
+    case Mangle.Tbool:
+      ti = typeid(bool);break;
+    case Mangle.Tbyte:
+      ti = typeid(byte);break;
+    case Mangle.Tubyte:
+      ti = typeid(ubyte);break;
+    case Mangle.Tshort:
+      ti = typeid(short);break;
+    case Mangle.Tushort:
+      ti = typeid(ushort);break;
+    case Mangle.Tint:
+      ti = typeid(int);break;
+    case Mangle.Tuint:
+      ti = typeid(uint);break;
+    case Mangle.Tlong:
+      ti = typeid(long);break;
+    case Mangle.Tulong:
+      ti = typeid(ulong);break;
+    case Mangle.Tfloat:
+      ti = typeid(float);break;
+    case Mangle.Tdouble:
+      ti = typeid(double);break;
+    case Mangle.Treal:
+      ti = typeid(real);break;
+    case Mangle.Tifloat:
+      ti = typeid(ifloat);break;
+    case Mangle.Tidouble:
+      ti = typeid(idouble);break;
+    case Mangle.Tireal:
+      ti = typeid(ireal);break;
+    /+
+    // No complex in LLVMDC yes
+    case Mangle.Tcfloat:
+      ti = typeid(cfloat);break;
+    case Mangle.Tcdouble:
+      ti = typeid(cdouble);break;
+    case Mangle.Tcreal:
+      ti = typeid(creal);break;
+    +/
+    case Mangle.Tchar:
+      ti = typeid(char);break;
+    case Mangle.Twchar:
+      ti = typeid(wchar);break;
+    case Mangle.Tdchar:
+      ti = typeid(dchar);
+    default:
+      ti = null;
+    }
+  return ti;
+}
+
+/************************************
+ * Interprets variadic argument list pointed to by argptr whose types are given
+ * by arguments[], formats them according to embedded format strings in the
+ * variadic argument list, and sends the resulting characters to putc.
+ *
+ * The variadic arguments are consumed in order.
+ * Each is formatted into a sequence of chars, using the default format
+ * specification for its type, and the
+ * characters are sequentially passed to putc.
+ * If a char[], wchar[], or dchar[]
+ * argument is encountered, it is interpreted as a format string. As many
+ * arguments as specified in the format string are consumed and formatted
+ * according to the format specifications in that string and passed to putc. If
+ * there are too few remaining arguments, a FormatError is thrown. If there are
+ * more remaining arguments than needed by the format specification, the default
+ * processing of arguments resumes until they are all consumed.
+ *
+ * Params:
+ *	putc =	Output is sent do this delegate, character by character.
+ *	arguments = Array of TypeInfo's, one for each argument to be formatted.
+ *	argptr = Points to variadic argument list.
+ *
+ * Throws:
+ *	Mismatched arguments and formats result in a FormatError being thrown.
+ *
+ * Format_String:
+ *	<a name="format-string">$(I Format strings)</a>
+ *	consist of characters interspersed with
+ *	$(I format specifications). Characters are simply copied
+ *	to the output (such as putc) after any necessary conversion
+ *	to the corresponding UTF-8 sequence.
+ *
+ *	A $(I format specification) starts with a '%' character,
+ *	and has the following grammar:
+
+<pre>
+$(I FormatSpecification):
+    $(B '%%')
+    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
+
+$(I Flags):
+    $(I empty)
+    $(B '-') $(I Flags)
+    $(B '+') $(I Flags)
+    $(B '#') $(I Flags)
+    $(B '0') $(I Flags)
+    $(B ' ') $(I Flags)
+
+$(I Width):
+    $(I empty)
+    $(I Integer)
+    $(B '*')
+
+$(I Precision):
+    $(I empty)
+    $(B '.')
+    $(B '.') $(I Integer)
+    $(B '.*')
+
+$(I Integer):
+    $(I Digit)
+    $(I Digit) $(I Integer)
+
+$(I Digit):
+    $(B '0')
+    $(B '1')
+    $(B '2')
+    $(B '3')
+    $(B '4')
+    $(B '5')
+    $(B '6')
+    $(B '7')
+    $(B '8')
+    $(B '9')
+
+$(I FormatChar):
+    $(B 's')
+    $(B 'b')
+    $(B 'd')
+    $(B 'o')
+    $(B 'x')
+    $(B 'X')
+    $(B 'e')
+    $(B 'E')
+    $(B 'f')
+    $(B 'F')
+    $(B 'g')
+    $(B 'G')
+    $(B 'a')
+    $(B 'A')
+</pre>
+    <dl>
+    <dt>$(I Flags)
+    <dl>
+	<dt>$(B '-')
+	<dd>
+	Left justify the result in the field.
+	It overrides any $(B 0) flag.
+
+	<dt>$(B '+')
+	<dd>Prefix positive numbers in a signed conversion with a $(B +).
+	It overrides any $(I space) flag.
+
+	<dt>$(B '#')
+	<dd>Use alternative formatting:
+	<dl>
+	    <dt>For $(B 'o'):
+	    <dd> Add to precision as necessary so that the first digit
+	    of the octal formatting is a '0', even if both the argument
+	    and the $(I Precision) are zero.
+	    <dt> For $(B 'x') ($(B 'X')):
+	    <dd> If non-zero, prefix result with $(B 0x) ($(B 0X)).
+	    <dt> For floating point formatting:
+	    <dd> Always insert the decimal point.
+	    <dt> For $(B 'g') ($(B 'G')):
+	    <dd> Do not elide trailing zeros.
+	</dl>
+
+	<dt>$(B '0')
+	<dd> For integer and floating point formatting when not nan or
+	infinity, use leading zeros
+	to pad rather than spaces.
+	Ignore if there's a $(I Precision).
+
+	<dt>$(B ' ')
+	<dd>Prefix positive numbers in a signed conversion with a space.
+    </dl>
+
+    <dt>$(I Width)
+    <dd>
+    Specifies the minimum field width.
+    If the width is a $(B *), the next argument, which must be
+    of type $(B int), is taken as the width.
+    If the width is negative, it is as if the $(B -) was given
+    as a $(I Flags) character.
+
+    <dt>$(I Precision)
+    <dd> Gives the precision for numeric conversions.
+    If the precision is a $(B *), the next argument, which must be
+    of type $(B int), is taken as the precision. If it is negative,
+    it is as if there was no $(I Precision).
+
+    <dt>$(I FormatChar)
+    <dd>
+    <dl>
+	<dt>$(B 's')
+	<dd>The corresponding argument is formatted in a manner consistent
+	with its type:
+	<dl>
+	    <dt>$(B bool)
+	    <dd>The result is <tt>'true'</tt> or <tt>'false'</tt>.
+	    <dt>integral types
+	    <dd>The $(B %d) format is used.
+	    <dt>floating point types
+	    <dd>The $(B %g) format is used.
+	    <dt>string types
+	    <dd>The result is the string converted to UTF-8.
+	    A $(I Precision) specifies the maximum number of characters
+	    to use in the result.
+	    <dt>classes derived from $(B Object)
+	    <dd>The result is the string returned from the class instance's
+	    $(B .toString()) method.
+	    A $(I Precision) specifies the maximum number of characters
+	    to use in the result.
+	    <dt>non-string static and dynamic arrays
+	    <dd>The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
+	    where s<sub>k</sub> is the kth element 
+	    formatted with the default format.
+	</dl>
+
+	<dt>$(B 'b','d','o','x','X')
+	<dd> The corresponding argument must be an integral type
+	and is formatted as an integer. If the argument is a signed type
+	and the $(I FormatChar) is $(B d) it is converted to
+	a signed string of characters, otherwise it is treated as
+	unsigned. An argument of type $(B bool) is formatted as '1'
+	or '0'. The base used is binary for $(B b), octal for $(B o),
+	decimal
+	for $(B d), and hexadecimal for $(B x) or $(B X).
+	$(B x) formats using lower case letters, $(B X) uppercase.
+	If there are fewer resulting digits than the $(I Precision),
+	leading zeros are used as necessary.
+	If the $(I Precision) is 0 and the number is 0, no digits
+	result.
+
+	<dt>$(B 'e','E')
+	<dd> A floating point number is formatted as one digit before
+	the decimal point, $(I Precision) digits after, the $(I FormatChar),
+	&plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
+	If there is no $(I Precision), six
+	digits are generated after the decimal point.
+	If the $(I Precision) is 0, no decimal point is generated.
+
+	<dt>$(B 'f','F')
+	<dd> A floating point number is formatted in decimal notation.
+	The $(I Precision) specifies the number of digits generated
+	after the decimal point. It defaults to six. At least one digit
+	is generated before the decimal point. If the $(I Precision)
+	is zero, no decimal point is generated.
+
+	<dt>$(B 'g','G')
+	<dd> A floating point number is formatted in either $(B e) or
+	$(B f) format for $(B g); $(B E) or $(B F) format for
+	$(B G).
+	The $(B f) format is used if the exponent for an $(B e) format
+	is greater than -5 and less than the $(I Precision).
+	The $(I Precision) specifies the number of significant
+	digits, and defaults to six.
+	Trailing zeros are elided after the decimal point, if the fractional
+	part is zero then no decimal point is generated.
+
+	<dt>$(B 'a','A')
+	<dd> A floating point number is formatted in hexadecimal
+	exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
+	There is one hexadecimal digit before the decimal point, and as
+	many after as specified by the $(I Precision).
+	If the $(I Precision) is zero, no decimal point is generated.
+	If there is no $(I Precision), as many hexadecimal digits as
+	necessary to exactly represent the mantissa are generated.
+	The exponent is written in as few digits as possible,
+	but at least one, is in decimal, and represents a power of 2 as in
+	$(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
+	The exponent for zero is zero.
+	The hexadecimal digits, x and p are in upper case if the
+	$(I FormatChar) is upper case.
+    </dl>
+
+    Floating point NaN's are formatted as $(B nan) if the
+    $(I FormatChar) is lower case, or $(B NAN) if upper.
+    Floating point infinities are formatted as $(B inf) or
+    $(B infinity) if the
+    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
+    </dl>
+
+Example:
+
+-------------------------
+import std.c.stdio;
+import std.format;
+
+void formattedPrint(...)
+{
+    void putc(char c)
+    {
+	fputc(c, stdout);
+    }
+
+    std.format.doFormat(&putc, _arguments, _argptr);
+}
+
+...
+
+int x = 27;
+// prints 'The answer is 27:6'
+formattedPrint("The answer is %s:", x, 6);
+------------------------
+ */
+
+void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr)
+{   int j;
+    TypeInfo ti;
+    Mangle m;
+    uint flags;
+    int field_width;
+    int precision;
+
+    enum : uint
+    {
+	FLdash = 1,
+	FLplus = 2,
+	FLspace = 4,
+	FLhash = 8,
+	FLlngdbl = 0x20,
+	FL0pad = 0x40,
+	FLprecision = 0x80,
+    }
+
+    static TypeInfo skipCI(TypeInfo valti)
+    {
+      while (1)
+      {
+	if (valti.classinfo.name.length == 18 &&
+	    valti.classinfo.name[9..18] == "Invariant")
+	    valti =	(cast(TypeInfo_Invariant)valti).next;
+	else if (valti.classinfo.name.length == 14 &&
+	    valti.classinfo.name[9..14] == "Const")
+	    valti =	(cast(TypeInfo_Const)valti).next;
+	else
+	    break;
+      }
+      return valti;
+    }
+
+    void formatArg(char fc)
+    {
+	bool vbit;
+	ulong vnumber;
+	char vchar;
+	dchar vdchar;
+	Object vobject;
+	real vreal;
+	creal vcreal;
+	Mangle m2;
+	int signed = 0;
+	uint base = 10;
+	int uc;
+	char[ulong.sizeof * 8] tmpbuf;	// long enough to print long in binary
+	char* prefix = "";
+	string s;
+
+	void putstr(char[] s)
+	{
+	    //printf("flags = x%x\n", flags);
+	    int prepad = 0;
+	    int postpad = 0;
+	    int padding = field_width - (strlen(prefix) + s.length);
+	    if (padding > 0)
+	    {
+		if (flags & FLdash)
+		    postpad = padding;
+		else
+		    prepad = padding;
+	    }
+
+	    if (flags & FL0pad)
+	    {
+		while (*prefix)
+		    putc(*prefix++);
+		while (prepad--)
+		    putc('0');
+	    }
+	    else
+	    {
+		while (prepad--)
+		    putc(' ');
+		while (*prefix)
+		    putc(*prefix++);
+	    }
+
+	    foreach (dchar c; s)
+		putc(c);
+
+	    while (postpad--)
+		putc(' ');
+	}
+
+	void putreal(real v)
+	{
+	    //printf("putreal %Lg\n", vreal);
+
+	    switch (fc)
+	    {
+		case 's':
+		    fc = 'g';
+		    break;
+
+		case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
+		    break;
+
+		default:
+		    //printf("fc = '%c'\n", fc);
+		Lerror:
+		    throw new FormatError("floating");
+	    }
+	    version (DigitalMarsC)
+	    {
+		int sl;
+		char[] fbuf = tmpbuf;
+		if (!(flags & FLprecision))
+		    precision = 6;
+		while (1)
+		{
+		    sl = fbuf.length;
+		    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
+			    precision, &v, cast(char*)fbuf, &sl, field_width);
+		    if (sl != -1)
+			break;
+		    sl = fbuf.length * 2;
+		    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+		}
+		putstr(fbuf[0 .. sl]);
+	    }
+	    else
+	    {
+		int sl;
+		char[] fbuf = tmpbuf;
+		char[12] format;
+		format[0] = '%';
+		int i = 1;
+		if (flags & FLdash)
+		    format[i++] = '-';
+		if (flags & FLplus)
+		    format[i++] = '+';
+		if (flags & FLspace)
+		    format[i++] = ' ';
+		if (flags & FLhash)
+		    format[i++] = '#';
+		if (flags & FL0pad)
+		    format[i++] = '0';
+		format[i + 0] = '*';
+		format[i + 1] = '.';
+		format[i + 2] = '*';
+		format[i + 3] = 'L';
+		format[i + 4] = fc;
+		format[i + 5] = 0;
+		if (!(flags & FLprecision))
+		    precision = -1;
+		while (1)
+		{   int n;
+
+		    sl = fbuf.length;
+		    n = snprintf(fbuf.ptr, sl, format.ptr, field_width, precision, v);
+		    //printf("format = '%s', n = %d\n", cast(char*)format, n);
+		    if (n >= 0 && n < sl)
+		    {	sl = n;
+			break;
+		    }
+		    if (n < 0)
+			sl = sl * 2;
+		    else
+			sl = n + 1;
+		    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+		}
+		putstr(fbuf[0 .. sl]);
+	    }
+	    return;
+	}
+
+	static Mangle getMan(TypeInfo ti)
+	{
+	  auto m = cast(Mangle)ti.classinfo.name[9];
+	  if (ti.classinfo.name.length == 20 &&
+	      ti.classinfo.name[9..20] == "StaticArray")
+		m = cast(Mangle)'G';
+	  return m;
+	}
+
+	void putArray(void* p, size_t len, TypeInfo valti)
+	{
+	  //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize());
+	  putc('[');
+	  valti = skipCI(valti);
+	  size_t tsize = valti.tsize();
+	  auto argptrSave = argptr;
+	  auto tiSave = ti;
+	  auto mSave = m;
+	  ti = valti;
+	  //printf("\n%.*s\n", valti.classinfo.name);
+	  m = getMan(valti);
+	  while (len--)
+	  {
+	    //doFormat(putc, (&valti)[0 .. 1], p);
+	    argptr = p;
+	    formatArg('s');
+
+	    p += tsize;
+	    if (len > 0) putc(',');
+	  }
+	  m = mSave;
+	  ti = tiSave;
+	  argptr = argptrSave;
+	  putc(']');
+	}
+
+	void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
+	{
+	  putc('[');
+	  bool comma=false;
+	  auto argptrSave = argptr;
+	  auto tiSave = ti;
+	  auto mSave = m;
+	  valti = skipCI(valti);
+	  keyti = skipCI(keyti);
+	  foreach(inout fakevalue; vaa)
+	  {
+	    if (comma) putc(',');
+	    comma = true;
+	    // the key comes before the value
+	    ubyte* key = &fakevalue - long.sizeof;
+
+	    //doFormat(putc, (&keyti)[0..1], key);
+	    argptr = key;
+	    ti = keyti;
+	    m = getMan(keyti);
+	    formatArg('s');
+
+	    putc(':');
+	    auto keysize = keyti.tsize;
+	    keysize = (keysize + 3) & ~3;
+	    ubyte* value = key + keysize;
+	    //doFormat(putc, (&valti)[0..1], value);
+	    argptr = value;
+	    ti = valti;
+	    m = getMan(valti);
+	    formatArg('s');
+	  }
+	  m = mSave;
+	  ti = tiSave;
+	  argptr = argptrSave;
+	  putc(']');
+	}
+
+	//printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
+	switch (m)
+	{
+	    case Mangle.Tbool:
+		vbit = va_arg!(bool)(argptr);
+		if (fc != 's')
+		{   vnumber = vbit;
+		    goto Lnumber;
+		}
+		putstr(vbit ? "true" : "false");
+		return;
+
+
+	    case Mangle.Tchar:
+		vchar = va_arg!(char)(argptr);
+		if (fc != 's')
+		{   vnumber = vchar;
+		    goto Lnumber;
+		}
+	    L2:
+		putstr((&vchar)[0 .. 1]);
+		return;
+
+	    case Mangle.Twchar:
+		vdchar = va_arg!(wchar)(argptr);
+		goto L1;
+
+	    case Mangle.Tdchar:
+		vdchar = va_arg!(dchar)(argptr);
+	    L1:
+		if (fc != 's')
+		{   vnumber = vdchar;
+		    goto Lnumber;
+		}
+		if (vdchar <= 0x7F)
+		{   vchar = cast(char)vdchar;
+		    goto L2;
+		}
+		else
+		{   if (!isValidDchar(vdchar))
+			throw new UtfException("invalid dchar in format", 0);
+		    char[4] vbuf;
+		    putstr(toUTF8(vbuf, vdchar));
+		}
+		return;
+
+
+	    case Mangle.Tbyte:
+		signed = 1;
+		vnumber = va_arg!(byte)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tubyte:
+		vnumber = va_arg!(ubyte)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tshort:
+		signed = 1;
+		vnumber = va_arg!(short)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tushort:
+		vnumber = va_arg!(ushort)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tint:
+		signed = 1;
+		vnumber = va_arg!(int)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tuint:
+	    Luint:
+		vnumber = va_arg!(uint)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tlong:
+		signed = 1;
+		vnumber = cast(ulong)va_arg!(long)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tulong:
+	    Lulong:
+		vnumber = va_arg!(ulong)(argptr);
+		goto Lnumber;
+
+	    case Mangle.Tclass:
+		vobject = va_arg!(Object)(argptr);
+		if (vobject is null)
+		    s = "null";
+		else
+		    s = vobject.toString();
+		goto Lputstr;
+
+	    case Mangle.Tpointer:
+		vnumber = cast(ulong)va_arg!(void*)(argptr);
+		uc = 1;
+		flags |= FL0pad;
+		if (!(flags & FLprecision))
+		{   flags |= FLprecision;
+		    precision = (void*).sizeof;
+		}
+		base = 16;
+		goto Lnumber;
+
+
+	    case Mangle.Tfloat:
+	    case Mangle.Tifloat:
+		if (fc == 'x' || fc == 'X')
+		    goto Luint;
+		vreal = va_arg!(float)(argptr);
+		goto Lreal;
+
+	    case Mangle.Tdouble:
+	    case Mangle.Tidouble:
+		if (fc == 'x' || fc == 'X')
+		    goto Lulong;
+		vreal = va_arg!(double)(argptr);
+		goto Lreal;
+
+	    case Mangle.Treal:
+	    case Mangle.Tireal:
+		vreal = va_arg!(real)(argptr);
+		goto Lreal;
+
+
+	    case Mangle.Tcfloat:
+		vcreal = va_arg!(cfloat)(argptr);
+		goto Lcomplex;
+
+	    case Mangle.Tcdouble:
+		vcreal = va_arg!(cdouble)(argptr);
+		goto Lcomplex;
+
+	    case Mangle.Tcreal:
+		vcreal = va_arg!(creal)(argptr);
+		goto Lcomplex;
+
+	    case Mangle.Tsarray:
+		putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
+		return;
+
+	    case Mangle.Tarray:
+		int mi = 10;
+	        if (ti.classinfo.name.length == 14 &&
+		    ti.classinfo.name[9..14] == "Array") 
+		{ // array of non-primitive types
+		  TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+		  tn = skipCI(tn);
+		  switch (cast(Mangle)tn.classinfo.name[9])
+		  {
+		    case Mangle.Tchar:  goto LarrayChar;
+		    case Mangle.Twchar: goto LarrayWchar;
+		    case Mangle.Tdchar: goto LarrayDchar;
+		    default:
+			break;
+		  }
+		  void[] va = va_arg!(void[])(argptr);
+		  putArray(va.ptr, va.length, tn);
+		  return;
+		}
+		if (ti.classinfo.name.length == 25 &&
+		    ti.classinfo.name[9..25] == "AssociativeArray") 
+		{ // associative array
+		  ubyte[long] vaa = va_arg!(ubyte[long])(argptr);
+		  putAArray(vaa,
+			(cast(TypeInfo_AssociativeArray)ti).next,
+			(cast(TypeInfo_AssociativeArray)ti).key);
+		  return;
+		}
+
+		while (1)
+		{
+		    m2 = cast(Mangle)ti.classinfo.name[mi];
+		    switch (m2)
+		    {
+			case Mangle.Tchar:
+			LarrayChar:
+			    s = va_arg!(char[])(argptr);
+			    goto Lputstr;
+
+			case Mangle.Twchar:
+			LarrayWchar:
+			    wchar[] sw = va_arg!(wchar[])(argptr);
+			    s = toUTF8(sw);
+			    goto Lputstr;
+
+			case Mangle.Tdchar:
+			LarrayDchar:
+			    dchar[] sd = va_arg!(dchar[])(argptr);
+			    s = toUTF8(sd);
+			Lputstr:
+			    if (fc != 's')
+				throw new FormatError("string");
+			    if (flags & FLprecision && precision < s.length)
+				s = s[0 .. precision];
+			    putstr(s);
+			    break;
+
+			case Mangle.Tconst:
+			case Mangle.Tinvariant:
+			    mi++;
+			    continue;
+
+			default:
+			    TypeInfo ti2 = primitiveTypeInfo(m2);
+			    if (!ti2)
+			      goto Lerror;
+			    void[] va = va_arg!(void[])(argptr);
+			    putArray(va.ptr, va.length, ti2);
+		    }
+		    return;
+		}
+
+	    case Mangle.Ttypedef:
+		ti = (cast(TypeInfo_Typedef)ti).base;
+		m = cast(Mangle)ti.classinfo.name[9];
+		formatArg(fc);
+		return;
+
+	    case Mangle.Tenum:
+		ti = (cast(TypeInfo_Enum)ti).base;
+		m = cast(Mangle)ti.classinfo.name[9];
+		formatArg(fc);
+		return;
+
+	    case Mangle.Tstruct:
+	    {	TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
+		if (tis.xtoString is null)
+		    throw new FormatError("Can't convert " ~ tis.toString() ~ " to string: \"string toString()\" not defined");
+		s = tis.xtoString(argptr);
+		argptr += (tis.tsize() + 3) & ~3;
+		goto Lputstr;
+	    }
+
+	    default:
+		goto Lerror;
+	}
+
+    Lnumber:
+	switch (fc)
+	{
+	    case 's':
+	    case 'd':
+		if (signed)
+		{   if (cast(long)vnumber < 0)
+		    {	prefix = "-";
+			vnumber = -vnumber;
+		    }
+		    else if (flags & FLplus)
+			prefix = "+";
+		    else if (flags & FLspace)
+			prefix = " ";
+		}
+		break;
+
+	    case 'b':
+		signed = 0;
+		base = 2;
+		break;
+
+	    case 'o':
+		signed = 0;
+		base = 8;
+		break;
+
+	    case 'X':
+		uc = 1;
+		if (flags & FLhash && vnumber)
+		    prefix = "0X";
+		signed = 0;
+		base = 16;
+		break;
+
+	    case 'x':
+		if (flags & FLhash && vnumber)
+		    prefix = "0x";
+		signed = 0;
+		base = 16;
+		break;
+
+	    default:
+		goto Lerror;
+	}
+
+	if (!signed)
+	{
+	    switch (m)
+	    {
+		case Mangle.Tbyte:
+		    vnumber &= 0xFF;
+		    break;
+
+		case Mangle.Tshort:
+		    vnumber &= 0xFFFF;
+		    break;
+
+		case Mangle.Tint:
+		    vnumber &= 0xFFFFFFFF;
+		    break;
+
+		default:
+		    break;
+	    }
+	}
+
+	if (flags & FLprecision && fc != 'p')
+	    flags &= ~FL0pad;
+
+	if (vnumber < base)
+	{
+	    if (vnumber == 0 && precision == 0 && flags & FLprecision &&
+		!(fc == 'o' && flags & FLhash))
+	    {
+		putstr(null);
+		return;
+	    }
+	    if (precision == 0 || !(flags & FLprecision))
+	    {	vchar = cast(char)('0' + vnumber);
+		if (vnumber < 10)
+		    vchar = cast(char)('0' + vnumber);
+		else
+		    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
+		goto L2;
+	    }
+	}
+
+	int n = tmpbuf.length;
+	char c;
+	int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
+
+	while (vnumber)
+	{
+	    c = cast(char)((vnumber % base) + '0');
+	    if (c > '9')
+		c += hexoffset;
+	    vnumber /= base;
+	    tmpbuf[--n] = c;
+	}
+	if (tmpbuf.length - n < precision && precision < tmpbuf.length)
+	{
+	    int m = tmpbuf.length - precision;
+	    tmpbuf[m .. n] = '0';
+	    n = m;
+	}
+	else if (flags & FLhash && fc == 'o')
+	    prefix = "0";
+	putstr(tmpbuf[n .. tmpbuf.length]);
+	return;
+
+    Lreal:
+	putreal(vreal);
+	return;
+
+    Lcomplex:
+	putreal(vcreal.re);
+	putc('+');
+	putreal(vcreal.im);
+	putc('i');
+	return;
+
+    Lerror:
+	throw new FormatError("formatArg");
+    }
+
+
+    for (j = 0; j < arguments.length; )
+    {	ti = arguments[j++];
+	//printf("test1: '%.*s' %d\n", ti.classinfo.name, ti.classinfo.name.length);
+	//ti.print();
+
+	flags = 0;
+	precision = 0;
+	field_width = 0;
+
+	ti = skipCI(ti);
+	int mi = 9;
+	do
+	{
+	    if (ti.classinfo.name.length <= mi)
+		goto Lerror;
+	    m = cast(Mangle)ti.classinfo.name[mi++];
+	} while (m == Mangle.Tconst || m == Mangle.Tinvariant);
+
+	if (m == Mangle.Tarray)
+	{
+	    if (ti.classinfo.name.length == 14 &&
+		ti.classinfo.name[9..14] == "Array") 
+	    {
+	      TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+	      tn = skipCI(tn);
+	      switch (cast(Mangle)tn.classinfo.name[9])
+	      {
+		case Mangle.Tchar:
+		case Mangle.Twchar:
+		case Mangle.Tdchar:
+		    ti = tn;
+		    mi = 9;
+		    break;
+		default:
+		    break;
+	      }
+	    }
+	L1:
+	    Mangle m2 = cast(Mangle)ti.classinfo.name[mi];
+	    string  fmt;			// format string
+	    wstring wfmt;
+	    dstring dfmt;
+
+	    /* For performance reasons, this code takes advantage of the
+	     * fact that most format strings will be ASCII, and that the
+	     * format specifiers are always ASCII. This means we only need
+	     * to deal with UTF in a couple of isolated spots.
+	     */
+
+	    switch (m2)
+	    {
+		case Mangle.Tchar:
+		    fmt = va_arg!(char[])(argptr);
+		    break;
+
+		case Mangle.Twchar:
+		    wfmt = va_arg!(wchar[])(argptr);
+		    fmt = toUTF8(wfmt);
+		    break;
+
+		case Mangle.Tdchar:
+		    dfmt = va_arg!(dchar[])(argptr);
+		    fmt = toUTF8(dfmt);
+		    break;
+
+		case Mangle.Tconst:
+		case Mangle.Tinvariant:
+		    mi++;
+		    goto L1;
+
+		default:
+		    formatArg('s');
+		    continue;
+	    }
+
+	    for (size_t i = 0; i < fmt.length; )
+	    {	dchar c = fmt[i++];
+
+		dchar getFmtChar()
+		{   // Valid format specifier characters will never be UTF
+		    if (i == fmt.length)
+			throw new FormatError("invalid specifier");
+		    return fmt[i++];
+		}
+
+		int getFmtInt()
+		{   int n;
+
+		    while (1)
+		    {
+			n = n * 10 + (c - '0');
+			if (n < 0)	// overflow
+			    throw new FormatError("int overflow");
+			c = getFmtChar();
+			if (c < '0' || c > '9')
+			    break;
+		    }
+		    return n;
+		}
+
+		int getFmtStar()
+		{   Mangle m;
+		    TypeInfo ti;
+
+		    if (j == arguments.length)
+			throw new FormatError("too few arguments");
+		    ti = arguments[j++];
+		    m = cast(Mangle)ti.classinfo.name[9];
+		    if (m != Mangle.Tint)
+			throw new FormatError("int argument expected");
+		    return va_arg!(int)(argptr);
+		}
+
+		if (c != '%')
+		{
+		    if (c > 0x7F)	// if UTF sequence
+		    {
+			i--;		// back up and decode UTF sequence
+			c = std.utf.decode(fmt, i);
+		    }
+		Lputc:
+		    putc(c);
+		    continue;
+		}
+
+		// Get flags {-+ #}
+		flags = 0;
+		while (1)
+		{
+		    c = getFmtChar();
+		    switch (c)
+		    {
+			case '-':	flags |= FLdash;	continue;
+			case '+':	flags |= FLplus;	continue;
+			case ' ':	flags |= FLspace;	continue;
+			case '#':	flags |= FLhash;	continue;
+			case '0':	flags |= FL0pad;	continue;
+
+			case '%':	if (flags == 0)
+					    goto Lputc;
+			default:	break;
+		    }
+		    break;
+		}
+
+		// Get field width
+		field_width = 0;
+		if (c == '*')
+		{
+		    field_width = getFmtStar();
+		    if (field_width < 0)
+		    {   flags |= FLdash;
+			field_width = -field_width;
+		    }
+
+		    c = getFmtChar();
+		}
+		else if (c >= '0' && c <= '9')
+		    field_width = getFmtInt();
+
+		if (flags & FLplus)
+		    flags &= ~FLspace;
+		if (flags & FLdash)
+		    flags &= ~FL0pad;
+
+		// Get precision
+		precision = 0;
+		if (c == '.')
+		{   flags |= FLprecision;
+		    //flags &= ~FL0pad;
+
+		    c = getFmtChar();
+		    if (c == '*')
+		    {
+			precision = getFmtStar();
+			if (precision < 0)
+			{   precision = 0;
+			    flags &= ~FLprecision;
+			}
+
+			c = getFmtChar();
+		    }
+		    else if (c >= '0' && c <= '9')
+			precision = getFmtInt();
+		}
+
+		if (j == arguments.length)
+		    goto Lerror;
+		ti = arguments[j++];
+		ti = skipCI(ti);
+		mi = 9;
+		do
+		{
+		    m = cast(Mangle)ti.classinfo.name[mi++];
+		} while (m == Mangle.Tconst || m == Mangle.Tinvariant);
+
+		if (c > 0x7F)		// if UTF sequence
+		    goto Lerror;	// format specifiers can't be UTF
+		formatArg(cast(char)c);
+	    }
+	}
+	else
+	{
+	    formatArg('s');
+	}
+    }
+    return;
+
+Lerror:
+    throw new FormatError();
+}
+
+/* ======================== Unit Tests ====================================== */
+
+unittest
+{
+    int i;
+    string s;
+
+    debug(format) printf("std.format.format.unittest\n");
+ 
+    s = std.string.format("hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
+    assert(s == "hello world! true 57 1000000000x foo");
+
+    s = std.string.format(1.67, " %A ", -1.28, float.nan);
+    /* The host C library is used to format floats.
+     * C99 doesn't specify what the hex digit before the decimal point
+     * is for %A.
+     */
+    version (linux)
+	assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
+    else
+	assert(s == "1.67 -0X1.47AE147AE147BP+0 nan");
+
+    s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "1234af AFAFAFAF");
+
+    s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "100100011010010101111 25753727657");
+
+    s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF);
+    assert(s == "1193135 2947526575");
+
+    s = std.string.format("%s", 1.2 + 3.4i);
+    assert(s == "1.2+3.4i");
+
+    s = std.string.format("%x %X", 1.32, 6.78f);
+    assert(s == "3ff51eb851eb851f 40D8F5C3");
+
+    s = std.string.format("%#06.*f",2,12.345);
+    assert(s == "012.35");
+
+    s = std.string.format("%#0*.*f",6,2,12.345);
+    assert(s == "012.35");
+
+    s = std.string.format("%7.4g:", 12.678);
+    assert(s == "  12.68:");
+
+    s = std.string.format("%7.4g:", 12.678L);
+    assert(s == "  12.68:");
+
+    s = std.string.format("%04f|%05d|%#05x|%#5x",-4.,-10,1,1);
+    assert(s == "-4.000000|-0010|0x001|  0x1");
+
+    i = -10;
+    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "-10|-10|-10|-10|-10.0000");
+
+    i = -5;
+    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "-5| -5|-05|-5|-5.0000");
+
+    i = 0;
+    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "0|  0|000|0|0.0000");
+
+    i = 5;
+    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "5|  5|005|5|5.0000");
+
+    i = 10;
+    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+    assert(s == "10| 10|010|10|10.0000");
+
+    s = std.string.format("%.0d", 0);
+    assert(s == "");
+
+    s = std.string.format("%.g", .34);
+    assert(s == "0.3");
+
+    s = std.string.format("%.0g", .34);
+    assert(s == "0.3");
+
+    s = std.string.format("%.2g", .34);
+    assert(s == "0.34");
+
+    s = std.string.format("%0.0008f", 1e-08);
+    assert(s == "0.00000001");
+
+    s = std.string.format("%0.0008f", 1e-05);
+    assert(s == "0.00001000");
+
+    s = "helloworld";
+    string r;
+    r = std.string.format("%.2s", s[0..5]);
+    assert(r == "he");
+    r = std.string.format("%.20s", s[0..5]);
+    assert(r == "hello");
+    r = std.string.format("%8s", s[0..5]);
+    assert(r == "   hello");
+
+    byte[] arrbyte = new byte[4];
+    arrbyte[0] = 100;
+    arrbyte[1] = -99;
+    arrbyte[3] = 0;
+    r = std.string.format(arrbyte);
+    assert(r == "[100,-99,0,0]");
+
+    ubyte[] arrubyte = new ubyte[4];
+    arrubyte[0] = 100;
+    arrubyte[1] = 200;
+    arrubyte[3] = 0;
+    r = std.string.format(arrubyte);
+    assert(r == "[100,200,0,0]");
+
+    short[] arrshort = new short[4];
+    arrshort[0] = 100;
+    arrshort[1] = -999;
+    arrshort[3] = 0;
+    r = std.string.format(arrshort);
+    assert(r == "[100,-999,0,0]");
+    r = std.string.format("%s",arrshort);
+    assert(r == "[100,-999,0,0]");
+
+    ushort[] arrushort = new ushort[4];
+    arrushort[0] = 100;
+    arrushort[1] = 20_000;
+    arrushort[3] = 0;
+    r = std.string.format(arrushort);
+    assert(r == "[100,20000,0,0]");
+
+    int[] arrint = new int[4];
+    arrint[0] = 100;
+    arrint[1] = -999;
+    arrint[3] = 0;
+    r = std.string.format(arrint);
+    assert(r == "[100,-999,0,0]");
+    r = std.string.format("%s",arrint);
+    assert(r == "[100,-999,0,0]");
+
+    long[] arrlong = new long[4];
+    arrlong[0] = 100;
+    arrlong[1] = -999;
+    arrlong[3] = 0;
+    r = std.string.format(arrlong);
+    assert(r == "[100,-999,0,0]");
+    r = std.string.format("%s",arrlong);
+    assert(r == "[100,-999,0,0]");
+
+    ulong[] arrulong = new ulong[4];
+    arrulong[0] = 100;
+    arrulong[1] = 999;
+    arrulong[3] = 0;
+    r = std.string.format(arrulong);
+    assert(r == "[100,999,0,0]");
+
+    string[] arr2 = new string[4];
+    arr2[0] = "hello";
+    arr2[1] = "world";
+    arr2[3] = "foo";
+    r = std.string.format(arr2);
+    assert(r == "[hello,world,,foo]");
+
+    r = std.string.format("%.8d", 7);
+    assert(r == "00000007");
+    r = std.string.format("%.8x", 10);
+    assert(r == "0000000a");
+
+    r = std.string.format("%-3d", 7);
+    assert(r == "7  ");
+
+    r = std.string.format("%*d", -3, 7);
+    assert(r == "7  ");
+
+    r = std.string.format("%.*d", -3, 7);
+    assert(r == "7");
+
+    typedef int myint;
+    myint m = -7;
+    r = std.string.format(m);
+    assert(r == "-7");
+
+    r = std.string.format("abc"c);
+    assert(r == "abc");
+    r = std.string.format("def"w);
+    assert(r == "def");
+    r = std.string.format("ghi"d);
+    assert(r == "ghi");
+
+    void* p = cast(void*)0xDEADBEEF;
+    r = std.string.format(p);
+    assert(r == "DEADBEEF");
+
+    r = std.string.format("%#x", 0xabcd);
+    assert(r == "0xabcd");
+    r = std.string.format("%#X", 0xABCD);
+    assert(r == "0XABCD");
+
+    r = std.string.format("%#o", 012345);
+    assert(r == "012345");
+    r = std.string.format("%o", 9);
+    assert(r == "11");
+
+    r = std.string.format("%+d", 123);
+    assert(r == "+123");
+    r = std.string.format("%+d", -123);
+    assert(r == "-123");
+    r = std.string.format("% d", 123);
+    assert(r == " 123");
+    r = std.string.format("% d", -123);
+    assert(r == "-123");
+
+    r = std.string.format("%%");
+    assert(r == "%");
+
+    r = std.string.format("%d", true);
+    assert(r == "1");
+    r = std.string.format("%d", false);
+    assert(r == "0");
+
+    r = std.string.format("%d", 'a');
+    assert(r == "97");
+    wchar wc = 'a';
+    r = std.string.format("%d", wc);
+    assert(r == "97");
+    dchar dc = 'a';
+    r = std.string.format("%d", dc);
+    assert(r == "97");
+
+    byte b = byte.max;
+    r = std.string.format("%x", b);
+    assert(r == "7f");
+    r = std.string.format("%x", ++b);
+    assert(r == "80");
+    r = std.string.format("%x", ++b);
+    assert(r == "81");
+
+    short sh = short.max;
+    r = std.string.format("%x", sh);
+    assert(r == "7fff");
+    r = std.string.format("%x", ++sh);
+    assert(r == "8000");
+    r = std.string.format("%x", ++sh);
+    assert(r == "8001");
+
+    i = int.max;
+    r = std.string.format("%x", i);
+    assert(r == "7fffffff");
+    r = std.string.format("%x", ++i);
+    assert(r == "80000000");
+    r = std.string.format("%x", ++i);
+    assert(r == "80000001");
+
+    r = std.string.format("%x", 10);
+    assert(r == "a");
+    r = std.string.format("%X", 10);
+    assert(r == "A");
+    r = std.string.format("%x", 15);
+    assert(r == "f");
+    r = std.string.format("%X", 15);
+    assert(r == "F");
+
+    Object c = null;
+    r = std.string.format(c);
+    assert(r == "null");
+
+    enum TestEnum
+    {
+	    Value1, Value2
+    }
+    r = std.string.format("%s", TestEnum.Value2);
+    assert(r == "1");
+
+    char[5][int] aa = ([3:"hello", 4:"betty"]);
+    r = std.string.format("%s", aa.values);
+    assert(r == "[[h,e,l,l,o],[b,e,t,t,y]]");
+    r = std.string.format("%s", aa);
+    assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
+
+    static const dchar[] ds = ['a','b'];
+    for (int j = 0; j < ds.length; ++j)
+    {
+	r = std.string.format(" %d", ds[j]);
+	if (j == 0)
+	    assert(r == " 97");
+	else
+	    assert(r == " 98");
+    }
+
+    r = std.string.format(">%14d<, ", 15, [1,2,3]);
+    assert(r == ">            15<, [1,2,3]");
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lphobos/std/math.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,1954 @@
+// Written in the D programming language
+
+/**
+ * Macros:
+ *	WIKI = Phobos/StdMath
+ *
+ *	TABLE_SV = <table border=1 cellpadding=4 cellspacing=0>
+ *		<caption>Special Values</caption>
+ *		$0</table>
+ *	SVH = $(TR $(TH $1) $(TH $2))
+ *	SV  = $(TR $(TD $1) $(TD $2))
+ *
+ *	NAN = $(RED NAN)
+ *	SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
+ *	GAMMA =  &#915;
+ *	INTEGRAL = &#8747;
+ *	INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
+ *	POWER = $1<sup>$2</sup>
+ *	BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
+ *	CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
+ *	PLUSMN = &plusmn;
+ *	INFIN = &infin;
+ *	PI = &pi;
+ *	LT = &lt;
+ *	GT = &gt;
+ */
+
+/*
+ * Author:
+ *	Walter Bright
+ * Copyright:
+ *	Copyright (c) 2001-2005 by Digital Mars,
+ *	All Rights Reserved,
+ *	www.digitalmars.com
+ *  Copyright (c) 2007 by Tomas Lindquist Olsen
+ * License:
+ *  This software is provided 'as-is', without any express or implied
+ *  warranty. In no event will the authors be held liable for any damages
+ *  arising from the use of this software.
+ *
+ *  Permission is granted to anyone to use this software for any purpose,
+ *  including commercial applications, and to alter it and redistribute it
+ *  freely, subject to the following restrictions:
+ *
+ *  <ul>
+ *  <li> The origin of this software must not be misrepresented; you must not
+ *       claim that you wrote the original software. If you use this software
+ *       in a product, an acknowledgment in the product documentation would be
+ *       appreciated but is not required.
+ *  </li>
+ *  <li> Altered source versions must be plainly marked as such, and must not
+ *       be misrepresented as being the original software.
+ *  </li>
+ *  <li> This notice may not be removed or altered from any source
+ *       distribution.
+ *  </li>
+ *  </ul>
+ */
+
+
+module std.math;
+
+//debug=math;		// uncomment to turn on debugging printf's
+
+private import std.c.stdio;
+private import std.c.math;
+
+class NotImplemented : Error
+{
+    this(string msg)
+    {
+	super(msg ~ "not implemented");
+    }
+}
+
+const real E =		2.7182818284590452354L;  /** e */
+const real LOG2T =	0x1.a934f0979a3715fcp+1; /** log<sub>2</sub>10 */ // 3.32193 fldl2t
+const real LOG2E =	0x1.71547652b82fe178p+0; /** log<sub>2</sub>e */ // 1.4427 fldl2e
+const real LOG2 =	0x1.34413509f79fef32p-2; /** log<sub>10</sub>2 */ // 0.30103 fldlg2
+const real LOG10E =	0.43429448190325182765;  /** log<sub>10</sub>e */
+const real LN2 =	0x1.62e42fefa39ef358p-1; /** ln 2 */	// 0.693147 fldln2
+const real LN10 =	2.30258509299404568402;  /** ln 10 */
+const real PI =		0x1.921fb54442d1846ap+1; /** $(PI) */ // 3.14159 fldpi
+const real PI_2 =	1.57079632679489661923;  /** $(PI) / 2 */
+const real PI_4 =	0.78539816339744830962;  /** $(PI) / 4 */
+const real M_1_PI =	0.31830988618379067154;  /** 1 / $(PI) */
+const real M_2_PI =	0.63661977236758134308;  /** 2 / $(PI) */
+const real M_2_SQRTPI =	1.12837916709551257390;  /** 2 / &radic;$(PI) */
+const real SQRT2 =	1.41421356237309504880;  /** &radic;2 */
+const real SQRT1_2 =	0.70710678118654752440;  /** &radic;&frac12; */
+
+/*
+	Octal versions:
+	PI/64800	0.00001 45530 36176 77347 02143 15351 61441 26767
+	PI/180		0.01073 72152 11224 72344 25603 54276 63351 22056
+	PI/8		0.31103 75524 21026 43021 51423 06305 05600 67016
+	SQRT(1/PI)	0.44067 27240 41233 33210 65616 51051 77327 77303
+	2/PI		0.50574 60333 44710 40522 47741 16537 21752 32335
+	PI/4		0.62207 73250 42055 06043 23046 14612 13401 56034
+	SQRT(2/PI)	0.63041 05147 52066 24106 41762 63612 00272 56161
+
+	PI		3.11037 55242 10264 30215 14230 63050 56006 70163
+	LOG2		0.23210 11520 47674 77674 61076 11263 26013 37111
+ */
+
+
+/***********************************
+ * Calculates the absolute value
+ *
+ * For complex numbers, abs(z) = sqrt( $(POWER z.re, 2) + $(POWER z.im, 2) )
+ * = hypot(z.re, z.im).
+ */
+real abs(real x)
+{
+    return fabs(x);
+}
+
+/** ditto */
+long abs(long x)
+{
+    return x>=0 ? x : -x;
+}
+
+/** ditto */
+int abs(int x)
+{
+    return x>=0 ? x : -x;
+}
+
+/** ditto */
+real abs(creal z)
+{
+    return hypot(z.re, z.im);
+}
+
+/** ditto */
+real abs(ireal y)
+{
+    return fabs(y.im);
+}
+
+
+unittest
+{
+    assert(isPosZero(abs(-0.0L)));
+    assert(isnan(abs(real.nan)));
+    assert(abs(-real.infinity) == real.infinity);
+    assert(abs(-3.2Li) == 3.2L);
+    assert(abs(71.6Li) == 71.6L);
+    assert(abs(-56) == 56);
+    assert(abs(2321312L)  == 2321312L);
+    assert(abs(-1+1i) == sqrt(2.0));
+}
+
+/***********************************
+ * Complex conjugate
+ *
+ *  conj(x + iy) = x - iy
+ *
+ * Note that z * conj(z) = $(POWER z.re, 2) - $(POWER z.im, 2)
+ * is always a real number
+ */
+creal conj(creal z)
+{
+    return z.re - z.im*1i;
+}
+
+/** ditto */
+ireal conj(ireal y)
+{
+    return -y;
+}
+
+unittest
+{
+    assert(conj(7 + 3i) == 7-3i);
+    ireal z = -3.2Li;
+    assert(conj(z) == -z);
+}
+
+/***********************************
+ * Returns cosine of x. x is in radians.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH cos(x)) $(TH invalid?))
+ *	$(TR $(TD $(NAN))            $(TD $(NAN)) $(TD yes)	)
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN)) $(TD yes)	)
+ *	)
+ * Bugs:
+ *	Results are undefined if |x| >= $(POWER 2,64).
+ */
+
+pragma(LLVM_internal, "intrinsic", "llvm.cos.f32")
+float cos(float x);
+
+pragma(LLVM_internal, "intrinsic", "llvm.cos.f64") {
+double cos(double x); // ditto
+real cos(real x); /// ditto
+}
+
+
+/***********************************
+ * Returns sine of x. x is in radians.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)               $(TH sin(x))      $(TH invalid?))
+ *	$(TR $(TD $(NAN))          $(TD $(NAN))      $(TD yes))
+ *	$(TR $(TD $(PLUSMN)0.0)    $(TD $(PLUSMN)0.0) $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN))      $(TD yes))
+ *	)
+ * Bugs:
+ *	Results are undefined if |x| >= $(POWER 2,64).
+ */
+
+pragma(LLVM_internal, "intrinsic", "llvm.sin.f32")
+float sin(float x);
+
+pragma(LLVM_internal, "intrinsic", "llvm.sin.f64") {
+double sin(double x); // ditto
+real sin(real x); /// ditto
+}
+
+
+/****************************************************************************
+ * Returns tangent of x. x is in radians.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)               $(TH tan(x))       $(TH invalid?))
+ *	$(TR $(TD $(NAN))          $(TD $(NAN))       $(TD yes))
+ *	$(TR $(TD $(PLUSMN)0.0)    $(TD $(PLUSMN)0.0) $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN))     $(TD yes))
+ *	)
+ */
+
+version(D_InlineAsm_X86)
+real tan(real x)
+{
+
+    asm
+    {
+	fld	x[EBP]			; // load theta
+	fxam				; // test for oddball values
+	fstsw	AX			;
+	sahf				;
+	jc	trigerr			; // x is NAN, infinity, or empty
+					  // 387's can handle denormals
+SC18:	fptan				;
+	fstp	ST(0)			; // dump X, which is always 1
+	fstsw	AX			;
+	sahf				;
+	jnp	Lret			; // C2 = 1 (x is out of range)
+
+	// Do argument reduction to bring x into range
+	fldpi				;
+	fxch				;
+SC17:	fprem1				;
+	fstsw	AX			;
+	sahf				;
+	jp	SC17			;
+	fstp	ST(1)			; // remove pi from stack
+	jmp	SC18			;
+
+trigerr:
+	jnp	Lret			; // if theta is NAN, return theta
+	fstp	ST(0)			; // dump theta
+    }
+    return real.nan;
+
+Lret:
+    ;
+}
+else
+{
+real tan(real x) { return std.c.math.atan(x); }
+}
+
+
+unittest
+{
+    static real vals[][2] =	// angle,tan
+    [
+	    [   0,   0],
+	    [   .5,  .5463024898],
+	    [   1,   1.557407725],
+	    [   1.5, 14.10141995],
+	    [   2,  -2.185039863],
+	    [   2.5,-.7470222972],
+	    [   3,  -.1425465431],
+	    [   3.5, .3745856402],
+	    [   4,   1.157821282],
+	    [   4.5, 4.637332055],
+	    [   5,  -3.380515006],
+	    [   5.5,-.9955840522],
+	    [   6,  -.2910061914],
+	    [   6.5, .2202772003],
+	    [   10,  .6483608275],
+
+	    // special angles
+	    [   PI_4,	1],
+	    //[	PI_2,	real.infinity],
+	    [   3*PI_4,	-1],
+	    [   PI,	0],
+	    [   5*PI_4,	1],
+	    //[	3*PI_2,	-real.infinity],
+	    [   7*PI_4,	-1],
+	    [   2*PI,	0],
+
+	    // overflow
+	    [   real.infinity,	real.nan],
+	    [   real.nan,	real.nan],
+	    //[   1e+100,	real.nan],
+    ];
+    int i;
+
+    for (i = 0; i < vals.length; i++)
+    {
+	real x = vals[i][0];
+	real r = vals[i][1];
+	real t = tan(x);
+
+	//printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r);
+	assert(mfeq(r, t, .0000001));
+
+	x = -x;
+	r = -r;
+	t = tan(x);
+	//printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r);
+	assert(mfeq(r, t, .0000001));
+    }
+}
+
+/***************
+ * Calculates the arc cosine of x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *	$(TABLE_SV
+ *      $(TR $(TH x)         $(TH acos(x)) $(TH invalid?))
+ *      $(TR $(TD $(GT)1.0)  $(TD $(NAN))  $(TD yes))
+ *      $(TR $(TD $(LT)-1.0) $(TD $(NAN))  $(TD yes))
+ *      $(TR $(TD $(NAN))    $(TD $(NAN))  $(TD yes))
+ *      )
+ */
+real acos(real x)		{ return std.c.math.acos(x); }
+
+/***************
+ * Calculates the arc sine of x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)            $(TH asin(x))      $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no))
+ *	$(TR $(TD $(GT)1.0)     $(TD $(NAN))       $(TD yes))
+ *	$(TR $(TD $(LT)-1.0)    $(TD $(NAN))       $(TD yes))
+ *       )
+ */
+real asin(real x)		{ return std.c.math.asin(x); }
+
+/***************
+ * Calculates the arc tangent of x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH atan(x))      $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN))       $(TD yes))
+ *       )
+ */
+real atan(real x)		{ return std.c.math.atan(x); }
+
+/***************
+ * Calculates the arc tangent of y / x,
+ * returning a value ranging from -$(PI)/2 to $(PI)/2.
+ *
+ *      $(TABLE_SV
+ *      $(TR $(TH y)                 $(TH x)            $(TH atan(y, x)))
+ *      $(TR $(TD $(NAN))            $(TD anything)     $(TD $(NAN)) )
+ *      $(TR $(TD anything)          $(TD $(NAN))       $(TD $(NAN)) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(GT)0.0)     $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD $(LT)0.0)     $(TD $(PLUSMN)$(PI)))
+ *      $(TR $(TD $(PLUSMN)0.0)      $(TD -0.0)         $(TD $(PLUSMN)$(PI)))
+ *      $(TR $(TD $(GT)0.0)          $(TD $(PLUSMN)0.0) $(TD $(PI)/2) )
+ *      $(TR $(TD $(LT)0.0)          $(TD $(PLUSMN)0.0) $(TD $(PI)/2))
+ *      $(TR $(TD $(GT)0.0)          $(TD $(INFIN))     $(TD $(PLUSMN)0.0) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD anything)     $(TD $(PLUSMN)$(PI)/2))
+ *      $(TR $(TD $(GT)0.0)          $(TD -$(INFIN))    $(TD $(PLUSMN)$(PI)) )
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(INFIN))     $(TD $(PLUSMN)$(PI)/4))
+ *      $(TR $(TD $(PLUSMN)$(INFIN)) $(TD -$(INFIN))    $(TD $(PLUSMN)3$(PI)/4))
+ *      )
+ */
+real atan2(real y, real x)      { return std.c.math.atan2(y,x); }
+
+/***********************************
+ * Calculates the hyperbolic cosine of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH cosh(x))      $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)0.0) $(TD no) )
+ *      )
+ */
+real cosh(real x)		{ return std.c.math.cosh(x); }
+
+/***********************************
+ * Calculates the hyperbolic sine of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH sinh(x))           $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0)      $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no))
+ *      )
+ */
+real sinh(real x)		{ return std.c.math.sinh(x); }
+
+/***********************************
+ * Calculates the hyperbolic tangent of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH tanh(x))      $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)	     $(TD $(PLUSMN)0.0) $(TD no) )
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)1.0) $(TD no))
+ *      )
+ */
+real tanh(real x)		{ return std.c.math.tanh(x); }
+
+//real acosh(real x)		{ return std.c.math.acoshl(x); }
+//real asinh(real x)		{ return std.c.math.asinhl(x); }
+//real atanh(real x)		{ return std.c.math.atanhl(x); }
+
+/***********************************
+ * Calculates the inverse hyperbolic cosine of x.
+ *
+ *  Mathematically, acosh(x) = log(x + sqrt( x*x - 1))
+ *
+ * $(TABLE_DOMRG
+ *  $(DOMAIN 1..$(INFIN))
+ *  $(RANGE  1..log(real.max), $(INFIN)) )
+ *	$(TABLE_SV
+ *    $(SVH  x,     acosh(x) )
+ *    $(SV  $(NAN), $(NAN) )
+ *    $(SV  <1,     $(NAN) )
+ *    $(SV  1,      0       )
+ *    $(SV  +$(INFIN),+$(INFIN))
+ *  )
+ */   
+real acosh(real x)
+{
+    if (x > 1/real.epsilon)
+	return LN2 + log(x);
+    else
+	return log(x + sqrt(x*x - 1));
+}
+
+unittest
+{
+    assert(isnan(acosh(0.9)));
+    assert(isnan(acosh(real.nan)));
+    assert(acosh(1)==0.0);
+    assert(acosh(real.infinity) == real.infinity);
+}
+
+/***********************************
+ * Calculates the inverse hyperbolic sine of x.
+ *
+ *  Mathematically,
+ *  ---------------
+ *  asinh(x) =  log( x + sqrt( x*x + 1 )) // if x >= +0
+ *  asinh(x) = -log(-x + sqrt( x*x + 1 )) // if x <= -0
+ *  -------------
+ *
+ *	$(TABLE_SV
+ *    $(SVH x,                asinh(x)       )
+ *    $(SV  $(NAN),           $(NAN)         )
+ *    $(SV  $(PLUSMN)0,       $(PLUSMN)0      )
+ *    $(SV  $(PLUSMN)$(INFIN),$(PLUSMN)$(INFIN))
+ *  )
+ */
+real asinh(real x)
+{   
+    if (fabs(x) > 1 / real.epsilon)   // beyond this point, x*x + 1 == x*x
+	return copysign(LN2 + log(fabs(x)), x);
+    else
+    {
+	// sqrt(x*x + 1) ==  1 + x * x / ( 1 + sqrt(x*x + 1) )
+	return copysign(log1p(fabs(x) + x*x / (1 + sqrt(x*x + 1)) ), x);
+    }
+}
+
+unittest
+{
+    assert(isPosZero(asinh(0.0)));
+    assert(isNegZero(asinh(-0.0)));
+    assert(asinh(real.infinity) == real.infinity);
+    assert(asinh(-real.infinity) == -real.infinity);
+    assert(isnan(asinh(real.nan)));
+}
+
+/***********************************
+ * Calculates the inverse hyperbolic tangent of x,
+ * returning a value from ranging from -1 to 1.
+ *  
+ * Mathematically, atanh(x) = log( (1+x)/(1-x) ) / 2
+ *  
+ *
+ * $(TABLE_DOMRG
+ *  $(DOMAIN -$(INFIN)..$(INFIN))
+ *  $(RANGE  -1..1) )
+ *	$(TABLE_SV
+ *    $(SVH  x,     acosh(x) )
+ *    $(SV  $(NAN), $(NAN) )
+ *    $(SV  $(PLUSMN)0, $(PLUSMN)0)
+ *    $(SV  -$(INFIN), -0)
+ *  )
+ */   
+real atanh(real x)
+{
+    // log( (1+x)/(1-x) ) == log ( 1 + (2*x)/(1-x) )
+    return  0.5 * log1p( 2 * x / (1 - x) );
+}
+
+unittest
+{
+    assert(isPosZero(atanh(0.0)));
+    assert(isNegZero(atanh(-0.0)));
+    assert(isnan(atanh(real.nan)));
+    assert(isnan(atanh(-real.infinity))); 
+}
+
+/*****************************************
+ * Returns x rounded to a long value using the current rounding mode.
+ * If the integer value of x is
+ * greater than long.max, the result is
+ * indeterminate.
+ */
+long rndtol(real x);	/* intrinsic */
+
+
+/*****************************************
+ * Returns x rounded to a long value using the FE_TONEAREST rounding mode.
+ * If the integer value of x is
+ * greater than long.max, the result is
+ * indeterminate.
+ */
+extern (C) real rndtonl(real x);
+
+/***************************************
+ * Compute square root of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)         $(TH sqrt(x))   $(TH invalid?))
+ *	$(TR $(TD -0.0)      $(TD -0.0)      $(TD no))
+ *	$(TR $(TD $(LT)0.0)  $(TD $(NAN))    $(TD yes))
+ *	$(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no))
+ *	)
+ */
+
+pragma(LLVM_internal, "intrinsic", "llvm.sqrt.f32")
+float sqrt(float x);	/* intrinsic */
+
+pragma(LLVM_internal, "intrinsic", "llvm.sqrt.f64") {
+double sqrt(double x);	/* intrinsic */	/// ditto
+real sqrt(real x);	/* intrinsic */ /// ditto
+}
+
+creal sqrt(creal z)
+{
+    creal c;
+    real x,y,w,r;
+
+    if (z == 0)
+    {
+	c = 0 + 0i;
+    }
+    else
+    {	real z_re = z.re;
+	real z_im = z.im;
+
+	x = fabs(z_re);
+	y = fabs(z_im);
+	if (x >= y)
+	{
+	    r = y / x;
+	    w = sqrt(x) * sqrt(0.5 * (1 + sqrt(1 + r * r)));
+	}
+	else
+	{
+	    r = x / y;
+	    w = sqrt(y) * sqrt(0.5 * (r + sqrt(1 + r * r)));
+	}
+
+	if (z_re >= 0)
+	{
+	    c = w + (z_im / (w + w)) * 1.0i;
+	}
+	else
+	{
+	    if (z_im < 0)
+		w = -w;
+	    c = z_im / (w + w) + w * 1.0i;
+	}
+    }
+    return c;
+}
+
+/**********************
+ * Calculates e$(SUP x).
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)         $(TH exp(x)))
+ *	$(TR $(TD +$(INFIN)) $(TD +$(INFIN)) )
+ *	$(TR $(TD -$(INFIN)) $(TD +0.0) )
+ *	)
+ */
+real exp(real x)		{ return std.c.math.exp(x); }
+
+/**********************
+ * Calculates 2$(SUP x).
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)         $(TH exp2(x)))
+ *	$(TR $(TD +$(INFIN)) $(TD +$(INFIN)))
+ *	$(TR $(TD -$(INFIN)) $(TD +0.0))
+ *	)
+ */
+real exp2(real x)		{ return std.c.math.exp2(x); }
+
+/******************************************
+ * Calculates the value of the natural logarithm base (e)
+ * raised to the power of x, minus 1.
+ *
+ * For very small x, expm1(x) is more accurate 
+ * than exp(x)-1. 
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)            $(TH e$(SUP x)-1))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0))
+ *	$(TR $(TD +$(INFIN))    $(TD +$(INFIN)))
+ *	$(TR $(TD -$(INFIN))    $(TD -1.0))
+ *	)
+ */
+
+real expm1(real x)		{ return std.c.math.expm1(x); }
+
+
+/*********************************************************************
+ * Separate floating point value into significand and exponent.
+ *
+ * Returns:
+ *	Calculate and return <i>x</i> and exp such that
+ *	value =<i>x</i>*2$(SUP exp) and
+ *	.5 $(LT)= |<i>x</i>| $(LT) 1.0<br>
+ *	<i>x</i> has same sign as value.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH value)           $(TH returns)         $(TH exp))
+ *	$(TR $(TD $(PLUSMN)0.0)    $(TD $(PLUSMN)0.0)    $(TD 0))
+ *	$(TR $(TD +$(INFIN))       $(TD +$(INFIN))       $(TD int.max))
+ *	$(TR $(TD -$(INFIN))       $(TD -$(INFIN))       $(TD int.min))
+ *	$(TR $(TD $(PLUSMN)$(NAN)) $(TD $(PLUSMN)$(NAN)) $(TD int.min))
+ *	)
+ */
+
+
+real frexp(real value, out int exp)
+{
+    ushort* vu = cast(ushort*)&value;
+    long* vl = cast(long*)&value;
+    uint ex;
+
+    // If exponent is non-zero
+    ex = vu[4] & 0x7FFF;
+    if (ex)
+    {
+	if (ex == 0x7FFF)
+	{   // infinity or NaN
+	    if (*vl &  0x7FFFFFFFFFFFFFFF)	// if NaN
+	    {	*vl |= 0xC000000000000000;	// convert $(NAN)S to $(NAN)Q
+		exp = int.min;
+	    }
+	    else if (vu[4] & 0x8000)
+	    {	// negative infinity
+		exp = int.min;
+	    }
+	    else
+	    {	// positive infinity
+		exp = int.max;
+	    }
+	}
+	else
+	{
+	    exp = ex - 0x3FFE;
+	    vu[4] = cast(ushort)((0x8000 & vu[4]) | 0x3FFE);
+	}
+    }
+    else if (!*vl)
+    {
+	// value is +-0.0
+	exp = 0;
+    }
+    else
+    {	// denormal
+	int i = -0x3FFD;
+
+	do
+	{
+	    i--;
+	    *vl <<= 1;
+	} while (*vl > 0);
+	exp = i;
+        vu[4] = cast(ushort)((0x8000 & vu[4]) | 0x3FFE);
+    }
+    return value;
+}
+
+
+unittest
+{
+    static real vals[][3] =	// x,frexp,exp
+    [
+	[0.0,	0.0,	0],
+	[-0.0,	-0.0,	0],
+	[1.0,	.5,	1],
+	[-1.0,	-.5,	1],
+	[2.0,	.5,	2],
+	[155.67e20,	0x1.A5F1C2EB3FE4Fp-1,	74],	// normal
+	[1.0e-320,	0.98829225,		-1063],
+	[real.min,	.5,		-16381],
+	[real.min/2.0L,	.5,		-16382],	// denormal
+
+	[real.infinity,real.infinity,int.max],
+	[-real.infinity,-real.infinity,int.min],
+	[real.nan,real.nan,int.min],
+	[-real.nan,-real.nan,int.min],
+
+	// Don't really support signalling nan's in D
+	//[real.nans,real.nan,int.min],
+	//[-real.nans,-real.nan,int.min],
+    ];
+    int i;
+
+    for (i = 0; i < vals.length; i++)
+    {
+	real x = vals[i][0];
+	real e = vals[i][1];
+	int exp = cast(int)vals[i][2];
+	int eptr;
+	real v = frexp(x, eptr);
+
+	//printf("frexp(%Lg) = %.8Lg, should be %.8Lg, eptr = %d, should be %d\n", x, v, e, eptr, exp);
+	assert(mfeq(e, v, .0000001));
+	assert(exp == eptr);
+    }
+}
+
+
+/******************************************
+ * Extracts the exponent of x as a signed integral value.
+ *
+ * If x is not a special value, the result is the same as
+ * <tt>cast(int)logb(x)</tt>.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                $(TH ilogb(x))     $(TH Range error?))
+ *	$(TR $(TD 0)                 $(TD FP_ILOGB0)   $(TD yes))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD int.max)     $(TD no))
+ *	$(TR $(TD $(NAN))            $(TD FP_ILOGBNAN) $(TD no))
+ *	)
+ */
+int ilogb(real x)		{ return std.c.math.ilogb(x); }
+
+alias std.c.math.FP_ILOGB0   FP_ILOGB0;
+alias std.c.math.FP_ILOGBNAN FP_ILOGBNAN;
+
+
+/*******************************************
+ * Compute n * 2$(SUP exp)
+ * References: frexp
+ */
+
+real ldexp(real n, int exp)	{ return std.c.math.ldexp(n, exp); }
+
+/**************************************
+ * Calculate the natural logarithm of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)            $(TH log(x))    $(TH divide by 0?) $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no))
+ *	$(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes))
+ *	$(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no))
+ *	)
+ */
+
+real log(real x)		{ return std.c.math.log(x); }
+
+/**************************************
+ * Calculate the base-10 logarithm of x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)            $(TH log10(x))  $(TH divide by 0?) $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no))
+ *	$(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes))
+ *	$(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no))
+ *	)
+ */
+
+real log10(real x)		{ return std.c.math.log10(x); }
+
+/******************************************
+ *	Calculates the natural logarithm of 1 + x.
+ *
+ *	For very small x, log1p(x) will be more accurate than 
+ *	log(1 + x). 
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)            $(TH log1p(x))     $(TH divide by 0?) $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)           $(TD no))
+ *	$(TR $(TD -1.0)         $(TD -$(INFIN))    $(TD yes)          $(TD no))
+ *	$(TR $(TD $(LT)-1.0)    $(TD $(NAN))       $(TD no)           $(TD yes))
+ *	$(TR $(TD +$(INFIN))    $(TD -$(INFIN))    $(TD no)           $(TD no))
+ *	)
+ */
+
+real log1p(real x)		{ return std.c.math.log1p(x); }
+
+/***************************************
+ * Calculates the base-2 logarithm of x:
+ * log<sub>2</sub>x
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x) 	        $(TH log2(x))   $(TH divide by 0?) $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes)          $(TD no) )
+ *	$(TR $(TD $(LT)0.0)     $(TD $(NAN))    $(TD no)           $(TD yes) )
+ *	$(TR $(TD +$(INFIN))    $(TD +$(INFIN)) $(TD no)           $(TD no) )
+ *	)
+ */
+real log2(real x)		{ return std.c.math.log2(x); }
+
+/*****************************************
+ * Extracts the exponent of x as a signed integral value.
+ *
+ * If x is subnormal, it is treated as if it were normalized.
+ * For a positive, finite x: 
+ *
+ * -----
+ * 1 <= $(I x) * FLT_RADIX$(SUP -logb(x)) < FLT_RADIX 
+ * -----
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH logb(x))   $(TH divide by 0?) )
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) $(TD no))
+ *	$(TR $(TD $(PLUSMN)0.0)      $(TD -$(INFIN)) $(TD yes) )
+ *	)
+ */
+real logb(real x)		{ return std.c.math.logb(x); }
+
+/************************************
+ * Calculates the remainder from the calculation x/y.
+ * Returns:
+ * The value of x - i * y, where i is the number of times that y can 
+ * be completely subtracted from x. The result has the same sign as x. 
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                   $(TH y)                 $(TH modf(x, y))   $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)        $(TD no)t 0.0           $(TD $(PLUSMN)0.0) $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN))   $(TD anything)          $(TD $(NAN))       $(TD yes))
+ *	$(TR $(TD anything)            $(TD $(PLUSMN)0.0)      $(TD $(NAN))       $(TD yes))
+ *	$(TR $(TD !=$(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD x)            $(TD no))
+ *	)
+ */
+real modf(real x, inout real y)
+{
+double Y = y;
+auto tmp = std.c.math.modf(x,&Y);
+y = Y;
+return tmp;
+}
+
+/*************************************
+ * Efficiently calculates x * 2$(SUP n).
+ *
+ * scalbn handles underflow and overflow in 
+ * the same fashion as the basic arithmetic operators. 
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH scalb(x)))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) )
+ *	$(TR $(TD $(PLUSMN)0.0)      $(TD $(PLUSMN)0.0) )
+ *	)
+ */
+real scalbn(real x, int n)
+{
+    version (linux)
+	return std.c.math.scalbn(x, n);
+    else
+	throw new NotImplemented("scalbn");
+}
+
+/***************
+ * Calculates the cube root x.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH $(I x))	     $(TH cbrt(x))           $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)	     $(TD $(PLUSMN)0.0)      $(TD no) )
+ *	$(TR $(TD $(NAN))	     $(TD $(NAN))            $(TD yes) )
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no) )
+ *	)
+ */
+real cbrt(real x)		{ return std.c.math.cbrt(x); }
+
+
+/*******************************
+ * Returns |x|
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH fabs(x)))
+ *	$(TR $(TD $(PLUSMN)0.0)      $(TD +0.0) )
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) )
+ *	)
+ */
+real fabs(real x)	{ return std.c.math.fabs(x); }
+
+
+/***********************************************************************
+ * Calculates the length of the 
+ * hypotenuse of a right-angled triangle with sides of length x and y. 
+ * The hypotenuse is the value of the square root of 
+ * the sums of the squares of x and y:
+ *
+ *	sqrt(x&sup2; + y&sup2;)
+ *
+ * Note that hypot(x, y), hypot(y, x) and
+ * hypot(x, -y) are equivalent.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH y)            $(TH hypot(x, y)) $(TH invalid?))
+ *	$(TR $(TD x)                 $(TD $(PLUSMN)0.0) $(TD |x|)         $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD y)            $(TD +$(INFIN))   $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN))       $(TD +$(INFIN))   $(TD no))
+ *	)
+ */
+
+real hypot(real x, real y)
+{
+    /*
+     * This is based on code from:
+     * Cephes Math Library Release 2.1:  January, 1989
+     * Copyright 1984, 1987, 1989 by Stephen L. Moshier
+     * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+     */
+
+    const int PRECL = 32;
+    const int MAXEXPL = real.max_exp; //16384;
+    const int MINEXPL = real.min_exp; //-16384;
+
+    real xx, yy, b, re, im;
+    int ex, ey, e;
+
+    // Note, hypot(INFINITY, NAN) = INFINITY.
+    if (isinf(x) || isinf(y))
+	return real.infinity;
+
+    if (isnan(x))
+	return x;
+    if (isnan(y))
+	return y;
+
+    re = fabs(x);
+    im = fabs(y);
+
+    if (re == 0.0)
+	return im;
+    if (im == 0.0)
+	return re;
+
+    // Get the exponents of the numbers
+    xx = frexp(re, ex);
+    yy = frexp(im, ey);
+
+    // Check if one number is tiny compared to the other
+    e = ex - ey;
+    if (e > PRECL)
+	return re;
+    if (e < -PRECL)
+	return im;
+
+    // Find approximate exponent e of the geometric mean.
+    e = (ex + ey) >> 1;
+
+    // Rescale so mean is about 1
+    xx = ldexp(re, -e);
+    yy = ldexp(im, -e);
+
+    // Hypotenuse of the right triangle
+    b = sqrt(xx * xx  +  yy * yy);
+
+    // Compute the exponent of the answer.
+    yy = frexp(b, ey);
+    ey = e + ey;
+
+    // Check it for overflow and underflow.
+    if (ey > MAXEXPL + 2)
+    {
+	//return __matherr(_OVERFLOW, INFINITY, x, y, "hypotl");
+	return real.infinity;
+    }
+    if (ey < MINEXPL - 2)
+	return 0.0;
+
+    // Undo the scaling
+    b = ldexp(b, e);
+    return b;
+}
+
+unittest
+{
+    static real vals[][3] =	// x,y,hypot
+    [
+	[	0,	0,	0],
+	[	0,	-0,	0],
+	[	3,	4,	5],
+	[	-300,	-400,	500],
+	[	real.min, real.min, 4.75473e-4932L],
+	[	real.max/2, real.max/2, 0x1.6a09e667f3bcc908p+16383L /*8.41267e+4931L*/],
+	[	real.infinity, real.nan, real.infinity],
+	[	real.nan, real.nan, real.nan],
+    ];
+    int i;
+
+    for (i = 0; i < vals.length; i++)
+    {
+	real x = vals[i][0];
+	real y = vals[i][1];
+	real z = vals[i][2];
+	real h = hypot(x, y);
+
+	//printf("hypot(%Lg, %Lg) = %Lg, should be %Lg\n", x, y, h, z);
+	//if (!mfeq(z, h, .0000001))
+	    //printf("%La\n", h);
+	assert(mfeq(z, h, .0000001));
+    }
+}
+
+/**********************************
+ * Returns the error function of x.
+ *
+ * <img src="erf.gif" alt="error function">
+ */
+real erf(real x)		{ return std.c.math.erf(x); }
+
+/**********************************
+ * Returns the complementary error function of x, which is 1 - erf(x).
+ *
+ * <img src="erfc.gif" alt="complementary error function">
+ */
+real erfc(real x)		{ return std.c.math.erfc(x); }
+
+/***********************************
+ * Natural logarithm of gamma function.
+ *
+ * Returns the base e (2.718...) logarithm of the absolute
+ * value of the gamma function of the argument.
+ *
+ * For reals, lgamma is equivalent to log(fabs(gamma(x))).
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                 $(TH lgamma(x)) $(TH invalid?))
+ *	$(TR $(TD $(NAN))            $(TD $(NAN))    $(TD yes))
+ *	$(TR $(TD integer <= 0)      $(TD +$(INFIN)) $(TD yes))
+ *	$(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) $(TD no))
+ *	)
+ */
+/* Documentation prepared by Don Clugston */
+real lgamma(real x)
+{
+    return std.c.math.lgamma(x);
+
+    // Use etc.gamma.lgamma for those C systems that are missing it
+}
+
+/***********************************
+ *  The Gamma function, $(GAMMA)(x)
+ *
+ *  $(GAMMA)(x) is a generalisation of the factorial function
+ *  to real and complex numbers.
+ *  Like x!, $(GAMMA)(x+1) = x*$(GAMMA)(x).
+ *
+ *  Mathematically, if z.re > 0 then
+ *   $(GAMMA)(z) =<big>$(INTEGRAL)<sub><small>0</small></sub><sup>$(INFIN)</sup></big>t<sup>z-1</sup>e<sup>-t</sup>dt
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)              $(TH $(GAMMA)(x))       $(TH invalid?))
+ *	$(TR $(TD $(NAN))         $(TD $(NAN))            $(TD yes))
+ *	$(TR $(TD $(PLUSMN)0.0)   $(TD $(PLUSMN)$(INFIN)) $(TD yes))
+ *	$(TR $(TD integer $(GT)0) $(TD (x-1)!)            $(TD no))
+ *	$(TR $(TD integer $(LT)0) $(TD $(NAN))            $(TD yes))
+ *	$(TR $(TD +$(INFIN))      $(TD +$(INFIN))         $(TD no))
+ *	$(TR $(TD -$(INFIN))      $(TD $(NAN))            $(TD yes))
+ *	)
+ *
+ *  References:
+ *	$(LINK http://en.wikipedia.org/wiki/Gamma_function),
+ *	$(LINK http://www.netlib.org/cephes/ldoubdoc.html#gamma)
+ */
+/* Documentation prepared by Don Clugston */
+real tgamma(real x)
+{
+    return std.c.math.tgamma(x);
+
+    // Use etc.gamma.tgamma for those C systems that are missing it
+}
+
+/**************************************
+ * Returns the value of x rounded upward to the next integer
+ * (toward positive infinity).
+ */
+real ceil(real x)		{ return std.c.math.ceil(x); }
+
+/**************************************
+ * Returns the value of x rounded downward to the next integer
+ * (toward negative infinity).
+ */
+real floor(real x)		{ return std.c.math.floor(x); }
+
+/******************************************
+ * Rounds x to the nearest integer value, using the current rounding 
+ * mode.
+ *
+ * Unlike the rint functions, nearbyint does not raise the 
+ * FE_INEXACT exception. 
+ */
+real nearbyint(real x) { return std.c.math.nearbyint(x); }
+
+/**********************************
+ * Rounds x to the nearest integer value, using the current rounding
+ * mode.
+ * If the return value is not equal to x, the FE_INEXACT
+ * exception is raised.
+ * <b>nearbyint</b> performs
+ * the same operation, but does not set the FE_INEXACT exception.
+ */
+real rint(real x)	{ return std.c.math.rint(x); }
+
+/***************************************
+ * Rounds x to the nearest integer value, using the current rounding
+ * mode.
+ */
+long lrint(real x)
+{
+    version (linux)
+	return std.c.math.llrint(x);
+    else
+	throw new NotImplemented("lrint");
+}
+
+/*******************************************
+ * Return the value of x rounded to the nearest integer.
+ * If the fractional part of x is exactly 0.5, the return value is rounded to
+ * the even integer. 
+ */
+real round(real x) { return std.c.math.round(x); }
+
+/**********************************************
+ * Return the value of x rounded to the nearest integer.
+ *
+ * If the fractional part of x is exactly 0.5, the return value is rounded
+ * away from zero.
+ */
+long lround(real x)
+{
+    version (linux)
+	return std.c.math.llround(x);
+    else
+	throw new NotImplemented("lround");
+}
+
+/****************************************************
+ * Returns the integer portion of x, dropping the fractional portion. 
+ *
+ * This is also known as "chop" rounding. 
+ */
+real trunc(real x) { return std.c.math.trunc(x); }
+
+/****************************************************
+ * Calculate the remainder x REM y, following IEC 60559.
+ *
+ * REM is the value of x - y * n, where n is the integer nearest the exact 
+ * value of x / y.
+ * If |n - x / y| == 0.5, n is even.
+ * If the result is zero, it has the same sign as x.
+ * Otherwise, the sign of the result is the sign of x / y.
+ * Precision mode has no effect on the remainder functions.
+ *
+ * remquo returns n in the parameter n.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)                    $(TH y)                 $(TH remainder(x, y)) $(TH n)   $(TH invalid?))
+ *	$(TR $(TD $(PLUSMN)0.0)         $(TD no)t 0.0           $(TD $(PLUSMN)0.0)    $(TD 0.0) $(TD no))
+ *	$(TR $(TD $(PLUSMN)$(INFIN))    $(TD anything)          $(TD $(NAN))          $(TD ?)   $(TD yes))
+ *	$(TR $(TD anything)             $(TD $(PLUSMN)0.0)      $(TD $(NAN))          $(TD ?)   $(TD yes))
+ *	$(TR $(TD != $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD x)               $(TD ?)   $(TD no))
+ *	)
+ */
+real remainder(real x, real y) { return std.c.math.remainder(x, y); }
+
+real remquo(real x, real y, out int n)	/// ditto
+{
+    version (linux)
+	return std.c.math.remquo(x, y, &n);
+    else
+	throw new NotImplemented("remquo");
+}
+
+/*********************************
+ * Returns !=0 if e is a NaN.
+ */
+
+int isnan(real e)
+{
+    ushort* pe = cast(ushort *)&e;
+    ulong*  ps = cast(ulong *)&e;
+
+    return (pe[4] & 0x7FFF) == 0x7FFF &&
+	    *ps & 0x7FFFFFFFFFFFFFFF;
+}
+
+unittest
+{
+    assert(isnan(float.nan));
+    assert(isnan(-double.nan));
+    assert(isnan(real.nan));
+
+    assert(!isnan(53.6));
+    assert(!isnan(float.infinity));
+}
+
+/*********************************
+ * Returns !=0 if e is finite.
+ */
+
+int isfinite(real e)
+{
+    ushort* pe = cast(ushort *)&e;
+
+    return (pe[4] & 0x7FFF) != 0x7FFF;
+}
+
+unittest
+{
+    assert(isfinite(1.23));
+    assert(!isfinite(double.infinity));
+    assert(!isfinite(float.nan));
+}
+
+
+/*********************************
+ * Returns !=0 if x is normalized.
+ */
+
+/* Need one for each format because subnormal floats might
+ * be converted to normal reals.
+ */
+
+int isnormal(float x)
+{
+    uint *p = cast(uint *)&x;
+    uint e;
+
+    e = *p & 0x7F800000;
+    //printf("e = x%x, *p = x%x\n", e, *p);
+    return e && e != 0x7F800000;
+}
+
+/// ditto
+
+int isnormal(double d)
+{
+    uint *p = cast(uint *)&d;
+    uint e;
+
+    e = p[1] & 0x7FF00000;
+    return e && e != 0x7FF00000;
+}
+
+/// ditto
+
+int isnormal(real e)
+{
+    ushort* pe = cast(ushort *)&e;
+    long*   ps = cast(long *)&e;
+
+    return (pe[4] & 0x7FFF) != 0x7FFF && *ps < 0;
+}
+
+unittest
+{
+    float f = 3;
+    double d = 500;
+    real e = 10e+48;
+
+    assert(isnormal(f));
+    assert(isnormal(d));
+    assert(isnormal(e));
+}
+
+/*********************************
+ * Is number subnormal? (Also called "denormal".)
+ * Subnormals have a 0 exponent and a 0 most significant mantissa bit.
+ */
+
+/* Need one for each format because subnormal floats might
+ * be converted to normal reals.
+ */
+
+int issubnormal(float f)
+{
+    uint *p = cast(uint *)&f;
+
+    //printf("*p = x%x\n", *p);
+    return (*p & 0x7F800000) == 0 && *p & 0x007FFFFF;
+}
+
+unittest
+{
+    float f = 3.0;
+
+    for (f = 1.0; !issubnormal(f); f /= 2)
+	assert(f != 0);
+}
+
+/// ditto
+
+int issubnormal(double d)
+{
+    uint *p = cast(uint *)&d;
+
+    return (p[1] & 0x7FF00000) == 0 && (p[0] || p[1] & 0x000FFFFF);
+}
+
+unittest
+{
+    double f;
+
+    for (f = 1; !issubnormal(f); f /= 2)
+	assert(f != 0);
+}
+
+/// ditto
+
+int issubnormal(real e)
+{
+    ushort* pe = cast(ushort *)&e;
+    long*   ps = cast(long *)&e;
+
+    return (pe[4] & 0x7FFF) == 0 && *ps > 0;
+}
+
+unittest
+{
+    real f;
+
+    for (f = 1; !issubnormal(f); f /= 2)
+	assert(f != 0);
+}
+
+/*********************************
+ * Return !=0 if e is $(PLUSMN)$(INFIN).
+ */
+
+int isinf(real e)
+{
+    ushort* pe = cast(ushort *)&e;
+    ulong*  ps = cast(ulong *)&e;
+
+    return (pe[4] & 0x7FFF) == 0x7FFF &&
+	    *ps == 0x8000000000000000;
+}
+
+unittest
+{
+    assert(isinf(float.infinity));
+    assert(!isinf(float.nan));
+    assert(isinf(double.infinity));
+    assert(isinf(-real.infinity));
+
+    assert(isinf(-1.0 / 0.0));
+}
+
+/*********************************
+ * Return 1 if sign bit of e is set, 0 if not.
+ */
+
+int signbit(real e)
+{
+    ubyte* pe = cast(ubyte *)&e;
+
+//printf("e = %Lg\n", e);
+    return (pe[9] & 0x80) != 0;
+}
+
+unittest
+{
+    debug (math) printf("math.signbit.unittest\n");
+    assert(!signbit(float.nan));
+    assert(signbit(-float.nan));
+    assert(!signbit(168.1234));
+    assert(signbit(-168.1234));
+    assert(!signbit(0.0));
+    assert(signbit(-0.0));
+}
+
+/*********************************
+ * Return a value composed of to with from's sign bit.
+ */
+
+real copysign(real to, real from)
+{
+    ubyte* pto   = cast(ubyte *)&to;
+    ubyte* pfrom = cast(ubyte *)&from;
+
+    pto[9] &= 0x7F;
+    pto[9] |= pfrom[9] & 0x80;
+
+    return to;
+}
+
+unittest
+{
+    real e;
+
+    e = copysign(21, 23.8);
+    assert(e == 21);
+
+    e = copysign(-21, 23.8);
+    assert(e == 21);
+
+    e = copysign(21, -23.8);
+    assert(e == -21);
+
+    e = copysign(-21, -23.8);
+    assert(e == -21);
+
+    e = copysign(real.nan, -23.8);
+    assert(isnan(e) && signbit(e));
+}
+
+/******************************************
+ * Creates a quiet NAN with the information from tagp[] embedded in it.
+ */
+real nan(char[] tagp) { return std.c.math.nan((tagp~\0).ptr); }
+
+/******************************************
+ * Calculates the next representable value after x in the direction of y. 
+ *
+ * If y $(GT) x, the result will be the next largest floating-point value;
+ * if y $(LT) x, the result will be the next smallest value.
+ * If x == y, the result is y.
+ * The FE_INEXACT and FE_OVERFLOW exceptions will be raised if x is finite and
+ * the function result is infinite. The FE_INEXACT and FE_UNDERFLOW 
+ * exceptions will be raised if the function value is subnormal, and x is 
+ * not equal to y. 
+ */
+real nextafter(real x, real y)
+{
+    version (linux)
+	return std.c.math.nextafterl(x, y);
+    else
+	throw new NotImplemented("nextafter");
+}
+
+//real nexttoward(real x, real y) { return std.c.math.nexttowardl(x, y); }
+
+/*******************************************
+ * Returns the positive difference between x and y.
+ * Returns:
+ *	$(TABLE_SV
+ *	$(TR $(TH x, y)       $(TH fdim(x, y)))
+ *	$(TR $(TD x $(GT) y)  $(TD x - y))
+ *	$(TR $(TD x $(LT)= y) $(TD +0.0))
+ *	)
+ */
+real fdim(real x, real y) { return (x > y) ? x - y : +0.0; }
+
+/****************************************
+ * Returns the larger of x and y.
+ */
+real fmax(real x, real y) { return x > y ? x : y; }
+
+/****************************************
+ * Returns the smaller of x and y.
+ */
+real fmin(real x, real y) { return x < y ? x : y; }
+
+/**************************************
+ * Returns (x * y) + z, rounding only once according to the
+ * current rounding mode.
+ */
+real fma(real x, real y, real z) { return (x * y) + z; }
+
+/*******************************************************************
+ * Fast integral powers.
+ */
+
+pragma(LLVM_internal, "intrinsic", "llvm.powi.f32")
+{
+float pow(float x, uint n);
+/// ditto
+float pow(float x, int n);
+}
+
+pragma(LLVM_internal, "intrinsic", "llvm.powi.f64")
+{
+/// ditto
+double pow(double x, uint n);
+/// ditto
+double pow(double x, int n);
+/// ditto
+real pow(real x, uint n);
+/// ditto
+real pow(real x, int n);
+}
+
+/+
+real pow(real x, uint n);
+{
+    real p;
+
+    switch (n)
+    {
+	case 0:
+	    p = 1.0;
+	    break;
+
+	case 1:
+	    p = x;
+	    break;
+
+	case 2:
+	    p = x * x;
+	    break;
+
+	default:
+	    p = 1.0;
+	    while (1)
+	    {
+		if (n & 1)
+		    p *= x;
+		n >>= 1;
+		if (!n)
+		    break;
+		x *= x;
+	    }
+	    break;
+    }
+    return p;
+}
+
+/// ditto
+real pow(real x, int n);
+{
+    if (n < 0)
+	return pow(x, cast(real)n);
+    else
+	return pow(x, cast(uint)n);
+}
++/
+
+/*********************************************
+ * Calculates x$(SUP y).
+ *
+ * $(TABLE_SV
+ * $(TR
+ * $(TH x) $(TH y) $(TH pow(x, y)) $(TH div 0) $(TH invalid?))
+ * $(TR
+ * $(TD anything) 	$(TD $(PLUSMN)0.0) 	$(TD 1.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD |x| $(GT) 1) 	$(TD +$(INFIN)) 	$(TD +$(INFIN)) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD |x| $(LT) 1) 	$(TD +$(INFIN)) 	$(TD +0.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD |x| $(GT) 1) 	$(TD -$(INFIN)) 	$(TD +0.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD |x| $(LT) 1) 	$(TD -$(INFIN)) 	$(TD +$(INFIN)) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD +$(INFIN)) 	$(TD $(GT) 0.0) 	$(TD +$(INFIN)) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD +$(INFIN)) 	$(TD $(LT) 0.0) 	$(TD +0.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD -$(INFIN)) 	$(TD odd integer $(GT) 0.0)	$(TD -$(INFIN)) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD -$(INFIN))  	$(TD $(GT) 0.0, not odd integer) $(TD +$(INFIN)) 	$(TD no) 	$(TD no))
+ * $(TR
+ * $(TD -$(INFIN)) 	$(TD odd integer $(LT) 0.0)  	$(TD -0.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD -$(INFIN)) 	$(TD $(LT) 0.0, not odd integer) $(TD +0.0) 	$(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD $(PLUSMN)1.0) 	$(TD $(PLUSMN)$(INFIN)) 	$(TD $(NAN)) 	$(TD no) 	$(TD yes) )
+ * $(TR
+ * $(TD $(LT) 0.0) 	$(TD finite, nonintegral) 	$(TD $(NAN)) 	$(TD no) 	$(TD yes))
+ * $(TR
+ * $(TD $(PLUSMN)0.0) 	$(TD odd integer $(LT) 0.0)	$(TD $(PLUSMN)$(INFIN)) $(TD yes) 	$(TD no) )
+ * $(TR
+ * $(TD $(PLUSMN)0.0) 	$(TD $(LT) 0.0, not odd integer) $(TD +$(INFIN)) 	$(TD yes) 	$(TD no))
+ * $(TR
+ * $(TD $(PLUSMN)0.0) 	$(TD odd integer $(GT) 0.0)	$(TD $(PLUSMN)0.0) $(TD no) 	$(TD no) )
+ * $(TR
+ * $(TD $(PLUSMN)0.0) 	$(TD $(GT) 0.0, not odd integer) $(TD +0.0) 	$(TD no) 	$(TD no) )
+ * )
+ */
+
+pragma(LLVM_internal, "intrinsic", "llvm.pow.f32")
+float pow(float x, float y);
+
+pragma(LLVM_internal, "intrinsic", "llvm.pow.f64")
+{
+/// ditto
+double pow(double x, double y);
+/// ditto
+real pow(real x, real y);
+}
+
+/+
+real pow(real x, real y);
+{
+    version (linux) // C pow() often does not handle special values correctly
+    {
+	if (isnan(y))
+	    return y;
+
+	if (y == 0)
+	    return 1;		// even if x is $(NAN)
+	if (isnan(x) && y != 0)
+	    return x;
+	if (isinf(y))
+	{
+	    if (fabs(x) > 1)
+	    {
+		if (signbit(y))
+		    return +0.0;
+		else
+		    return real.infinity;
+	    }
+	    else if (fabs(x) == 1)
+	    {
+		return real.nan;
+	    }
+	    else // < 1
+	    {
+		if (signbit(y))
+		    return real.infinity;
+		else
+		    return +0.0;
+	    }
+	}
+	if (isinf(x))
+	{
+	    if (signbit(x))
+	    {   long i;
+
+		i = cast(long)y;
+		if (y > 0)
+		{
+		    if (i == y && i & 1)
+			return -real.infinity;
+		    else
+			return real.infinity;
+		}
+		else if (y < 0)
+		{
+		    if (i == y && i & 1)
+			return -0.0;
+		    else
+			return +0.0;
+		}
+	    }
+	    else
+	    {
+		if (y > 0)
+		    return real.infinity;
+		else if (y < 0)
+		    return +0.0;
+	    }
+	}
+
+	if (x == 0.0)
+	{
+	    if (signbit(x))
+	    {   long i;
+
+		i = cast(long)y;
+		if (y > 0)
+		{
+		    if (i == y && i & 1)
+			return -0.0;
+		    else
+			return +0.0;
+		}
+		else if (y < 0)
+		{
+		    if (i == y && i & 1)
+			return -real.infinity;
+		    else
+			return real.infinity;
+		}
+	    }
+	    else
+	    {
+		if (y > 0)
+		    return +0.0;
+		else if (y < 0)
+		    return real.infinity;
+	    }
+	}
+    }
+    return std.c.math.powl(x, y);
+}
++/
+
+unittest
+{
+    real x = 46;
+
+    assert(pow(x,0) == 1.0);
+    assert(pow(x,1) == x);
+    assert(pow(x,2) == x * x);
+    assert(pow(x,3) == x * x * x);
+    assert(pow(x,8) == (x * x) * (x * x) * (x * x) * (x * x));
+}
+
+/****************************************
+ * Simple function to compare two floating point values
+ * to a specified precision.
+ * Returns:
+ *	1	match
+ *	0	nomatch
+ */
+
+private int mfeq(real x, real y, real precision)
+{
+    if (x == y)
+	return 1;
+    if (isnan(x))
+	return isnan(y);
+    if (isnan(y))
+	return 0;
+    return fabs(x - y) <= precision;
+}
+
+// Returns true if x is +0.0 (This function is used in unit tests)
+bool isPosZero(real x)
+{
+    return (x == 0) && (signbit(x) == 0);
+}
+
+// Returns true if x is -0.0 (This function is used in unit tests)
+bool isNegZero(real x)
+{
+    return (x == 0) && signbit(x);
+}
+
+/**************************************
+ * To what precision is x equal to y?
+ *
+ * Returns: the number of mantissa bits which are equal in x and y.
+ * eg, 0x1.F8p+60 and 0x1.F1p+60 are equal to 5 bits of precision.
+ *
+ *	$(TABLE_SV
+ *	$(TR $(TH x)      $(TH y)          $(TH feqrel(x, y)))
+ *	$(TR $(TD x)      $(TD x)          $(TD real.mant_dig))
+ *	$(TR $(TD x)      $(TD $(GT)= 2*x) $(TD 0))
+ *	$(TR $(TD x)      $(TD $(LT)= x/2) $(TD 0))
+ *	$(TR $(TD $(NAN)) $(TD any)        $(TD 0))
+ *	$(TR $(TD any)    $(TD $(NAN))     $(TD 0))
+ *	)
+ */
+
+int feqrel(real x, real y)
+{
+    /* Public Domain. Author: Don Clugston, 18 Aug 2005.
+     */
+
+    if (x == y)
+	return real.mant_dig; // ensure diff!=0, cope with INF.
+
+    real diff = fabs(x - y);
+
+    ushort *pa = cast(ushort *)(&x);
+    ushort *pb = cast(ushort *)(&y);
+    ushort *pd = cast(ushort *)(&diff);
+
+    // The difference in abs(exponent) between x or y and abs(x-y)
+    // is equal to the number of mantissa bits of x which are
+    // equal to y. If negative, x and y have different exponents.
+    // If positive, x and y are equal to 'bitsdiff' bits.
+    // AND with 0x7FFF to form the absolute value.
+    // To avoid out-by-1 errors, we subtract 1 so it rounds down
+    // if the exponents were different. This means 'bitsdiff' is
+    // always 1 lower than we want, except that if bitsdiff==0,
+    // they could have 0 or 1 bits in common.
+    int bitsdiff = ( ((pa[4]&0x7FFF) + (pb[4]&0x7FFF)-1)>>1) - pd[4];
+
+    if (pd[4] == 0)
+    {	// Difference is denormal
+	// For denormals, we need to add the number of zeros that
+	// lie at the start of diff's mantissa.
+	// We do this by multiplying by 2^real.mant_dig
+	diff *= 0x1p+63;
+	return bitsdiff + real.mant_dig - pd[4];
+    }
+
+    if (bitsdiff > 0)
+	return bitsdiff + 1; // add the 1 we subtracted before
+
+    // Avoid out-by-1 errors when factor is almost 2.
+    return (bitsdiff == 0) ? (pa[4] == pb[4]) : 0;
+}
+
+unittest
+{
+   // Exact equality
+   assert(feqrel(real.max,real.max)==real.mant_dig);
+   assert(feqrel(0,0)==real.mant_dig);
+   assert(feqrel(7.1824,7.1824)==real.mant_dig);
+   assert(feqrel(real.infinity,real.infinity)==real.mant_dig);
+
+   // a few bits away from exact equality
+   real w=1;
+   for (int i=1; i<real.mant_dig-1; ++i) {
+      assert(feqrel(1+w*real.epsilon,1)==real.mant_dig-i);
+      assert(feqrel(1-w*real.epsilon,1)==real.mant_dig-i);
+      assert(feqrel(1,1+(w-1)*real.epsilon)==real.mant_dig-i+1);
+      w*=2;
+   }
+   assert(feqrel(1.5+real.epsilon,1.5)==real.mant_dig-1);
+   assert(feqrel(1.5-real.epsilon,1.5)==real.mant_dig-1);
+   assert(feqrel(1.5-real.epsilon,1.5+real.epsilon)==real.mant_dig-2);
+
+   // Numbers that are close
+   assert(feqrel(0x1.Bp+84, 0x1.B8p+84)==5);
+   assert(feqrel(0x1.8p+10, 0x1.Cp+10)==2);
+   assert(feqrel(1.5*(1-real.epsilon), 1)==2);
+   assert(feqrel(1.5, 1)==1);
+   assert(feqrel(2*(1-real.epsilon), 1)==1);
+
+   // Factors of 2
+   assert(feqrel(real.max,real.infinity)==0);
+   assert(feqrel(2*(1-real.epsilon), 1)==1);
+   assert(feqrel(1, 2)==0);
+   assert(feqrel(4, 1)==0);
+
+   // Extreme inequality
+   assert(feqrel(real.nan,real.nan)==0);
+   assert(feqrel(0,-real.nan)==0);
+   assert(feqrel(real.nan,real.infinity)==0);
+   assert(feqrel(real.infinity,-real.infinity)==0);
+   assert(feqrel(-real.max,real.infinity)==0);
+   assert(feqrel(real.max,-real.max)==0);
+}
+
+
+/***********************************
+ * Evaluate polynomial A(x) = a<sub>0</sub> + a<sub>1</sub>x + a<sub>2</sub>x&sup2; + a<sub>3</sub>x&sup3; ...
+ *
+ * Uses Horner's rule A(x) = a<sub>0</sub> + x(a<sub>1</sub> + x(a<sub>2</sub> + x(a<sub>3</sub> + ...)))
+ * Params:
+ *	A =	array of coefficients a<sub>0</sub>, a<sub>1</sub>, etc.
+ */ 
+real poly(real x, real[] A)
+in
+{
+    assert(A.length > 0);
+}
+body
+{
+    version (D_InlineAsm_X86)
+    {
+	version (Windows)
+	{
+	    asm	// assembler by W. Bright
+	    {
+		// EDX = (A.length - 1) * real.sizeof
+		mov     ECX,A[EBP]		; // ECX = A.length
+		dec     ECX			;
+		lea     EDX,[ECX][ECX*8]	;
+		add     EDX,ECX			;
+		add     EDX,A+4[EBP]		;
+		fld     real ptr [EDX]		; // ST0 = coeff[ECX]
+		jecxz   return_ST		;
+		fld     x[EBP]			; // ST0 = x
+		fxch    ST(1)			; // ST1 = x, ST0 = r
+		align   4			;
+	L2:     fmul    ST,ST(1)		; // r *= x
+		fld     real ptr -10[EDX]	;
+		sub     EDX,10			; // deg--
+		faddp   ST(1),ST		;
+		dec     ECX			;
+		jne     L2			;
+		fxch    ST(1)			; // ST1 = r, ST0 = x
+		fstp    ST(0)			; // dump x
+		align   4			;
+	return_ST:				;
+		;
+	    }
+	}
+	else
+	{
+	    asm	// assembler by W. Bright
+	    {
+		// EDX = (A.length - 1) * real.sizeof
+		mov     ECX,A[EBP]		; // ECX = A.length
+		dec     ECX			;
+		lea     EDX,[ECX*8]		;
+		lea	EDX,[EDX][ECX*4]	;
+		add     EDX,A+4[EBP]		;
+		fld     real ptr [EDX]		; // ST0 = coeff[ECX]
+		jecxz   return_ST		;
+		fld     x[EBP]			; // ST0 = x
+		fxch    ST(1)			; // ST1 = x, ST0 = r
+		align   4			;
+	L2:     fmul    ST,ST(1)		; // r *= x
+		fld     real ptr -12[EDX]	;
+		sub     EDX,12			; // deg--
+		faddp   ST(1),ST		;
+		dec     ECX			;
+		jne     L2			;
+		fxch    ST(1)			; // ST1 = r, ST0 = x
+		fstp    ST(0)			; // dump x
+		align   4			;
+	return_ST:				;
+		;
+	    }
+	}
+    }
+    else
+    {
+	int i = A.length - 1;
+	real r = A[i];
+	while (--i >= 0)
+	{
+	    r *= x;
+	    r += A[i];
+	}
+	return r;
+    }
+}
+
+unittest
+{
+    debug (math) printf("math.poly.unittest\n");
+    real x = 3.1;
+    static real pp[] = [56.1, 32.7, 6];
+
+    assert( poly(x, pp) == (56.1L + (32.7L + 6L * x) * x) );
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lphobos/std/string.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,4083 @@
+
+// Written in the D programming language.
+
+/**
+ * String handling functions.
+ *
+ * To copy or not to copy?
+ * When a function takes a string as a parameter, and returns a string,
+ * is that string the same as the input string, modified in place, or
+ * is it a modified copy of the input string? The D array convention is
+ * "copy-on-write". This means that if no modifications are done, the
+ * original string (or slices of it) can be returned. If any modifications
+ * are done, the returned string is a copy.
+ *
+ * Macros:
+ *	WIKI = Phobos/StdString
+ * Copyright:
+ *	Public Domain
+ */
+
+/* Author:
+ *	Walter Bright, Digital Mars, www.digitalmars.com
+ */
+
+// The code is not optimized for speed, that will have to wait
+// until the design is solidified.
+
+module std.string;
+
+//debug=string;		// uncomment to turn on debugging printf's
+
+//private import std.stdio;
+private import std.c.stdio;
+private import std.c.stdlib;
+private import std.c.string;
+private import std.utf;
+private import std.uni;
+private import std.array;
+private import std.format;
+private import std.ctype;
+private import std.stdarg;
+
+extern (C)
+{
+
+    size_t wcslen(wchar *);
+    int wcscmp(wchar *, wchar *);
+}
+
+/* ************* Exceptions *************** */
+
+/// Thrown on errors in string functions.
+class StringException : Exception
+{
+    this(char[] msg)	/// Constructor
+    {
+	super(msg);
+    }
+}
+
+/* ************* Constants *************** */
+
+const char[16] hexdigits = "0123456789ABCDEF";			/// 0..9A..F
+const char[10] digits    = "0123456789";			/// 0..9
+const char[8]  octdigits = "01234567";				/// 0..7
+const char[26] lowercase = "abcdefghijklmnopqrstuvwxyz";	/// a..z
+const char[26] uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";	/// A..Z
+const char[52] letters   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+			   "abcdefghijklmnopqrstuvwxyz";	/// A..Za..z
+const char[6] whitespace = " \t\v\r\n\f";			/// ASCII whitespace
+
+const dchar LS = '\u2028';	/// UTF line separator
+const dchar PS = '\u2029';	/// UTF paragraph separator
+
+/// Newline sequence for this system
+version (Windows)
+    const char[2] newline = "\r\n";
+else version (linux)
+    const char[1] newline = "\n";
+
+/**********************************
+ * Returns true if c is whitespace
+ */
+
+bool iswhite(dchar c)
+{
+    return (c <= 0x7F)
+		? find(whitespace, c) != -1
+		: (c == PS || c == LS);
+}
+
+/*********************************
+ * Convert string to integer.
+ */
+
+long atoi(char[] s)
+{
+    return std.c.stdlib.atoi(toStringz(s));
+}
+
+/*************************************
+ * Convert string to real.
+ */
+
+real atof(char[] s)
+{   char* endptr;
+
+    auto result = strtold(toStringz(s), &endptr);
+    return result;
+}
+
+/**********************************
+ * Compare two strings. cmp is case sensitive, icmp is case insensitive.
+ * Returns:
+ *	<table border=1 cellpadding=4 cellspacing=0>
+ *	$(TR $(TD < 0)	$(TD s1 < s2))
+ *	$(TR $(TD = 0)	$(TD s1 == s2))
+ *	$(TR $(TD > 0)	$(TD s1 > s2))
+ *	</table>
+ */
+
+int cmp(char[] s1, char[] s2)
+{
+    auto len = s1.length;
+    int result;
+
+    //printf("cmp('%.*s', '%.*s')\n", s1, s2);
+    if (s2.length < len)
+	len = s2.length;
+    result = memcmp(s1.ptr, s2.ptr, len);
+    if (result == 0)
+	result = cast(int)s1.length - cast(int)s2.length;
+    return result;
+}
+
+/*********************************
+ * ditto
+ */
+
+int icmp(char[] s1, char[] s2)
+{
+    auto len = s1.length;
+    int result;
+
+    if (s2.length < len)
+	len = s2.length;
+    version (Win32)
+    {
+	result = memicmp(s1.ptr, s2.ptr, len);
+    }
+    version (linux)
+    {
+	for (size_t i = 0; i < len; i++)
+	{
+	    if (s1[i] != s2[i])
+	    {
+		char c1 = s1[i];
+		char c2 = s2[i];
+
+		if (c1 >= 'A' && c1 <= 'Z')
+		    c1 += cast(int)'a' - cast(int)'A';
+		if (c2 >= 'A' && c2 <= 'Z')
+		    c2 += cast(int)'a' - cast(int)'A';
+		result = cast(int)c1 - cast(int)c2;
+		if (result)
+		    break;
+	    }
+	}
+    }
+    if (result == 0)
+	result = cast(int)s1.length - cast(int)s2.length;
+    return result;
+}
+
+unittest
+{
+    int result;
+
+    debug(string) printf("string.cmp.unittest\n");
+    result = icmp("abc", "abc");
+    assert(result == 0);
+    result = icmp(null, null);
+    assert(result == 0);
+    result = icmp("", "");
+    assert(result == 0);
+    result = icmp("abc", "abcd");
+    assert(result < 0);
+    result = icmp("abcd", "abc");
+    assert(result > 0);
+    result = icmp("abc", "abd");
+    assert(result < 0);
+    result = icmp("bbc", "abc");
+    assert(result > 0);
+}
+
+/* ********************************
+ * Converts a D array of chars to a C-style 0 terminated string.
+ * Deprecated: replaced with toStringz().
+ */
+
+deprecated char* toCharz(char[] s)
+{
+    return toStringz(s);
+}
+
+/*********************************
+ * Convert array of chars s[] to a C-style 0 terminated string.
+ */
+
+char* toStringz(char[] s)
+    in
+    {
+    }
+    out (result)
+    {
+	if (result)
+	{   assert(strlen(result) == s.length);
+	    assert(memcmp(result, s.ptr, s.length) == 0);
+	}
+    }
+    body
+    {
+	char[] copy;
+
+	if (s.length == 0)
+	    return "";
+
+	/+ Unfortunately, this isn't reliable.
+	   We could make this work if string literals are put
+	   in read-only memory and we test if s[] is pointing into
+	   that.
+
+	    /* Peek past end of s[], if it's 0, no conversion necessary.
+	     * Note that the compiler will put a 0 past the end of static
+	     * strings, and the storage allocator will put a 0 past the end
+	     * of newly allocated char[]'s.
+	     */
+	    char* p = &s[0] + s.length;
+	    if (*p == 0)
+		return s;
+	+/
+
+	// Need to make a copy
+	copy = new char[s.length + 1];
+	copy[0..s.length] = s;
+	copy[s.length] = 0;
+	return copy.ptr;
+    }
+
+unittest
+{
+    debug(string) printf("string.toStringz.unittest\n");
+
+    char* p = toStringz("foo");
+    assert(strlen(p) == 3);
+    char foo[] = "abbzxyzzy";
+    p = toStringz(foo[3..5]);
+    assert(strlen(p) == 2);
+
+    char[] test = "";
+    p = toStringz(test);
+    assert(*p == 0);
+}
+
+/******************************************
+ * find, ifind _find first occurrence of c in string s.
+ * rfind, irfind _find last occurrence of c in string s.
+ *
+ * find, rfind are case sensitive; ifind, irfind are case insensitive.
+ * Returns:
+ *	Index in s where c is found, -1 if not found.
+ */
+
+int find(char[] s, dchar c)
+{
+    if (c <= 0x7F)
+    {	// Plain old ASCII
+	auto p = cast(char*)memchr(s.ptr, c, s.length);
+	if (p)
+	    return p - cast(char *)s;
+	else
+	    return -1;
+    }
+
+    // c is a universal character
+    foreach (int i, dchar c2; s)
+    {
+	if (c == c2)
+	    return i;
+    }
+    return -1;
+}
+
+unittest
+{
+    debug(string) printf("string.find.unittest\n");
+
+    int i;
+
+    i = find(null, cast(dchar)'a');
+    assert(i == -1);
+    i = find("def", cast(dchar)'a');
+    assert(i == -1);
+    i = find("abba", cast(dchar)'a');
+    assert(i == 0);
+    i = find("def", cast(dchar)'f');
+    assert(i == 2);
+}
+
+
+/******************************************
+ * ditto
+ */
+
+int ifind(char[] s, dchar c)
+{
+    char* p;
+
+    if (c <= 0x7F)
+    {	// Plain old ASCII
+	char c1 = cast(char) std.ctype.tolower(c);
+
+	foreach (int i, char c2; s)
+	{
+	    c2 = cast(char)std.ctype.tolower(c2);
+	    if (c1 == c2)
+		return i;
+	}
+    }
+    else
+    {	// c is a universal character
+	dchar c1 = std.uni.toUniLower(c);
+
+	foreach (int i, dchar c2; s)
+	{
+	    c2 = std.uni.toUniLower(c2);
+	    if (c1 == c2)
+		return i;
+	}
+    }
+    return -1;
+}
+
+unittest
+{
+    debug(string) printf("string.ifind.unittest\n");
+
+    int i;
+
+    i = ifind(null, cast(dchar)'a');
+    assert(i == -1);
+    i = ifind("def", cast(dchar)'a');
+    assert(i == -1);
+    i = ifind("Abba", cast(dchar)'a');
+    assert(i == 0);
+    i = ifind("def", cast(dchar)'F');
+    assert(i == 2);
+
+    char[] sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+
+    i = ifind("def", cast(char)'f');
+    assert(i == 2);
+
+    i = ifind(sPlts, cast(char)'P');
+    assert(i == 23);
+    i = ifind(sPlts, cast(char)'R');
+    assert(i == 2);
+}
+
+
+/******************************************
+ * ditto
+ */
+
+int rfind(char[] s, dchar c)
+{
+    size_t i;
+
+    if (c <= 0x7F)
+    {	// Plain old ASCII
+	for (i = s.length; i-- != 0;)
+	{
+	    if (s[i] == c)
+		break;
+	}
+	return i;
+    }
+
+    // c is a universal character
+    char[4] buf;
+    char[] t;
+    t = std.utf.toUTF8(buf, c);
+    return rfind(s, t);
+}
+
+unittest
+{
+    debug(string) printf("string.rfind.unittest\n");
+
+    int i;
+
+    i = rfind(null, cast(dchar)'a');
+    assert(i == -1);
+    i = rfind("def", cast(dchar)'a');
+    assert(i == -1);
+    i = rfind("abba", cast(dchar)'a');
+    assert(i == 3);
+    i = rfind("def", cast(dchar)'f');
+    assert(i == 2);
+}
+
+/******************************************
+ * ditto
+ */
+
+int irfind(char[] s, dchar c)
+{
+    size_t i;
+
+    if (c <= 0x7F)
+    {	// Plain old ASCII
+	char c1 = cast(char) std.ctype.tolower(c);
+
+	for (i = s.length; i-- != 0;)
+	{   char c2 = s[i];
+
+	    c2 = cast(char) std.ctype.tolower(c2);
+	    if (c1 == c2)
+		break;
+	}
+    }
+    else
+    {	// c is a universal character
+	dchar c1 = std.uni.toUniLower(c);
+
+	for (i = s.length; i-- != 0;)
+	{   char cx = s[i];
+
+	    if (cx <= 0x7F)
+		continue;		// skip, since c is not ASCII
+	    if ((cx & 0xC0) == 0x80)
+		continue;		// skip non-starting UTF-8 chars
+
+	    size_t j = i;
+	    dchar c2 = std.utf.decode(s, j);
+	    c2 = std.uni.toUniLower(c2);
+	    if (c1 == c2)
+		break;
+	}
+    }
+    return i;
+}
+
+unittest
+{
+    debug(string) printf("string.irfind.unittest\n");
+
+    int i;
+
+    i = irfind(null, cast(dchar)'a');
+    assert(i == -1);
+    i = irfind("def", cast(dchar)'a');
+    assert(i == -1);
+    i = irfind("AbbA", cast(dchar)'a');
+    assert(i == 3);
+    i = irfind("def", cast(dchar)'F');
+    assert(i == 2);
+
+    char[] sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+
+    i = irfind("def", cast(char)'f');
+    assert(i == 2);
+
+    i = irfind(sPlts, cast(char)'M');
+    assert(i == 34);
+    i = irfind(sPlts, cast(char)'S');
+    assert(i == 40);
+}
+
+
+/******************************************
+ * find, ifind _find first occurrence of sub[] in string s[].
+ * rfind, irfind _find last occurrence of sub[] in string s[].
+ *
+ * find, rfind are case sensitive; ifind, irfind are case insensitive.
+ * Returns:
+ *	Index in s where c is found, -1 if not found.
+ */
+
+int find(char[] s, char[] sub)
+    out (result)
+    {
+	if (result == -1)
+	{
+	}
+	else
+	{
+	    assert(0 <= result && result < s.length - sub.length + 1);
+	    assert(memcmp(&s[result], sub.ptr, sub.length) == 0);
+	}
+    }
+    body
+    {
+	auto sublength = sub.length;
+
+	if (sublength == 0)
+	    return 0;
+
+	if (s.length >= sublength)
+	{
+	    auto c = sub[0];
+	    if (sublength == 1)
+	    {
+		auto p = cast(char*)memchr(s.ptr, c, s.length);
+		if (p)
+		    return p - &s[0];
+	    }
+	    else
+	    {
+		size_t imax = s.length - sublength + 1;
+
+		// Remainder of sub[]
+		char *q = &sub[1];
+		sublength--;
+
+		for (size_t i = 0; i < imax; i++)
+		{
+		    char *p = cast(char*)memchr(&s[i], c, imax - i);
+		    if (!p)
+			break;
+		    i = p - &s[0];
+		    if (memcmp(p + 1, q, sublength) == 0)
+			return i;
+		}
+	    }
+	}
+	return -1;
+    }
+
+
+unittest
+{
+    debug(string) printf("string.find.unittest\n");
+
+    int i;
+
+    i = find(null, "a");
+    assert(i == -1);
+    i = find("def", "a");
+    assert(i == -1);
+    i = find("abba", "a");
+    assert(i == 0);
+    i = find("def", "f");
+    assert(i == 2);
+    i = find("dfefffg", "fff");
+    assert(i == 3);
+    i = find("dfeffgfff", "fff");
+    assert(i == 6);
+}
+
+/******************************************
+ * ditto
+ */
+
+int ifind(char[] s, char[] sub)
+    out (result)
+    {
+	if (result == -1)
+	{
+	}
+	else
+	{
+	    assert(0 <= result && result < s.length - sub.length + 1);
+	    assert(icmp(s[result .. result + sub.length], sub) == 0);
+	}
+    }
+    body
+    {
+	auto sublength = sub.length;
+	int i;
+
+	if (sublength == 0)
+	    return 0;
+
+	if (s.length < sublength)
+	    return -1;
+
+	auto c = sub[0];
+	if (sublength == 1)
+	{
+	    i = ifind(s, c);
+	}
+	else if (c <= 0x7F)
+	{
+	    size_t imax = s.length - sublength + 1;
+
+	    // Remainder of sub[]
+	    char[] subn = sub[1 .. sublength];
+
+	    for (i = 0; i < imax; i++)
+	    {
+		auto j = ifind(s[i .. imax], c);
+		if (j == -1)
+		    return -1;
+		i += j;
+		if (icmp(s[i + 1 .. i + sublength], subn) == 0)
+		    return i;
+	    }
+	    i = -1;
+	}
+	else
+	{
+	    size_t imax = s.length - sublength;
+
+	    for (i = 0; i <= imax; i++)
+	    {
+		if (icmp(s[i .. i + sublength], sub) == 0)
+		    return i;
+	    }
+	    i = -1;
+	}
+	return i;
+    }
+
+
+unittest
+{
+    debug(string) printf("string.ifind.unittest\n");
+
+    int i;
+
+    i = ifind(null, "a");
+    assert(i == -1);
+    i = ifind("def", "a");
+    assert(i == -1);
+    i = ifind("abba", "a");
+    assert(i == 0);
+    i = ifind("def", "f");
+    assert(i == 2);
+    i = ifind("dfefffg", "fff");
+    assert(i == 3);
+    i = ifind("dfeffgfff", "fff");
+    assert(i == 6);
+
+    char[] sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+    char[] sMars = "Who\'s \'My Favorite Maritian?\'";
+
+    i = ifind(sMars, "MY fAVe");
+    assert(i == -1);
+    i = ifind(sMars, "mY fAVOriTe");
+    assert(i == 7);
+    i = ifind(sPlts, "mArS:");
+    assert(i == 0);
+    i = ifind(sPlts, "rOcK");
+    assert(i == 17);
+    i = ifind(sPlts, "Un.");
+    assert(i == 41);
+    i = ifind(sPlts, sPlts);
+    assert(i == 0);
+
+    i = ifind("\u0100", "\u0100");
+    assert(i == 0);
+
+    // Thanks to Carlos Santander B. and zwang
+    i = ifind("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
+	"page-break-before");
+    assert(i == -1);
+}
+
+/******************************************
+ * ditto
+ */
+
+int rfind(char[] s, char[] sub)
+    out (result)
+    {
+	if (result == -1)
+	{
+	}
+	else
+	{
+	    assert(0 <= result && result < s.length - sub.length + 1);
+	    assert(memcmp(&s[0] + result, sub.ptr, sub.length) == 0);
+	}
+    }
+    body
+    {
+	char c;
+
+	if (sub.length == 0)
+	    return s.length;
+	c = sub[0];
+	if (sub.length == 1)
+	    return rfind(s, c);
+	for (int i = s.length - sub.length; i >= 0; i--)
+	{
+	    if (s[i] == c)
+	    {
+		if (memcmp(&s[i + 1], &sub[1], sub.length - 1) == 0)
+		    return i;
+	    }
+	}
+	return -1;
+    }
+
+unittest
+{
+    int i;
+
+    debug(string) printf("string.rfind.unittest\n");
+    i = rfind("abcdefcdef", "c");
+    assert(i == 6);
+    i = rfind("abcdefcdef", "cd");
+    assert(i == 6);
+    i = rfind("abcdefcdef", "x");
+    assert(i == -1);
+    i = rfind("abcdefcdef", "xy");
+    assert(i == -1);
+    i = rfind("abcdefcdef", "");
+    assert(i == 10);
+}
+
+
+/******************************************
+ * ditto
+ */
+
+int irfind(char[] s, char[] sub)
+    out (result)
+    {
+	if (result == -1)
+	{
+	}
+	else
+	{
+	    assert(0 <= result && result < s.length - sub.length + 1);
+	    assert(icmp(s[result .. result + sub.length], sub) == 0);
+	}
+    }
+    body
+    {
+	dchar c;
+
+	if (sub.length == 0)
+	    return s.length;
+	c = sub[0];
+	if (sub.length == 1)
+	    return irfind(s, c);
+	if (c <= 0x7F)
+	{
+	    c = std.ctype.tolower(c);
+	    for (int i = s.length - sub.length; i >= 0; i--)
+	    {
+		if (std.ctype.tolower(s[i]) == c)
+		{
+		    if (icmp(s[i + 1 .. i + sub.length], sub[1 .. sub.length]) == 0)
+			return i;
+		}
+	    }
+	}
+	else
+	{
+	    for (int i = s.length - sub.length; i >= 0; i--)
+	    {
+		if (icmp(s[i .. i + sub.length], sub) == 0)
+		    return i;
+	    }
+	}
+	return -1;
+    }
+
+unittest
+{
+    int i;
+
+    debug(string) printf("string.irfind.unittest\n");
+    i = irfind("abcdefCdef", "c");
+    assert(i == 6);
+    i = irfind("abcdefCdef", "cD");
+    assert(i == 6);
+    i = irfind("abcdefcdef", "x");
+    assert(i == -1);
+    i = irfind("abcdefcdef", "xy");
+    assert(i == -1);
+    i = irfind("abcdefcdef", "");
+    assert(i == 10);
+
+    char[] sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
+    char[] sMars = "Who\'s \'My Favorite Maritian?\'";
+    
+    i = irfind("abcdefcdef", "c");
+    assert(i == 6);
+    i = irfind("abcdefcdef", "cd");
+    assert(i == 6);
+    i = irfind( "abcdefcdef", "def" );
+    assert(i == 7);
+    
+    i = irfind(sMars, "RiTE maR");
+    assert(i == 14);
+    i = irfind(sPlts, "FOuRTh");
+    assert(i == 10);
+    i = irfind(sMars, "whO\'s \'MY");
+    assert(i == 0);
+    i = irfind(sMars, sMars);
+    assert(i == 0);
+}
+
+
+/************************************
+ * Convert string s[] to lower case.
+ */
+
+string tolower(string s)
+{
+    int changed;
+    char[] r;
+
+    for (size_t i = 0; i < s.length; i++)
+    {
+	auto c = s[i];
+	if ('A' <= c && c <= 'Z')
+	{
+	    if (!changed)
+	    {
+		r = s.dup;
+		changed = 1;
+	    }
+	    r[i] = cast(char) (c + (cast(char)'a' - 'A'));
+	}
+	else if (c > 0x7F)
+	{
+	    foreach (size_t j, dchar dc; s[i .. length])
+	    {
+		if (std.uni.isUniUpper(dc))
+		{
+		    dc = std.uni.toUniLower(dc);
+		    if (!changed)
+		    {
+			r = s[0 .. i + j].dup;
+			changed = 2;
+		    }
+		}
+		if (changed)
+		{
+		    if (changed == 1)
+		    {	r = r[0 .. i + j];
+			changed = 2;
+		    }
+		    std.utf.encode(r, dc);
+		}
+	    }
+	    break;
+	}
+    }
+    return changed ? r : s;
+}
+
+unittest
+{
+    debug(string) printf("string.tolower.unittest\n");
+
+    char[] s1 = "FoL";
+    char[] s2;
+
+    s2 = tolower(s1);
+    assert(cmp(s2, "fol") == 0);
+    assert(s2 != s1);
+
+    s1 = "A\u0100B\u0101d";
+    s2 = tolower(s1);
+    assert(cmp(s2, "a\u0101b\u0101d") == 0);
+    assert(s2 !is s1);
+
+    s1 = "A\u0460B\u0461d";
+    s2 = tolower(s1);
+    assert(cmp(s2, "a\u0461b\u0461d") == 0);
+    assert(s2 !is s1);
+
+    s1 = "\u0130";
+    s2 = tolower(s1);
+    assert(s2 == "i");
+    assert(s2 !is s1);
+}
+
+/************************************
+ * Convert string s[] to upper case.
+ */
+
+string toupper(string s)
+{
+    int changed;
+    char[] r;
+
+    for (size_t i = 0; i < s.length; i++)
+    {
+	auto c = s[i];
+	if ('a' <= c && c <= 'z')
+	{
+	    if (!changed)
+	    {
+		r = s.dup;
+		changed = 1;
+	    }
+	    r[i] = cast(char) (c - (cast(char)'a' - 'A'));
+	}
+	else if (c > 0x7F)
+	{
+	    foreach (size_t j, dchar dc; s[i .. length])
+	    {
+		if (std.uni.isUniLower(dc))
+		{
+		    dc = std.uni.toUniUpper(dc);
+		    if (!changed)
+		    {
+			r = s[0 .. i + j].dup;
+			changed = 2;
+		    }
+		}
+		if (changed)
+		{
+		    if (changed == 1)
+		    {	r = r[0 .. i + j];
+			changed = 2;
+		    }
+		    std.utf.encode(r, dc);
+		}
+	    }
+	    break;
+	}
+    }
+    return changed ? r : s;
+}
+
+unittest
+{
+    debug(string) printf("string.toupper.unittest\n");
+
+    char[] s1 = "FoL";
+    char[] s2;
+
+    s2 = toupper(s1);
+    assert(cmp(s2, "FOL") == 0);
+    assert(s2 !is s1);
+
+    s1 = "a\u0100B\u0101d";
+    s2 = toupper(s1);
+    assert(cmp(s2, "A\u0100B\u0100D") == 0);
+    assert(s2 !is s1);
+
+    s1 = "a\u0460B\u0461d";
+    s2 = toupper(s1);
+    assert(cmp(s2, "A\u0460B\u0460D") == 0);
+    assert(s2 !is s1);
+}
+
+
+/********************************************
+ * Capitalize first character of string s[], convert rest of string s[]
+ * to lower case.
+ */
+
+char[] capitalize(char[] s)
+{
+    int changed;
+    int i;
+    char[] r = s;
+
+    changed = 0;
+
+    foreach (size_t i, dchar c; s)
+    {	dchar c2;
+
+	if (i == 0)
+	{
+	    c2 = std.uni.toUniUpper(c);
+	    if (c != c2)
+	    {
+		changed = 1;
+		r = null;
+	    }
+	}
+	else
+	{
+	    c2 = std.uni.toUniLower(c);
+	    if (c != c2)
+	    {
+		if (!changed)
+		{   changed = 1;
+		    r = s[0 .. i].dup;
+		}
+	    }
+	}
+	if (changed)
+	    std.utf.encode(r, c2);
+    }
+    return r;
+}
+
+
+unittest
+{
+    debug(string) printf("string.toupper.capitalize\n");
+
+    char[] s1 = "FoL";
+    char[] s2;
+
+    s2 = capitalize(s1);
+    assert(cmp(s2, "Fol") == 0);
+    assert(s2 !is s1);
+
+    s2 = capitalize(s1[0 .. 2]);
+    assert(cmp(s2, "Fo") == 0);
+    assert(s2.ptr == s1.ptr);
+
+    s1 = "fOl";
+    s2 = capitalize(s1);
+    assert(cmp(s2, "Fol") == 0);
+    assert(s2 !is s1);
+}
+
+
+/********************************************
+ * Capitalize all words in string s[].
+ * Remove leading and trailing whitespace.
+ * Replace all sequences of whitespace with a single space.
+ */
+
+char[] capwords(char[] s)
+{
+    char[] r;
+    bool inword = false;
+    size_t istart = 0;
+    size_t i;
+
+    for (i = 0; i < s.length; i++)
+    {
+	switch (s[i])
+	{
+	    case ' ':
+	    case '\t':
+	    case '\f':
+	    case '\r':
+	    case '\n':
+	    case '\v':
+		if (inword)
+		{
+		    r ~= capitalize(s[istart .. i]);
+		    inword = false;
+		}
+		break;
+
+	    default:
+		if (!inword)
+		{
+		    if (r.length)
+			r ~= ' ';
+		    istart = i;
+		    inword = true;
+		}
+		break;
+	}
+    }
+    if (inword)
+    {
+	r ~= capitalize(s[istart .. i]);
+    }
+
+    return r;
+}
+
+
+unittest
+{
+    debug(string) printf("string.capwords.unittest\n");
+
+    char[] s1 = "\tfoo abc(aD)*  \t  (q PTT  ";
+    char[] s2;
+
+    s2 = capwords(s1);
+    //writefln("s2 = '%s'", s2);
+    assert(cmp(s2, "Foo Abc(ad)* (q Ptt") == 0);
+}
+
+/********************************************
+ * Return a string that consists of s[] repeated n times.
+ */
+
+char[] repeat(char[] s, size_t n)
+{
+    if (n == 0)
+	return null;
+    if (n == 1)
+	return s;
+    char[] r = new char[n * s.length];
+    if (s.length == 1)
+	r[] = s[0];
+    else
+    {	auto len = s.length;
+
+	for (size_t i = 0; i < n * len; i += len)
+	{
+	    r[i .. i + len] = s[];
+	}
+    }
+    return r;
+}
+
+
+unittest
+{
+    debug(string) printf("string.repeat.unittest\n");
+
+    char[] s;
+
+    s = repeat("1234", 0);
+    assert(s is null);
+    s = repeat("1234", 1);
+    assert(cmp(s, "1234") == 0);
+    s = repeat("1234", 2);
+    assert(cmp(s, "12341234") == 0);
+    s = repeat("1", 4);
+    assert(cmp(s, "1111") == 0);
+    s = repeat(null, 4);
+    assert(s is null);
+}
+
+
+/********************************************
+ * Concatenate all the strings in words[] together into one
+ * string; use sep[] as the separator.
+ */
+
+char[] join(char[][] words, char[] sep)
+{
+    char[] result;
+
+    if (words.length)
+    {
+	size_t len = 0;
+	size_t i;
+
+	for (i = 0; i < words.length; i++)
+	    len += words[i].length;
+
+	auto seplen = sep.length;
+	len += (words.length - 1) * seplen;
+
+	result = new char[len];
+
+	size_t j;
+	i = 0;
+	while (true)
+	{
+	    uint wlen = words[i].length;
+
+	    result[j .. j + wlen] = words[i];
+	    j += wlen;
+	    i++;
+	    if (i >= words.length)
+		break;
+	    result[j .. j + seplen] = sep;
+	    j += seplen;
+	}
+	assert(j == len);
+    }
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.join.unittest\n");
+
+    char[] word1 = "peter";
+    char[] word2 = "paul";
+    char[] word3 = "jerry";
+    char[][3] words;
+    char[] r;
+    int i;
+
+    words[0] = word1;
+    words[1] = word2;
+    words[2] = word3;
+    r = join(words, ",");
+    i = cmp(r, "peter,paul,jerry");
+    assert(i == 0);
+}
+
+
+/**************************************
+ * Split s[] into an array of words,
+ * using whitespace as the delimiter.
+ */
+
+char[][] split(char[] s)
+{
+    size_t i;
+    size_t istart = 0;
+    bool inword = false;
+    char[][] words;
+
+    for (i = 0; i < s.length; i++)
+    {
+	switch (s[i])
+	{
+	    case ' ':
+	    case '\t':
+	    case '\f':
+	    case '\r':
+	    case '\n':
+	    case '\v':
+		if (inword)
+		{
+		    words ~= s[istart .. i];
+		    inword = false;
+		}
+		break;
+
+	    default:
+		if (!inword)
+		{   istart = i;
+		    inword = true;
+		}
+		break;
+	}
+    }
+    if (inword)
+	words ~= s[istart .. i];
+    return words;
+}
+
+unittest
+{
+    debug(string) printf("string.split1\n");
+
+    char[] s = " peter paul\tjerry ";
+    char[][] words;
+    int i;
+
+    words = split(s);
+    assert(words.length == 3);
+    i = cmp(words[0], "peter");
+    assert(i == 0);
+    i = cmp(words[1], "paul");
+    assert(i == 0);
+    i = cmp(words[2], "jerry");
+    assert(i == 0);
+}
+
+
+/**************************************
+ * Split s[] into an array of words,
+ * using delim[] as the delimiter.
+ */
+
+char[][] split(char[] s, char[] delim)
+    in
+    {
+	assert(delim.length > 0);
+    }
+    body
+    {
+	size_t i;
+	size_t j;
+	char[][] words;
+
+	i = 0;
+	if (s.length)
+	{
+	    if (delim.length == 1)
+	    {	char c = delim[0];
+		size_t nwords = 0;
+		char* p = &s[0];
+		char* pend = p + s.length;
+
+		while (true)
+		{
+		    nwords++;
+		    p = cast(char*)memchr(p, c, pend - p);
+		    if (!p)
+			break;
+		    p++;
+		    if (p == pend)
+		    {	nwords++;
+			break;
+		    }
+		}
+		words.length = nwords;
+
+		int wordi = 0;
+		i = 0;
+		while (true)
+		{
+		    p = cast(char*)memchr(&s[i], c, s.length - i);
+		    if (!p)
+		    {
+			words[wordi] = s[i .. s.length];
+			break;
+		    }
+		    j = p - &s[0];
+		    words[wordi] = s[i .. j];
+		    wordi++;
+		    i = j + 1;
+		    if (i == s.length)
+		    {
+			words[wordi] = "";
+			break;
+		    }
+		}
+		assert(wordi + 1 == nwords);
+	    }
+	    else
+	    {	size_t nwords = 0;
+
+		while (true)
+		{
+		    nwords++;
+		    j = find(s[i .. s.length], delim);
+		    if (j == -1)
+			break;
+		    i += j + delim.length;
+		    if (i == s.length)
+		    {	nwords++;
+			break;
+		    }
+		    assert(i < s.length);
+		}
+		words.length = nwords;
+
+		int wordi = 0;
+		i = 0;
+		while (true)
+		{
+		    j = find(s[i .. s.length], delim);
+		    if (j == -1)
+		    {
+			words[wordi] = s[i .. s.length];
+			break;
+		    }
+		    words[wordi] = s[i .. i + j];
+		    wordi++;
+		    i += j + delim.length;
+		    if (i == s.length)
+		    {
+			words[wordi] = "";
+			break;
+		    }
+		    assert(i < s.length);
+		}
+		assert(wordi + 1 == nwords);
+	    }
+	}
+	return words;
+    }
+
+unittest
+{
+    debug(string) printf("string.split2\n");
+
+    char[] s = ",peter,paul,jerry,";
+    char[][] words;
+    int i;
+
+    words = split(s, ",");
+    assert(words.length == 5);
+    i = cmp(words[0], "");
+    assert(i == 0);
+    i = cmp(words[1], "peter");
+    assert(i == 0);
+    i = cmp(words[2], "paul");
+    assert(i == 0);
+    i = cmp(words[3], "jerry");
+    assert(i == 0);
+    i = cmp(words[4], "");
+    assert(i == 0);
+
+    s = s[0 .. s.length - 1];	// lop off trailing ','
+    words = split(s, ",");
+    assert(words.length == 4);
+    i = cmp(words[3], "jerry");
+    assert(i == 0);
+
+    s = s[1 .. s.length];	// lop off leading ','
+    words = split(s, ",");
+    assert(words.length == 3);
+    i = cmp(words[0], "peter");
+    assert(i == 0);
+
+    char[] s2 = ",,peter,,paul,,jerry,,";
+
+    words = split(s2, ",,");
+    //printf("words.length = %d\n", words.length);
+    assert(words.length == 5);
+    i = cmp(words[0], "");
+    assert(i == 0);
+    i = cmp(words[1], "peter");
+    assert(i == 0);
+    i = cmp(words[2], "paul");
+    assert(i == 0);
+    i = cmp(words[3], "jerry");
+    assert(i == 0);
+    i = cmp(words[4], "");
+    assert(i == 0);
+
+    s2 = s2[0 .. s2.length - 2];	// lop off trailing ',,'
+    words = split(s2, ",,");
+    assert(words.length == 4);
+    i = cmp(words[3], "jerry");
+    assert(i == 0);
+
+    s2 = s2[2 .. s2.length];	// lop off leading ',,'
+    words = split(s2, ",,");
+    assert(words.length == 3);
+    i = cmp(words[0], "peter");
+    assert(i == 0);
+}
+
+
+/**************************************
+ * Split s[] into an array of lines,
+ * using CR, LF, or CR-LF as the delimiter.
+ * The delimiter is not included in the line.
+ */
+
+char[][] splitlines(char[] s)
+{
+    uint i;
+    uint istart;
+    uint nlines;
+    char[][] lines;
+
+    nlines = 0;
+    for (i = 0; i < s.length; i++)
+    {	char c;
+
+	c = s[i];
+	if (c == '\r' || c == '\n')
+	{
+	    nlines++;
+	    istart = i + 1;
+	    if (c == '\r' && i + 1 < s.length && s[i + 1] == '\n')
+	    {
+		i++;
+		istart++;
+	    }
+	}
+    }
+    if (istart != i)
+	nlines++;
+
+    lines = new char[][nlines];
+    nlines = 0;
+    istart = 0;
+    for (i = 0; i < s.length; i++)
+    {	char c;
+
+	c = s[i];
+	if (c == '\r' || c == '\n')
+	{
+	    lines[nlines] = s[istart .. i];
+	    nlines++;
+	    istart = i + 1;
+	    if (c == '\r' && i + 1 < s.length && s[i + 1] == '\n')
+	    {
+		i++;
+		istart++;
+	    }
+	}
+    }
+    if (istart != i)
+    {	lines[nlines] = s[istart .. i];
+	nlines++;
+    }
+
+    assert(nlines == lines.length);
+    return lines;
+}
+
+unittest
+{
+    debug(string) printf("string.splitlines\n");
+
+    char[] s = "\rpeter\n\rpaul\r\njerry\n";
+    char[][] lines;
+    int i;
+
+    lines = splitlines(s);
+    //printf("lines.length = %d\n", lines.length);
+    assert(lines.length == 5);
+    //printf("lines[0] = %llx, '%.*s'\n", lines[0], lines[0]);
+    assert(lines[0].length == 0);
+    i = cmp(lines[1], "peter");
+    assert(i == 0);
+    assert(lines[2].length == 0);
+    i = cmp(lines[3], "paul");
+    assert(i == 0);
+    i = cmp(lines[4], "jerry");
+    assert(i == 0);
+
+    s = s[0 .. s.length - 1];	// lop off trailing \n
+    lines = splitlines(s);
+    //printf("lines.length = %d\n", lines.length);
+    assert(lines.length == 5);
+    i = cmp(lines[4], "jerry");
+    assert(i == 0);
+}
+
+
+/*****************************************
+ * Strips leading or trailing whitespace, or both.
+ */
+
+char[] stripl(char[] s)
+{
+    uint i;
+
+    for (i = 0; i < s.length; i++)
+    {
+	if (!std.ctype.isspace(s[i]))
+	    break;
+    }
+    return s[i .. s.length];
+}
+
+char[] stripr(char[] s) /// ditto
+{
+    uint i;
+
+    for (i = s.length; i > 0; i--)
+    {
+	if (!std.ctype.isspace(s[i - 1]))
+	    break;
+    }
+    return s[0 .. i];
+}
+
+char[] strip(char[] s) /// ditto
+{
+    return stripr(stripl(s));
+}
+
+unittest
+{
+    debug(string) printf("string.strip.unittest\n");
+    char[] s;
+    int i;
+
+    s = strip("  foo\t ");
+    i = cmp(s, "foo");
+    assert(i == 0);
+}
+
+/*******************************************
+ * Returns s[] sans trailing delimiter[], if any.
+ * If delimiter[] is null, removes trailing CR, LF, or CRLF, if any.
+ */
+
+char[] chomp(char[] s, char[] delimiter = null)
+{
+    if (delimiter is null)
+    {   auto len = s.length;
+
+	if (len)
+	{   auto c = s[len - 1];
+
+	    if (c == '\r')			// if ends in CR
+		len--;
+	    else if (c == '\n')			// if ends in LF
+	    {
+		len--;
+		if (len && s[len - 1] == '\r')
+		    len--;			// remove CR-LF
+	    }
+	}
+	return s[0 .. len];
+    }
+    else if (s.length >= delimiter.length)
+    {
+	if (s[length - delimiter.length .. length] == delimiter)
+	    return s[0 .. length - delimiter.length];
+    }
+    return s;
+}
+
+unittest
+{
+    debug(string) printf("string.chomp.unittest\n");
+    char[] s;
+
+    s = chomp(null);
+    assert(s is null);
+    s = chomp("hello");
+    assert(s == "hello");
+    s = chomp("hello\n");
+    assert(s == "hello");
+    s = chomp("hello\r");
+    assert(s == "hello");
+    s = chomp("hello\r\n");
+    assert(s == "hello");
+    s = chomp("hello\n\r");
+    assert(s == "hello\n");
+    s = chomp("hello\n\n");
+    assert(s == "hello\n");
+    s = chomp("hello\r\r");
+    assert(s == "hello\r");
+    s = chomp("hello\nxxx\n");
+    assert(s == "hello\nxxx");
+
+    s = chomp(null, null);
+    assert(s is null);
+    s = chomp("hello", "o");
+    assert(s == "hell");
+    s = chomp("hello", "p");
+    assert(s == "hello");
+    s = chomp("hello", null);
+    assert(s == "hello");
+    s = chomp("hello", "llo");
+    assert(s == "he");
+}
+
+
+/***********************************************
+ * Returns s[] sans trailing character, if there is one.
+ * If last two characters are CR-LF, then both are removed.
+ */
+
+char[] chop(char[] s)
+{   auto len = s.length;
+
+    if (len)
+    {
+	if (len >= 2 && s[len - 1] == '\n' && s[len - 2] == '\r')
+	    return s[0 .. len - 2];
+
+	// If we're in a tail of a UTF-8 sequence, back up
+	while ((s[len - 1] & 0xC0) == 0x80)
+	{
+	    len--;
+	    if (len == 0)
+		throw new std.utf.UtfException("invalid UTF sequence", 0);
+	}
+
+	return s[0 .. len - 1];
+    }
+    return s;
+}
+
+
+unittest
+{
+    debug(string) printf("string.chop.unittest\n");
+    char[] s;
+
+    s = chop(null);
+    assert(s is null);
+    s = chop("hello");
+    assert(s == "hell");
+    s = chop("hello\r\n");
+    assert(s == "hello");
+    s = chop("hello\n\r");
+    assert(s == "hello\n");
+}
+
+
+/*******************************************
+ * Left justify, right justify, or center string s[]
+ * in field width chars wide.
+ */
+
+char[] ljustify(char[] s, int width)
+{
+    if (s.length >= width)
+	return s;
+    char[] r = new char[width];
+    r[0..s.length] = s;
+    r[s.length .. width] = cast(char)' ';
+    return r;
+}
+
+/// ditto
+char[] rjustify(char[] s, int width)
+{
+    if (s.length >= width)
+	return s;
+    char[] r = new char[width];
+    r[0 .. width - s.length] = cast(char)' ';
+    r[width - s.length .. width] = s;
+    return r;
+}
+
+/// ditto
+char[] center(char[] s, int width)
+{
+    if (s.length >= width)
+	return s;
+    char[] r = new char[width];
+    int left = (width - s.length) / 2;
+    r[0 .. left] = cast(char)' ';
+    r[left .. left + s.length] = s;
+    r[left + s.length .. width] = cast(char)' ';
+    return r;
+}
+
+unittest
+{
+    debug(string) printf("string.justify.unittest\n");
+
+    char[] s = "hello";
+    char[] r;
+    int i;
+
+    r = ljustify(s, 8);
+    i = cmp(r, "hello   ");
+    assert(i == 0);
+
+    r = rjustify(s, 8);
+    i = cmp(r, "   hello");
+    assert(i == 0);
+
+    r = center(s, 8);
+    i = cmp(r, " hello  ");
+    assert(i == 0);
+
+    r = zfill(s, 8);
+    i = cmp(r, "000hello");
+    assert(i == 0);
+}
+
+
+/*****************************************
+ * Same as rjustify(), but fill with '0's.
+ */
+
+char[] zfill(char[] s, int width)
+{
+    if (s.length >= width)
+	return s;
+    char[] r = new char[width];
+    r[0 .. width - s.length] = cast(char)'0';
+    r[width - s.length .. width] = s;
+    return r;
+}
+
+/********************************************
+ * Replace occurrences of from[] with to[] in s[].
+ */
+
+char[] replace(char[] s, char[] from, char[] to)
+{
+    char[] p;
+    int i;
+    size_t istart;
+
+    //printf("replace('%.*s','%.*s','%.*s')\n", s, from, to);
+    if (from.length == 0)
+	return s;
+    istart = 0;
+    while (istart < s.length)
+    {
+	i = find(s[istart .. s.length], from);
+	if (i == -1)
+	{
+	    p ~= s[istart .. s.length];
+	    break;
+	}
+	p ~= s[istart .. istart + i];
+	p ~= to;
+	istart += i + from.length;
+    }
+    return p;
+}
+
+unittest
+{
+    debug(string) printf("string.replace.unittest\n");
+
+    char[] s = "This is a foo foo list";
+    char[] from = "foo";
+    char[] to = "silly";
+    char[] r;
+    int i;
+
+    r = replace(s, from, to);
+    i = cmp(r, "This is a silly silly list");
+    assert(i == 0);
+
+    r = replace(s, "", to);
+    i = cmp(r, "This is a foo foo list");
+    assert(i == 0);
+}
+
+/*****************************
+ * Return a _string that is string[] with slice[] replaced by replacement[].
+ */
+
+char[] replaceSlice(char[] string, char[] slice, char[] replacement)
+in
+{
+    // Verify that slice[] really is a slice of string[]
+    int so = cast(char*)slice - cast(char*)string;
+    assert(so >= 0);
+    //printf("string.length = %d, so = %d, slice.length = %d\n", string.length, so, slice.length);
+    assert(string.length >= so + slice.length);
+}
+body
+{
+    char[] result;
+    int so = cast(char*)slice - cast(char*)string;
+
+    result.length = string.length - slice.length + replacement.length;
+
+    result[0 .. so] = string[0 .. so];
+    result[so .. so + replacement.length] = replacement;
+    result[so + replacement.length .. result.length] = string[so + slice.length .. string.length];
+
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.replaceSlice.unittest\n");
+
+    char[] string = "hello";
+    char[] slice = string[2 .. 4];
+
+    char[] r = replaceSlice(string, slice, "bar");
+    int i;
+    i = cmp(r, "hebaro");
+    assert(i == 0);
+}
+
+/**********************************************
+ * Insert sub[] into s[] at location index.
+ */
+
+char[] insert(char[] s, size_t index, char[] sub)
+in
+{
+    assert(0 <= index && index <= s.length);
+}
+body
+{
+    if (sub.length == 0)
+	return s;
+
+    if (s.length == 0)
+	return sub;
+
+    int newlength = s.length + sub.length;
+    char[] result = new char[newlength];
+
+    result[0 .. index] = s[0 .. index];
+    result[index .. index + sub.length] = sub;
+    result[index + sub.length .. newlength] = s[index .. s.length];
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.insert.unittest\n");
+
+    char[] r;
+    int i;
+
+    r = insert("abcd", 0, "e");
+    i = cmp(r, "eabcd");
+    assert(i == 0);
+
+    r = insert("abcd", 4, "e");
+    i = cmp(r, "abcde");
+    assert(i == 0);
+
+    r = insert("abcd", 2, "ef");
+    i = cmp(r, "abefcd");
+    assert(i == 0);
+
+    r = insert(null, 0, "e");
+    i = cmp(r, "e");
+    assert(i == 0);
+
+    r = insert("abcd", 0, null);
+    i = cmp(r, "abcd");
+    assert(i == 0);
+}
+
+/***********************************************
+ * Count up all instances of sub[] in s[].
+ */
+
+size_t count(char[] s, char[] sub)
+{
+    size_t i;
+    int j;
+    int count = 0;
+
+    for (i = 0; i < s.length; i += j + sub.length)
+    {
+	j = find(s[i .. s.length], sub);
+	if (j == -1)
+	    break;
+	count++;
+    }
+    return count;
+}
+
+unittest
+{
+    debug(string) printf("string.count.unittest\n");
+
+    char[] s = "This is a fofofof list";
+    char[] sub = "fof";
+    int i;
+
+    i = count(s, sub);
+    assert(i == 2);
+}
+
+
+/************************************************
+ * Replace tabs with the appropriate number of spaces.
+ * tabsize is the distance between tab stops.
+ */
+
+char[] expandtabs(char[] string, int tabsize = 8)
+{
+    bool changes = false;
+    char[] result = string;
+    int column;
+    int nspaces;
+
+    foreach (size_t i, dchar c; string)
+    {
+	switch (c)
+	{
+	    case '\t':
+		nspaces = tabsize - (column % tabsize);
+		if (!changes)
+		{
+		    changes = true;
+		    result = null;
+		    result.length = string.length + nspaces - 1;
+		    result.length = i + nspaces;
+		    result[0 .. i] = string[0 .. i];
+		    result[i .. i + nspaces] = ' ';
+		}
+		else
+		{   int j = result.length;
+		    result.length = j + nspaces;
+		    result[j .. j + nspaces] = ' ';
+		}
+		column += nspaces;
+		break;
+
+	    case '\r':
+	    case '\n':
+	    case PS:
+	    case LS:
+		column = 0;
+		goto L1;
+
+	    default:
+		column++;
+	    L1:
+		if (changes)
+		{
+		    if (c <= 0x7F)
+			result ~= cast(char)c;
+		    else
+			std.utf.encode(result, c);
+		}
+		break;
+	}
+    }
+
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.expandtabs.unittest\n");
+
+    char[] s = "This \tis\t a fofof\tof list";
+    char[] r;
+    int i;
+
+    r = expandtabs(s, 8);
+    i = cmp(r, "This    is       a fofof        of list");
+    assert(i == 0);
+
+    r = expandtabs(null);
+    assert(r == null);
+    r = expandtabs("");
+    assert(r.length == 0);
+    r = expandtabs("a");
+    assert(r == "a");
+    r = expandtabs("\t");
+    assert(r == "        ");
+    r = expandtabs(  "  ab\tasdf ");
+    //writefln("r = '%s'", r);
+    assert(r == "  ab    asdf ");
+    // TODO: need UTF test case
+}
+
+
+/*******************************************
+ * Replace spaces in string with the optimal number of tabs.
+ * Trailing spaces or tabs in a line are removed.
+ * Params:
+ *	string = String to convert.
+ *	tabsize = Tab columns are tabsize spaces apart. tabsize defaults to 8.
+ */
+
+char[] entab(char[] string, int tabsize = 8)
+{
+    bool changes = false;
+    char[] result = string;
+
+    int nspaces = 0;
+    int nwhite = 0;
+    int column = 0;			// column number
+
+    foreach (size_t i, dchar c; string)
+    {   
+
+	void change()
+	{
+	    changes = true;
+	    result = null;
+	    result.length = string.length;
+	    result.length = i;
+	    result[0 .. i] = string[0 .. i];
+	}
+
+	switch (c)
+	{   
+	    case '\t':
+		nwhite++;
+		if (nspaces)
+		{
+		    if (!changes)
+			change();
+
+		    int j = result.length - nspaces;
+		    int ntabs = (((column - nspaces) % tabsize) + nspaces) / tabsize;
+		    result.length = j + ntabs;
+		    result[j .. j + ntabs] = '\t';
+		    nwhite += ntabs - nspaces;
+		    nspaces = 0;
+		}
+		column = (column + tabsize) / tabsize * tabsize;
+		break;
+
+	    case '\r':
+	    case '\n':
+	    case PS:
+	    case LS:
+		// Truncate any trailing spaces or tabs
+		if (nwhite)
+		{
+		    if (!changes)
+			change();
+		    result = result[0 .. result.length - nwhite];
+		}
+		break;
+
+	    default:
+		if (nspaces >= 2 && (column % tabsize) == 0)
+		{
+		    if (!changes)
+			change();
+
+		    int j = result.length - nspaces;
+		    int ntabs = (nspaces + tabsize - 1) / tabsize;
+		    result.length = j + ntabs;
+		    result[j .. j + ntabs] = '\t';
+		    nwhite += ntabs - nspaces;
+		    nspaces = 0;
+		}
+		if (c == ' ')
+		{   nwhite++;
+		    nspaces++;
+		}
+		else
+		{   nwhite = 0;
+		    nspaces = 0;
+		}
+		column++;
+		break;
+	}
+	if (changes)
+	{
+	    if (c <= 0x7F)
+		result ~= cast(char)c;
+	    else
+		std.utf.encode(result, c);
+	}
+    }
+
+    // Truncate any trailing spaces or tabs
+    if (nwhite)
+	result = result[0 .. result.length - nwhite];
+
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.entab.unittest\n");
+
+    char[] r;
+
+    r = entab(null);
+    assert(r == null);
+    r = entab("");
+    assert(r.length == 0);
+    r = entab("a");
+    assert(r == "a");
+    r = entab("        ");
+    assert(r == "");
+    r = entab("        x");
+    assert(r == "\tx");
+    r = entab("  ab    asdf ");
+    assert(r == "  ab\tasdf");
+    r = entab("  ab     asdf ");
+    assert(r == "  ab\t asdf");
+    r = entab("  ab \t   asdf ");
+    assert(r == "  ab\t   asdf");
+    r = entab("1234567 \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567  \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567   \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567    \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567     \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567      \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567       \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567        \ta");
+    assert(r == "1234567\t\ta");
+    r = entab("1234567         \ta");
+    assert(r == "1234567\t\t\ta");
+    // TODO: need UTF test case
+}
+
+
+
+/************************************
+ * Construct translation table for translate().
+ * BUG: only works with ASCII
+ */
+
+char[] maketrans(char[] from, char[] to)
+    in
+    {
+	assert(from.length == to.length);
+	assert(from.length <= 128);
+	foreach (char c; from)
+	{
+	    assert(c <= 0x7F);
+	}
+	foreach (char c; to)
+	{
+	    assert(c <= 0x7F);
+	}
+    }
+    body
+    {
+	char[] t = new char[256];
+	int i;
+
+	for (i = 0; i < t.length; i++)
+	    t[i] = cast(char)i;
+
+	for (i = 0; i < from.length; i++)
+	    t[from[i]] = to[i];
+
+	return t;
+    }
+
+/******************************************
+ * Translate characters in s[] using table created by maketrans().
+ * Delete chars in delchars[].
+ * BUG: only works with ASCII
+ */
+
+char[] translate(char[] s, char[] transtab, char[] delchars)
+    in
+    {
+	assert(transtab.length == 256);
+    }
+    body
+    {
+	char[] r;
+	int count;
+	bool[256] deltab;
+
+	deltab[] = false;
+	foreach (char c; delchars)
+	{
+	    deltab[c] = true;
+	}
+
+	count = 0;
+	foreach (char c; s)
+	{
+	    if (!deltab[c])
+		count++;
+	    //printf("s[%d] = '%c', count = %d\n", i, s[i], count);
+	}
+
+	r = new char[count];
+	count = 0;
+	foreach (char c; s)
+	{
+	    if (!deltab[c])
+	    {
+		r[count] = transtab[c];
+		count++;
+	    }
+	}
+
+	return r;
+    }
+
+unittest
+{
+    debug(string) printf("string.translate.unittest\n");
+
+    char[] from = "abcdef";
+    char[] to   = "ABCDEF";
+    char[] s    = "The quick dog fox";
+    char[] t;
+    char[] r;
+    int i;
+
+    t = maketrans(from, to);
+    r = translate(s, t, "kg");
+    //printf("r = '%.*s'\n", r);
+    i = cmp(r, "ThE quiC Do Fox");
+    assert(i == 0);
+}
+
+/***********************************************
+ * Convert to char[].
+ */
+
+char[] toString(bool b)
+{
+    return b ? "true" : "false";
+}
+
+/// ditto
+char[] toString(char c)
+{
+    char[] result = new char[2];
+    result[0] = c;
+    result[1] = 0;
+    return result[0 .. 1];
+}
+
+unittest
+{
+    debug(string) printf("string.toString(char).unittest\n");
+
+    char[] s = "foo";
+    char[] s2;
+    foreach (char c; s)
+    {
+	s2 ~= std.string.toString(c);
+    }
+    //printf("%.*s", s2);
+    assert(s2 == "foo");
+}
+
+char[] toString(ubyte ub)  { return toString(cast(uint) ub); } /// ditto
+char[] toString(ushort us) { return toString(cast(uint) us); } /// ditto
+
+/// ditto
+char[] toString(uint u)
+{   char[uint.sizeof * 3] buffer = void;
+    int ndigits;
+    char[] result;
+
+    ndigits = 0;
+    if (u < 10)
+	// Avoid storage allocation for simple stuff
+	result = digits[u .. u + 1];
+    else
+    {
+	while (u)
+	{
+	    uint c = (u % 10) + '0';
+	    u /= 10;
+	    ndigits++;
+	    buffer[buffer.length - ndigits] = cast(char)c;
+	}
+	result = new char[ndigits];
+	result[] = buffer[buffer.length - ndigits .. buffer.length];
+    }
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(uint).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(0u);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(9u);
+    i = cmp(r, "9");
+    assert(i == 0);
+
+    r = toString(123u);
+    i = cmp(r, "123");
+    assert(i == 0);
+}
+
+/// ditto
+char[] toString(ulong u)
+{   char[ulong.sizeof * 3] buffer;
+    int ndigits;
+    char[] result;
+
+    if (u < 0x1_0000_0000)
+	return toString(cast(uint)u);
+    ndigits = 0;
+    while (u)
+    {
+	char c = cast(char)((u % 10) + '0');
+	u /= 10;
+	ndigits++;
+	buffer[buffer.length - ndigits] = c;
+    }
+    result = new char[ndigits];
+    result[] = buffer[buffer.length - ndigits .. buffer.length];
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(ulong).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(0uL);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(9uL);
+    i = cmp(r, "9");
+    assert(i == 0);
+
+    r = toString(123uL);
+    i = cmp(r, "123");
+    assert(i == 0);
+}
+
+char[] toString(byte b)  { return toString(cast(int) b); } /// ditto
+char[] toString(short s) { return toString(cast(int) s); } /// ditto
+
+/// ditto
+char[] toString(int i)
+{   char[1 + int.sizeof * 3] buffer;
+    char[] result;
+
+    if (i >= 0)
+	return toString(cast(uint)i);
+
+    uint u = -i;
+    int ndigits = 1;
+    while (u)
+    {
+	char c = cast(char)((u % 10) + '0');
+	u /= 10;
+	buffer[buffer.length - ndigits] = c;
+	ndigits++;
+    }
+    buffer[buffer.length - ndigits] = '-';
+    result = new char[ndigits];
+    result[] = buffer[buffer.length - ndigits .. buffer.length];
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(int).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(0);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(9);
+    i = cmp(r, "9");
+    assert(i == 0);
+
+    r = toString(123);
+    i = cmp(r, "123");
+    assert(i == 0);
+
+    r = toString(-0);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(-9);
+    i = cmp(r, "-9");
+    assert(i == 0);
+
+    r = toString(-123);
+    i = cmp(r, "-123");
+    assert(i == 0);
+}
+
+/// ditto
+char[] toString(long i)
+{   char[1 + long.sizeof * 3] buffer;
+    char[] result;
+
+    if (i >= 0)
+	return toString(cast(ulong)i);
+    if (cast(int)i == i)
+	return toString(cast(int)i);
+
+    ulong u = cast(ulong)(-i);
+    int ndigits = 1;
+    while (u)
+    {
+	char c = cast(char)((u % 10) + '0');
+	u /= 10;
+	buffer[buffer.length - ndigits] = c;
+	ndigits++;
+    }
+    buffer[buffer.length - ndigits] = '-';
+    result = new char[ndigits];
+    result[] = buffer[buffer.length - ndigits .. buffer.length];
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(long).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(0L);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(9L);
+    i = cmp(r, "9");
+    assert(i == 0);
+
+    r = toString(123L);
+    i = cmp(r, "123");
+    assert(i == 0);
+
+    r = toString(-0L);
+    i = cmp(r, "0");
+    assert(i == 0);
+
+    r = toString(-9L);
+    i = cmp(r, "-9");
+    assert(i == 0);
+
+    r = toString(-123L);
+    i = cmp(r, "-123");
+    assert(i == 0);
+}
+
+/// ditto
+char[] toString(float f) { return toString(cast(double) f); }
+
+/// ditto
+char[] toString(double d)
+{
+    char[20] buffer;
+
+    int len = sprintf(buffer.ptr, "%g", d);
+    return buffer[0 .. len].dup;
+}
+
+/// ditto
+char[] toString(real r)
+{
+    char[20] buffer;
+
+    int len = sprintf(buffer.ptr, "%Lg", r);
+    return buffer[0 .. len].dup;
+}
+
+/// ditto
+char[] toString(ifloat f) { return toString(cast(idouble) f); }
+
+/// ditto
+char[] toString(idouble d)
+{
+    char[21] buffer;
+
+    int len = sprintf(buffer.ptr, "%gi", d);
+    return buffer[0 .. len].dup;
+}
+
+/// ditto
+char[] toString(ireal r)
+{
+    char[21] buffer;
+
+    int len = sprintf(buffer.ptr, "%Lgi", r);
+    return buffer[0 .. len].dup;
+}
+
+/// ditto
+char[] toString(cfloat f) { return toString(cast(cdouble) f); }
+
+/// ditto
+char[] toString(cdouble d)
+{
+    char[20 + 1 + 20 + 1] buffer;
+
+    int len = sprintf(buffer.ptr, "%g+%gi", d.re, d.im);
+    return buffer[0 .. len].dup;
+}
+
+/// ditto
+char[] toString(creal r)
+{
+    char[20 + 1 + 20 + 1] buffer;
+
+    int len = sprintf(buffer.ptr, "%Lg+%Lgi", r.re, r.im);
+    return buffer[0 .. len].dup;
+}
+
+
+/******************************************
+ * Convert value to string in _radix radix.
+ *
+ * radix must be a value from 2 to 36.
+ * value is treated as a signed value only if radix is 10.
+ * The characters A through Z are used to represent values 10 through 36.
+ */
+char[] toString(long value, uint radix)
+in
+{
+    assert(radix >= 2 && radix <= 36);
+}
+body
+{
+    if (radix == 10)
+	return toString(value);		// handle signed cases only for radix 10
+    return toString(cast(ulong)value, radix);
+}
+
+/// ditto
+char[] toString(ulong value, uint radix)
+in
+{
+    assert(radix >= 2 && radix <= 36);
+}
+body
+{
+    char[value.sizeof * 8] buffer;
+    uint i = buffer.length;
+
+    if (value < radix && value < hexdigits.length)
+	return hexdigits[cast(size_t)value .. cast(size_t)value + 1];
+
+    do
+    {	ubyte c;
+
+	c = cast(ubyte)(value % radix);
+	value = value / radix;
+	i--;
+	buffer[i] = cast(char)((c < 10) ? c + '0' : c + 'A' - 10);
+    } while (value);
+    return buffer[i .. length].dup;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(ulong, uint).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(-10L, 10u);
+    assert(r == "-10");
+
+    r = toString(15L, 2u);
+    //writefln("r = '%s'", r);
+    assert(r == "1111");
+
+    r = toString(1L, 2u);
+    //writefln("r = '%s'", r);
+    assert(r == "1");
+
+    r = toString(0x1234AFL, 16u);
+    //writefln("r = '%s'", r);
+    assert(r == "1234AF");
+}
+
+/*************************************************
+ * Convert C-style 0 terminated string s to char[] string.
+ */
+
+char[] toString(char *s)
+{
+    return s ? s[0 .. strlen(s)] : cast(char[])null;
+}
+
+unittest
+{
+    debug(string) printf("string.toString(char*).unittest\n");
+
+    char[] r;
+    int i;
+
+    r = toString(null);
+    i = cmp(r, "");
+    assert(i == 0);
+
+    r = toString("foo\0");
+    i = cmp(r, "foo");
+    assert(i == 0);
+}
+
+
+/*****************************************************
+ * Format arguments into a string.
+ */
+
+
+char[] format(...)
+{
+    char[] s;
+
+    void putc(dchar c)
+    {
+	std.utf.encode(s, c);
+    }
+
+    std.format.doFormat(&putc, _arguments, _argptr);
+    return s;
+}
+
+
+/*****************************************************
+ * Format arguments into string <i>s</i> which must be large
+ * enough to hold the result. Throws ArrayBoundsError if it is not.
+ * Returns: s
+ */
+char[] sformat(char[] s, ...)
+{   size_t i;
+
+    void putc(dchar c)
+    {
+	if (c <= 0x7F)
+	{
+	    if (i >= s.length)
+		throw new ArrayBoundsError("std.string.sformat", 0);
+	    s[i] = cast(char)c;
+	    ++i;
+	}
+	else
+	{   char[4] buf;
+	    char[] b;
+
+	    b = std.utf.toUTF8(buf, c);
+	    if (i + b.length > s.length)
+		throw new ArrayBoundsError("std.string.sformat", 0);
+	    s[i..i+b.length] = b[];
+	    i += b.length;
+	}
+    }
+
+    std.format.doFormat(&putc, _arguments, _argptr);
+    return s[0 .. i];
+}
+
+
+unittest
+{
+    debug(string) printf("std.string.format.unittest\n");
+
+    char[] r;
+    int i;
+/+
+    r = format(null);
+    i = cmp(r, "");
+    assert(i == 0);
++/
+    r = format("foo");
+    i = cmp(r, "foo");
+    assert(i == 0);
+
+    r = format("foo%%");
+    i = cmp(r, "foo%");
+    assert(i == 0);
+
+    r = format("foo%s", 'C');
+    i = cmp(r, "fooC");
+    assert(i == 0);
+
+    r = format("%s foo", "bar");
+    i = cmp(r, "bar foo");
+    assert(i == 0);
+
+    r = format("%s foo %s", "bar", "abc");
+    i = cmp(r, "bar foo abc");
+    assert(i == 0);
+
+    r = format("foo %d", -123);
+    i = cmp(r, "foo -123");
+    assert(i == 0);
+
+    r = format("foo %d", 123);
+    i = cmp(r, "foo 123");
+    assert(i == 0);
+}
+
+
+/***********************************************
+ * See if character c is in the pattern.
+ * Patterns:
+ *
+ *	A <i>pattern</i> is an array of characters much like a <i>character
+ *	class</i> in regular expressions. A sequence of characters
+ *	can be given, such as "abcde". The '-' can represent a range
+ *	of characters, as "a-e" represents the same pattern as "abcde".
+ *	"a-fA-F0-9" represents all the hex characters.
+ *	If the first character of a pattern is '^', then the pattern
+ *	is negated, i.e. "^0-9" means any character except a digit.
+ *	The functions inPattern, <b>countchars</b>, <b>removeschars</b>,
+ *	and <b>squeeze</b>
+ *	use patterns.
+ *
+ * Note: In the future, the pattern syntax may be improved
+ *	to be more like regular expression character classes.
+ */
+
+bool inPattern(dchar c, char[] pattern)
+{
+    bool result = false;
+    int range = 0;
+    dchar lastc;
+
+    foreach (size_t i, dchar p; pattern)
+    {
+	if (p == '^' && i == 0)
+	{   result = true;
+	    if (i + 1 == pattern.length)
+		return (c == p);	// or should this be an error?
+	}
+	else if (range)
+	{
+	    range = 0;
+	    if (lastc <= c && c <= p || c == p)
+		return !result;
+	}
+	else if (p == '-' && i > result && i + 1 < pattern.length)
+	{
+	    range = 1;
+	    continue;
+	}
+	else if (c == p)
+	    return !result;
+	lastc = p;
+    }
+    return result;
+}
+
+
+unittest
+{
+    debug(string) printf("std.string.inPattern.unittest\n");
+
+    int i;
+
+    i = inPattern('x', "x");
+    assert(i == 1);
+    i = inPattern('x', "y");
+    assert(i == 0);
+    i = inPattern('x', cast(char[])null);
+    assert(i == 0);
+    i = inPattern('x', "^y");
+    assert(i == 1);
+    i = inPattern('x', "yxxy");
+    assert(i == 1);
+    i = inPattern('x', "^yxxy");
+    assert(i == 0);
+    i = inPattern('x', "^abcd");
+    assert(i == 1);
+    i = inPattern('^', "^^");
+    assert(i == 0);
+    i = inPattern('^', "^");
+    assert(i == 1);
+    i = inPattern('^', "a^");
+    assert(i == 1);
+    i = inPattern('x', "a-z");
+    assert(i == 1);
+    i = inPattern('x', "A-Z");
+    assert(i == 0);
+    i = inPattern('x', "^a-z");
+    assert(i == 0);
+    i = inPattern('x', "^A-Z");
+    assert(i == 1);
+    i = inPattern('-', "a-");
+    assert(i == 1);
+    i = inPattern('-', "^A-");
+    assert(i == 0);
+    i = inPattern('a', "z-a");
+    assert(i == 1);
+    i = inPattern('z', "z-a");
+    assert(i == 1);
+    i = inPattern('x', "z-a");
+    assert(i == 0);
+}
+
+
+/***********************************************
+ * See if character c is in the intersection of the patterns.
+ */
+
+int inPattern(dchar c, char[][] patterns)
+{   int result;
+
+    foreach (char[] pattern; patterns)
+    {
+	if (!inPattern(c, pattern))
+	{   result = 0;
+	    break;
+	}
+	result = 1;
+    }
+    return result;
+}
+
+
+/********************************************
+ * Count characters in s that match pattern.
+ */
+
+size_t countchars(char[] s, char[] pattern)
+{
+    size_t count;
+
+    foreach (dchar c; s)
+    {
+	count += inPattern(c, pattern);
+    }
+    return count;
+}
+
+
+unittest
+{
+    debug(string) printf("std.string.count.unittest\n");
+
+    size_t c;
+
+    c = countchars("abc", "a-c");
+    assert(c == 3);
+    c = countchars("hello world", "or");
+    assert(c == 3);
+}
+
+
+/********************************************
+ * Return string that is s with all characters removed that match pattern.
+ */
+
+char[] removechars(char[] s, char[] pattern)
+{
+    char[] r = s;
+    int changed;
+    size_t j;
+
+    foreach (size_t i, dchar c; s)
+    {
+	if (!inPattern(c, pattern))
+	{
+	    if (changed)
+	    {
+		if (r is s)
+		    r = s[0 .. j].dup;
+		std.utf.encode(r, c);
+	    }
+	}
+	else if (!changed)
+	{   changed = 1;
+	    j = i;
+	}
+    }
+    if (changed && r is s)
+	r = s[0 .. j].dup;
+    return r;
+}
+
+
+unittest
+{
+    debug(string) printf("std.string.remove.unittest\n");
+
+    char[] r;
+
+    r = removechars("abc", "a-c");
+    assert(r is null);
+    r = removechars("hello world", "or");
+    assert(r == "hell wld");
+    r = removechars("hello world", "d");
+    assert(r == "hello worl");
+}
+
+
+/***************************************************
+ * Return string where sequences of a character in s[] from pattern[]
+ * are replaced with a single instance of that character.
+ * If pattern is null, it defaults to all characters.
+ */
+
+char[] squeeze(char[] s, char[] pattern = null)
+{
+    char[] r = s;
+    dchar lastc;
+    size_t lasti;
+    int run;
+    bool changed;
+
+    foreach (size_t i, dchar c; s)
+    {
+	if (run && lastc == c)
+	{
+	    changed = true;
+	}
+	else if (pattern is null || inPattern(c, pattern))
+	{
+	    run = 1;
+	    if (changed)
+	    {	if (r is s)
+		    r = s[0 .. lasti].dup;
+		std.utf.encode(r, c);
+	    }
+	    else
+		lasti = i + std.utf.stride(s, i);
+	    lastc = c;
+	}
+	else
+	{
+	    run = 0;
+	    if (changed)
+	    {	if (r is s)
+		    r = s[0 .. lasti].dup;
+		std.utf.encode(r, c);
+	    }
+	}
+    }
+    if (changed)
+    {
+	if (r is s)
+	    r = s[0 .. lasti];
+    }
+    return r;
+}
+
+
+unittest
+{
+    debug(string) printf("std.string.squeeze.unittest\n");
+    char[] s,r;
+
+    r = squeeze("hello");
+    //writefln("r = '%s'", r);
+    assert(r == "helo");
+    s = "abcd";
+    r = squeeze(s);
+    assert(r is s);
+    s = "xyzz";
+    r = squeeze(s);
+    assert(r.ptr == s.ptr);	// should just be a slice
+    r = squeeze("hello goodbyee", "oe");
+    assert(r == "hello godbye");
+}
+
+
+/**********************************************
+ * Return string that is the 'successor' to s[].
+ * If the rightmost character is a-zA-Z0-9, it is incremented within
+ * its case or digits. If it generates a carry, the process is
+ * repeated with the one to its immediate left.
+ */
+
+char[] succ(char[] s)
+{
+    if (s.length && isalnum(s[length - 1]))
+    {
+	char[] r = s.dup;
+	size_t i = r.length - 1;
+
+	while (1)
+	{   dchar c = s[i];
+	    dchar carry;
+
+	    switch (c)
+	    {
+		case '9':
+		    c = '0';
+		    carry = '1';
+		    goto Lcarry;
+		case 'z':
+		case 'Z':
+		    c -= 'Z' - 'A';
+		    carry = c;
+		Lcarry:
+		    r[i] = cast(char)c;
+		    if (i == 0)
+		    {
+			char[] t = new char[r.length + 1];
+			t[0] = cast(char)carry;
+			t[1 .. length] = r[];
+			return t;
+		    }
+		    i--;
+		    break;
+
+		default:
+		    if (std.ctype.isalnum(c))
+			r[i]++;
+		    return r;
+	    }
+	}
+    }
+    return s;
+}
+
+unittest
+{
+    debug(string) printf("std.string.succ.unittest\n");
+
+    char[] r;
+
+    r = succ(null);
+    assert(r is null);
+    r = succ("!@#$%");
+    assert(r == "!@#$%");
+    r = succ("1");
+    assert(r == "2");
+    r = succ("9");
+    assert(r == "10");
+    r = succ("999");
+    assert(r == "1000");
+    r = succ("zz99");
+    assert(r == "aaa00");
+}
+
+
+/***********************************************
+ * Replaces characters in str[] that are in from[]
+ * with corresponding characters in to[] and returns the resulting
+ * string.
+ * Params:
+ *	modifiers = a string of modifier characters
+ * Modifiers:
+		<table border=1 cellspacing=0 cellpadding=5>
+		<tr> <th>Modifier <th>Description
+		<tr> <td><b>c</b> <td>Complement the list of characters in from[]
+		<tr> <td><b>d</b> <td>Removes matching characters with no corresponding replacement in to[]
+		<tr> <td><b>s</b> <td>Removes adjacent duplicates in the replaced characters
+		</table>
+
+	If modifier <b>d</b> is present, then the number of characters
+	in to[] may be only 0 or 1.
+
+	If modifier <b>d</b> is not present and to[] is null,
+	then to[] is taken _to be the same as from[].
+
+	If modifier <b>d</b> is not present and to[] is shorter
+	than from[], then to[] is extended by replicating the
+	last character in to[].
+
+	Both from[] and to[] may contain ranges using the <b>-</b>
+	character, for example <b>a-d</b> is synonymous with <b>abcd</b>.
+	Neither accept a leading <b>^</b> as meaning the complement of
+	the string (use the <b>c</b> modifier for that).
+ */
+
+char[] tr(char[] str, char[] from, char[] to, char[] modifiers = null)
+{
+    int mod_c;
+    int mod_d;
+    int mod_s;
+
+    foreach (char c; modifiers)
+    {
+	switch (c)
+	{
+	    case 'c':	mod_c = 1; break;	// complement
+	    case 'd':	mod_d = 1; break;	// delete unreplaced chars
+	    case 's':	mod_s = 1; break;	// squeeze duplicated replaced chars
+	    default:	assert(0);
+	}
+    }
+
+    if (to is null && !mod_d)
+	to = from;
+
+    char[] result = new char[str.length];
+    result.length = 0;
+    int m;
+    dchar lastc;
+
+    foreach (dchar c; str)
+    {	dchar lastf;
+	dchar lastt;
+	dchar newc;
+	int n = 0;
+
+	for (size_t i = 0; i < from.length; )
+	{
+	    dchar f = std.utf.decode(from, i);
+	    //writefln("\tf = '%s', c = '%s', lastf = '%x', '%x', i = %d, %d", f, c, lastf, dchar.init, i, from.length);
+	    if (f == '-' && lastf != dchar.init && i < from.length)
+	    {
+		dchar nextf = std.utf.decode(from, i);
+		//writefln("\tlastf = '%s', c = '%s', nextf = '%s'", lastf, c, nextf);
+		if (lastf <= c && c <= nextf)
+		{
+		    n += c - lastf - 1;
+		    if (mod_c)
+			goto Lnotfound;
+		    goto Lfound;
+		}
+		n += nextf - lastf;
+		lastf = lastf.init;
+		continue;
+	    }
+
+	    if (c == f)
+	    {	if (mod_c)
+		    goto Lnotfound;
+		goto Lfound;
+	    }
+	    lastf = f;
+	    n++;
+	}
+	if (!mod_c)
+	    goto Lnotfound;
+	n = 0;			// consider it 'found' at position 0
+
+    Lfound:
+
+	// Find the nth character in to[]
+	//writefln("\tc = '%s', n = %d", c, n);
+	dchar nextt;
+	for (size_t i = 0; i < to.length; )
+	{   dchar t = std.utf.decode(to, i);
+	    if (t == '-' && lastt != dchar.init && i < to.length)
+	    {
+		nextt = std.utf.decode(to, i);
+		//writefln("\tlastt = '%s', c = '%s', nextt = '%s', n = %d", lastt, c, nextt, n);
+		n -= nextt - lastt;
+		if (n < 0)
+		{
+		    newc = nextt + n + 1;
+		    goto Lnewc;
+		}
+		lastt = dchar.init;
+		continue;
+	    }
+	    if (n == 0)
+	    {	newc = t;
+		goto Lnewc;
+	    }
+	    lastt = t;
+	    nextt = t;
+	    n--;
+	}
+	if (mod_d)
+	    continue;
+	newc = nextt;
+
+      Lnewc:
+	if (mod_s && m && newc == lastc)
+	    continue;
+	std.utf.encode(result, newc);
+	m = 1;
+	lastc = newc;
+	continue;
+
+      Lnotfound:
+	std.utf.encode(result, c);
+	lastc = c;
+	m = 0;
+    }
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("std.string.tr.unittest\n");
+
+    char[] r;
+    //writefln("r = '%s'", r);
+
+    r = tr("abcdef", "cd", "CD");
+    assert(r == "abCDef");
+
+    r = tr("abcdef", "b-d", "B-D");
+    assert(r == "aBCDef");
+
+    r = tr("abcdefgh", "b-dh", "B-Dx");
+    assert(r == "aBCDefgx");
+
+    r = tr("abcdefgh", "b-dh", "B-CDx");
+    assert(r == "aBCDefgx");
+
+    r = tr("abcdefgh", "b-dh", "B-BCDx");
+    assert(r == "aBCDefgx");
+
+    r = tr("abcdef", "ef", "*", "c");
+    assert(r == "****ef");
+
+    r = tr("abcdef", "ef", "", "d");
+    assert(r == "abcd");
+
+    r = tr("hello goodbye", "lo", null, "s");
+    assert(r == "helo godbye");
+
+    r = tr("hello goodbye", "lo", "x", "s");
+    assert(r == "hex gxdbye");
+
+    r = tr("14-Jul-87", "a-zA-Z", " ", "cs");
+    assert(r == " Jul ");
+
+    r = tr("Abc", "AAA", "XYZ");
+    assert(r == "Xbc");
+}
+
+
+/* ************************************************
+ * Version       : v0.3
+ * Author        : David L. 'SpottedTiger' Davis
+ * Date Created  : 31.May.05 Compiled and Tested with dmd v0.125
+ * Date Modified : 01.Jun.05 Modified the function to handle the
+ *               :           imaginary and complex float-point 
+ *               :           datatypes.
+ *               :
+ * Licence       : Public Domain / Contributed to Digital Mars
+ */
+
+/**
+ * [in] char[] s can be formatted in the following ways:
+ *
+ * Integer Whole Number:
+ * (for byte, ubyte, short, ushort, int, uint, long, and ulong)
+ * ['+'|'-']digit(s)[U|L|UL]
+ *
+ * examples: 123, 123UL, 123L, +123U, -123L
+ *
+ * Floating-Point Number:
+ * (for float, double, real, ifloat, idouble, and ireal)
+ * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
+ *      or [nan|nani|inf|-inf]
+ *
+ * examples: +123., -123.01, 123.3e-10f, 123.3e-10fi, 123.3e-10L
+ * 
+ * (for cfloat, cdouble, and creal)
+ * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][+]
+ *         [digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
+ *      or [nan|nani|nan+nani|inf|-inf]
+ *
+ * examples: nan, -123e-1+456.9e-10Li, +123e+10+456i, 123+456
+ *
+ * [in] bool bAllowSep 
+ * False by default, but when set to true it will accept the 
+ * separator characters "," and "_" within the string, but these  
+ * characters should be stripped from the string before using any 
+ * of the conversion functions like toInt(), toFloat(), and etc 
+ * else an error will occur.
+ *
+ * Also please note, that no spaces are allowed within the string  
+ * anywhere whether it's a leading, trailing, or embedded space(s), 
+ * thus they too must be stripped from the string before using this
+ * function, or any of the conversion functions.
+ */
+
+final bool isNumeric(in char[] s, in bool bAllowSep = false)
+{
+    int    iLen = s.length;
+    bool   bDecimalPoint = false;
+    bool   bExponent = false;
+    bool   bComplex = false;
+    char[] sx = std.string.tolower(s); 
+    int    j  = 0;
+    char   c;
+
+    //writefln("isNumeric(char[], bool = false) called!");
+    // Empty string, return false
+    if (iLen == 0)
+        return false;
+    
+    // Check for NaN (Not a Number)
+    if (sx == "nan" || sx == "nani" || sx == "nan+nani")
+        return true;
+        
+    // Check for Infinity
+    if (sx == "inf" || sx == "-inf")
+        return true;
+     
+    // A sign is allowed only in the 1st character   
+    if (sx[0] == '-' || sx[0] == '+')
+        j++;
+            
+    for (int i = j; i < iLen; i++)
+    {
+        c = sx[i];
+    
+        // Digits are good, continue checking 
+        // with the next character... ;)
+        if (c >= '0' && c <= '9') 
+            continue;
+
+        // Check for the complex type, and if found 
+        // reset the flags for checking the 2nd number.  
+        else if (c == '+')
+            if (i > 0) 
+            {
+                bDecimalPoint = false;
+                bExponent = false;
+                bComplex = true;
+                continue;
+            }
+            else
+                return false;
+                
+        // Allow only one exponent per number   
+        else if (c == 'e')  
+        {
+            // A 2nd exponent found, return not a number
+            if (bExponent)
+                return false;
+                
+            if (i + 1 < iLen)
+            {
+                // Look forward for the sign, and if 
+                // missing then this is not a number.
+                if (sx[i + 1] != '-' && sx[i + 1] != '+')
+                    return false;
+                else
+                {
+                    bExponent = true;
+                    i++;    
+                }    
+            }        
+            else
+                // Ending in "E", return not a number
+                return false;        
+        }  
+        // Allow only one decimal point per number to be used
+        else if (c == '.' )
+        {
+            // A 2nd decimal point found, return not a number
+            if (bDecimalPoint)
+                return false;
+            
+            bDecimalPoint = true;
+            continue;
+        }   
+        // Check for ending literal characters: "f,u,l,i,ul,fi,li",
+        // and wheater they're being used with the correct datatype.
+        else if (i == iLen - 2)
+        {
+            // Integer Whole Number
+            if (sx[i..iLen] == "ul" && 
+               (!bDecimalPoint && !bExponent && !bComplex))
+                return true;
+            // Floating-Point Number
+            else if ((sx[i..iLen] == "fi" || sx[i..iLen] == "li") &&
+                     (bDecimalPoint || bExponent || bComplex))
+                return true;
+            else if (sx[i..iLen] == "ul" && 
+                    (bDecimalPoint || bExponent || bComplex))
+                return false;    
+            // Could be a Integer or a Float, thus
+            // all these suffixes are valid for both  
+            else if (sx[i..iLen] == "ul" || 
+                     sx[i..iLen] == "fi" || 
+                     sx[i..iLen] == "li")
+                return true;
+            else    
+                return false;
+        }
+        else if (i == iLen - 1)
+        {
+            // Integer Whole Number
+            if ((c == 'u' || c == 'l') && 
+                (!bDecimalPoint && !bExponent && !bComplex))
+                return true;
+            // Check to see if the last character in the string 
+            // is the required 'i' character
+            else if (bComplex)
+                if (c == 'i')
+                    return true;
+                else 
+                    return false;        
+            // Floating-Point Number
+            else if ((c == 'l' || c == 'f' || c == 'i') &&
+                     (bDecimalPoint || bExponent))
+                return true;
+            // Could be a Integer or a Float, thus  
+            // all these suffixes are valid for both 
+            else if (c == 'l' || c == 'f' || c == 'i')
+                return true;
+            else
+                return false;
+        }
+        else
+            // Check if separators are allow  
+            // to be in the numeric string
+            if (bAllowSep == true && (c == '_' || c == ','))
+                continue;
+            else    
+                return false;       
+    }     
+    
+    return true;
+}
+
+/// Allow any object as a parameter
+bool isNumeric(...)
+{
+    return isNumeric(_arguments, _argptr);
+}
+
+/// Check only the first parameter, all others will be ignored. 
+bool isNumeric(TypeInfo[] _arguments, va_list _argptr)
+{
+    char[]  s  = "";
+    wchar[] ws = "";
+    dchar[] ds = "";
+
+    //writefln("isNumeric(...) called!");
+    if (_arguments.length == 0)
+        return false;
+
+    if (_arguments[0] == typeid(char[]))
+        return isNumeric(va_arg!(char[])(_argptr));
+    else if (_arguments[0] == typeid(wchar[]))
+        return isNumeric(std.utf.toUTF8(va_arg!(wchar[])(_argptr)));
+    else if (_arguments[0] == typeid(dchar[]))
+        return isNumeric(std.utf.toUTF8(va_arg!(dchar[])(_argptr)));
+    else if (_arguments[0] == typeid(real))
+        return true;
+    else if (_arguments[0] == typeid(double)) 
+        return true;   
+    else if (_arguments[0] == typeid(float)) 
+        return true;  
+    else if (_arguments[0] == typeid(ulong)) 
+        return true; 
+    else if (_arguments[0] == typeid(long)) 
+        return true;   
+    else if (_arguments[0] == typeid(uint)) 
+        return true;  
+    else if (_arguments[0] == typeid(int)) 
+        return true;   
+    else if (_arguments[0] == typeid(ushort)) 
+        return true;   
+    else if (_arguments[0] == typeid(short)) 
+        return true;   
+    else if (_arguments[0] == typeid(ubyte)) 
+    {
+       s.length = 1;
+       s[0]= va_arg!(ubyte)(_argptr);
+       return isNumeric(cast(char[])s);
+    }
+    else if (_arguments[0] == typeid(byte)) 
+    {
+       s.length = 1;
+       s[0] = va_arg!(byte)(_argptr);
+       return isNumeric(cast(char[])s);
+    }
+    else if (_arguments[0] == typeid(ireal))
+        return true;
+    else if (_arguments[0] == typeid(idouble)) 
+        return true;   
+    else if (_arguments[0] == typeid(ifloat)) 
+        return true;  
+    else if (_arguments[0] == typeid(creal))
+        return true;
+    else if (_arguments[0] == typeid(cdouble)) 
+        return true;   
+    else if (_arguments[0] == typeid(cfloat)) 
+        return true;  
+    else if (_arguments[0] == typeid(char))
+    {
+        s.length = 1;
+        s[0] = va_arg!(char)(_argptr);
+        return isNumeric(s);
+    }
+    else if (_arguments[0] == typeid(wchar))
+    {
+        ws.length = 1;
+        ws[0] = va_arg!(wchar)(_argptr);
+        return isNumeric(std.utf.toUTF8(ws));
+    }
+    else if (_arguments[0] == typeid(dchar))
+    { 
+        ds.length =  1;
+        ds[0] = va_arg!(dchar)(_argptr);
+        return isNumeric(std.utf.toUTF8(ds));
+    }
+    //else if (_arguments[0] == typeid(cent)) 
+    //    return true;   
+    //else if (_arguments[0] == typeid(ucent)) 
+    //    return true;  
+    else       
+       return false; 
+}
+
+unittest
+{
+    debug (string) printf("isNumeric(in char[], bool = false).unittest\n");
+    char[] s;
+
+    // Test the isNumeric(in char[]) function
+    assert(isNumeric("1") == true );
+    assert(isNumeric("1.0") == true );
+    assert(isNumeric("1e-1") == true );
+    assert(isNumeric("12345xxxx890") == false );
+    assert(isNumeric("567L") == true );
+    assert(isNumeric("23UL") == true );
+    assert(isNumeric("-123..56f") == false );
+    assert(isNumeric("12.3.5.6") == false );
+    assert(isNumeric(" 12.356") == false );
+    assert(isNumeric("123 5.6") == false );
+    assert(isNumeric("1233E-1+1.0e-1i") == true );
+ 
+    assert(isNumeric("123.00E-5+1234.45E-12Li") == true);
+    assert(isNumeric("123.00e-5+1234.45E-12iL") == false);
+    assert(isNumeric("123.00e-5+1234.45e-12uL") == false);
+    assert(isNumeric("123.00E-5+1234.45e-12lu") == false);
+  
+    assert(isNumeric("123fi") == true);
+    assert(isNumeric("123li") == true);
+    assert(isNumeric("--123L") == false);
+    assert(isNumeric("+123.5UL") == false);
+    assert(isNumeric("123f") == true);
+    assert(isNumeric("123.u") == false);
+
+    assert(isNumeric(std.string.toString(real.nan)) == true);
+    assert(isNumeric(std.string.toString(-real.infinity)) == true);
+    assert(isNumeric(std.string.toString(123e+2+1234.78Li)) == true);
+
+    s = "$250.99-";
+    assert(isNumeric(s[1..s.length - 2]) == true);
+    assert(isNumeric(s) == false);
+    assert(isNumeric(s[0..s.length - 1]) == false);
+
+    // These test calling the isNumeric(...) function
+    assert(isNumeric(1,123UL) == true);
+    assert(isNumeric('2') == true);
+    assert(isNumeric('x') == false);
+    assert(isNumeric(cast(byte)0x57) == false); // 'W'
+    assert(isNumeric(cast(byte)0x37) == true);  // '7'
+    assert(isNumeric(cast(wchar[])"145.67") == true);
+    assert(isNumeric(cast(dchar[])"145.67U") == false);
+    assert(isNumeric(123_000.23fi) == true);
+    assert(isNumeric(123.00E-5+1234.45E-12Li) == true);
+    assert(isNumeric(real.nan) == true);
+    assert(isNumeric(-real.infinity) == true);
+}
+
+
+/*****************************
+ * Soundex algorithm.
+ *
+ * The Soundex algorithm converts a word into 4 characters
+ * based on how the word sounds phonetically. The idea is that
+ * two spellings that sound alike will have the same Soundex
+ * value, which means that Soundex can be used for fuzzy matching
+ * of names.
+ *
+ * Params:
+ *	string = String to convert to Soundex representation.
+ *	buffer = Optional 4 char array to put the resulting Soundex
+ *		characters into. If null, the return value
+ *		buffer will be allocated on the heap.
+ * Returns:
+ *	The four character array with the Soundex result in it.
+ *	Returns null if there is no Soundex representation for the string.
+ *
+ * See_Also:
+ *	$(LINK2 http://en.wikipedia.org/wiki/Soundex, Wikipedia),
+ *	$(LINK2 http://www.archives.gov/publications/general-info-leaflets/55.html, The Soundex Indexing System)
+ *
+ * Bugs:
+ *	Only works well with English names.
+ *	There are other arguably better Soundex algorithms,
+ *	but this one is the standard one.
+ */
+
+char[] soundex(char[] string, char[] buffer = null)
+in
+{
+    assert(!buffer || buffer.length >= 4);
+}
+out (result)
+{
+    if (result)
+    {
+	assert(result.length == 4);
+	assert(result[0] >= 'A' && result[0] <= 'Z');
+	foreach (char c; result[1 .. 4])
+	    assert(c >= '0' && c <= '6');
+    }
+}
+body
+{
+    static char[26] dex =
+    // ABCDEFGHIJKLMNOPQRSTUVWXYZ
+      "01230120022455012623010202";
+
+    int b = 0;
+    char lastc;
+    foreach (char c; string)
+    {
+	if (c >= 'a' && c <= 'z')
+	    c -= 'a' - 'A';
+	else if (c >= 'A' && c <= 'Z')
+	{
+	    ;
+	}
+	else
+	{   lastc = lastc.init;
+	    continue;
+	}
+	if (b == 0)
+	{
+	    if (!buffer)
+		buffer = new char[4];
+	    buffer[0] = c;
+	    b++;
+	    lastc = dex[c - 'A'];
+	}
+	else
+	{
+	    if (c == 'H' || c == 'W')
+		continue;
+	    if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
+		lastc = lastc.init;
+	    c = dex[c - 'A'];
+	    if (c != '0' && c != lastc)
+	    {
+		buffer[b] = c;
+		b++;
+		lastc = c;
+	    }
+	}
+	if (b == 4)
+	    goto Lret;
+    }
+    if (b == 0)
+	buffer = null;
+    else
+	buffer[b .. 4] = '0';
+Lret:
+    return buffer;
+}
+
+unittest
+{   char[4] buffer;
+
+    assert(soundex(null) == null);
+    assert(soundex("") == null);
+    assert(soundex("0123^&^^**&^") == null);
+    assert(soundex("Euler") == "E460");
+    assert(soundex(" Ellery ") == "E460");
+    assert(soundex("Gauss") == "G200");
+    assert(soundex("Ghosh") == "G200");
+    assert(soundex("Hilbert") == "H416");
+    assert(soundex("Heilbronn") == "H416");
+    assert(soundex("Knuth") == "K530");
+    assert(soundex("Kant", buffer) == "K530");
+    assert(soundex("Lloyd") == "L300");
+    assert(soundex("Ladd") == "L300");
+    assert(soundex("Lukasiewicz", buffer) == "L222");
+    assert(soundex("Lissajous") == "L222");
+    assert(soundex("Robert") == "R163");
+    assert(soundex("Rupert") == "R163");
+    assert(soundex("Rubin") == "R150");
+    assert(soundex("Washington") == "W252");
+    assert(soundex("Lee") == "L000");
+    assert(soundex("Gutierrez") == "G362");
+    assert(soundex("Pfister") == "P236");
+    assert(soundex("Jackson") == "J250");
+    assert(soundex("Tymczak") == "T522");
+    assert(soundex("Ashcraft") == "A261");
+
+    assert(soundex("Woo") == "W000");
+    assert(soundex("Pilgrim") == "P426");
+    assert(soundex("Flingjingwaller") == "F452");
+    assert(soundex("PEARSE") == "P620");
+    assert(soundex("PIERCE") == "P620");
+    assert(soundex("Price") == "P620");
+    assert(soundex("CATHY") == "C300");
+    assert(soundex("KATHY") == "K300");
+    assert(soundex("Jones") == "J520");
+    assert(soundex("johnsons") == "J525");
+    assert(soundex("Hardin") == "H635");
+    assert(soundex("Martinez") == "M635");
+}
+
+
+/***************************************************
+ * Construct an associative array consisting of all
+ * abbreviations that uniquely map to the strings in values.
+ *
+ * This is useful in cases where the user is expected to type
+ * in one of a known set of strings, and the program will helpfully
+ * autocomplete the string once sufficient characters have been
+ * entered that uniquely identify it.
+ * Example:
+ * ---
+ * import std.stdio;
+ * import std.string;
+ * 
+ * void main()
+ * {
+ *    static char[][] list = [ "food", "foxy" ];
+ * 
+ *    auto abbrevs = std.string.abbrev(list);
+ * 
+ *    foreach (key, value; abbrevs)
+ *    {
+ *       writefln("%s => %s", key, value);
+ *    }
+ * }
+ * ---
+ * produces the output:
+ * <pre>
+ * fox =&gt; foxy
+ * food =&gt; food
+ * foxy =&gt; foxy
+ * foo =&gt; food
+ * </pre>
+ */
+
+char[][char[]] abbrev(char[][] values)
+{
+    char[][char[]] result;
+
+    // Make a copy when sorting so we follow COW principles.
+    values = values.dup.sort;
+
+    size_t values_length = values.length;
+    size_t lasti = values_length;
+    size_t nexti;
+
+    char[] nv;
+    char[] lv;
+
+    for (size_t i = 0; i < values_length; i = nexti)
+    {	char[] value = values[i];
+
+	// Skip dups
+	for (nexti = i + 1; nexti < values_length; nexti++)
+	{   nv = values[nexti];
+	    if (value != values[nexti])
+		break;
+	}
+
+	for (size_t j = 0; j < value.length; j += std.utf.stride(value, j))
+	{   char[] v = value[0 .. j];
+
+	    if ((nexti == values_length || j > nv.length || v != nv[0 .. j]) &&
+		(lasti == values_length || j > lv.length || v != lv[0 .. j]))
+		result[v] = value;
+	}
+	result[value] = value;
+	lasti = i;
+	lv = value;
+    }
+
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.abbrev.unittest\n");
+
+    char[][] values;
+    values ~= "hello";
+    values ~= "hello";
+    values ~= "he";
+
+    char[][char[]] r;
+
+    r = abbrev(values);
+    char[][] keys = r.keys.dup;
+    keys.sort;
+
+    assert(keys.length == 4);
+    assert(keys[0] == "he");
+    assert(keys[1] == "hel");
+    assert(keys[2] == "hell");
+    assert(keys[3] == "hello");
+
+    assert(r[keys[0]] == "he");
+    assert(r[keys[1]] == "hello");
+    assert(r[keys[2]] == "hello");
+    assert(r[keys[3]] == "hello");
+}
+
+
+/******************************************
+ * Compute column number after string if string starts in the
+ * leftmost column, which is numbered starting from 0.
+ */
+
+size_t column(char[] string, int tabsize = 8)
+{
+    size_t column;
+
+    foreach (dchar c; string)
+    {
+	switch (c)
+	{
+	    case '\t':
+		column = (column + tabsize) / tabsize * tabsize;
+		break;
+
+	    case '\r':
+	    case '\n':
+	    case PS:
+	    case LS:
+		column = 0;
+		break;
+
+	    default:
+		column++;
+		break;
+	}
+    }
+    return column;
+}
+
+unittest
+{
+    debug(string) printf("string.column.unittest\n");
+
+    assert(column(null) == 0);
+    assert(column("") == 0);
+    assert(column("\t") == 8);
+    assert(column("abc\t") == 8);
+    assert(column("12345678\t") == 16);
+}
+
+/******************************************
+ * Wrap text into a paragraph.
+ *
+ * The input text string s is formed into a paragraph
+ * by breaking it up into a sequence of lines, delineated
+ * by \n, such that the number of columns is not exceeded
+ * on each line.
+ * The last line is terminated with a \n.
+ * Params:
+ *	s = text string to be wrapped
+ *	columns = maximum number of _columns in the paragraph
+ *	firstindent = string used to _indent first line of the paragraph
+ *	indent = string to use to _indent following lines of the paragraph
+ *	tabsize = column spacing of tabs
+ * Returns:
+ *	The resulting paragraph.
+ */
+
+char[] wrap(char[] s, int columns = 80, char[] firstindent = null,
+	char[] indent = null, int tabsize = 8)
+{
+    char[] result;
+    int col;
+    int spaces;
+    bool inword;
+    bool first = true;
+    size_t wordstart;
+
+    result.length = firstindent.length + s.length;
+    result.length = firstindent.length;
+    result[] = firstindent[];
+    col = column(result, tabsize);
+    foreach (size_t i, dchar c; s)
+    {
+	if (iswhite(c))
+	{
+	    if (inword)
+	    {
+		if (first)
+		{
+		    ;
+		}
+		else if (col + 1 + (i - wordstart) > columns)
+		{
+		    result ~= '\n';
+		    result ~= indent;
+		    col = column(indent, tabsize);
+		}
+		else
+		{   result ~= ' ';
+		    col += 1;
+		}
+		result ~= s[wordstart .. i];
+		col += i - wordstart;
+		inword = false;
+		first = false;
+	    }
+	}
+	else
+	{
+	    if (!inword)
+	    {
+		wordstart = i;
+		inword = true;
+	    }
+	}
+    }
+
+    if (inword)
+    {
+	if (col + 1 + (s.length - wordstart) >= columns)
+	{
+	    result ~= '\n';
+	    result ~= indent;
+	}
+	else if (result.length != firstindent.length)
+	    result ~= ' ';
+	result ~= s[wordstart .. s.length];
+    }
+    result ~= '\n';
+
+    return result;
+}
+
+unittest
+{
+    debug(string) printf("string.wrap.unittest\n");
+
+    assert(wrap(null) == "\n");
+    assert(wrap(" a b   df ") == "a b df\n");
+    //writefln("'%s'", wrap(" a b   df ",3));
+    assert(wrap(" a b   df ", 3) == "a b\ndf\n");
+    assert(wrap(" a bc   df ", 3) == "a\nbc\ndf\n");
+    //writefln("'%s'", wrap(" abcd   df ",3));
+    assert(wrap(" abcd   df ", 3) == "abcd\ndf\n");
+    assert(wrap("x") == "x\n");
+    assert(wrap("u u") == "u u\n");
+}
+
+
+/***************************
+ * Does string s[] start with an email address?
+ * Returns:
+ *	null	it does not
+ *	char[]	it does, and this is the slice of s[] that is that email address
+ * References:
+ *	RFC2822
+ */
+char[] isEmail(char[] s)
+{   size_t i;
+
+    if (!isalpha(s[0]))
+	goto Lno;
+
+    for (i = 1; 1; i++)
+    {
+	if (i == s.length)
+	    goto Lno;
+	auto c = s[i];
+	if (isalnum(c))
+	    continue;
+	if (c == '-' || c == '_' || c == '.')
+	    continue;
+	if (c != '@')
+	    goto Lno;
+	i++;
+	break;
+    }
+    //writefln("test1 '%s'", s[0 .. i]);
+
+    /* Now do the part past the '@'
+     */
+    size_t lastdot;
+    for (; i < s.length; i++)
+    {
+	auto c = s[i];
+	if (isalnum(c))
+	    continue;
+	if (c == '-' || c == '_')
+	    continue;
+	if (c == '.')
+	{
+	    lastdot = i;
+	    continue;
+	}
+	break;
+    }
+    if (!lastdot || (i - lastdot != 3 && i - lastdot != 4))
+	goto Lno;
+
+    return s[0 .. i];
+
+Lno:
+    return null;
+}
+
+
+/***************************
+ * Does string s[] start with a URL?
+ * Returns:
+ *	null	it does not
+ *	char[]	it does, and this is the slice of s[] that is that URL
+ */
+
+char[] isURL(char[] s)
+{
+    /* Must start with one of:
+     *	http://
+     *	https://
+     *	www.
+     */
+
+    size_t i;
+
+    if (s.length <= 4)
+	goto Lno;
+
+    //writefln("isURL(%s)", s);
+    if (s.length > 7 && std.string.icmp(s[0 .. 7], "http://") == 0)
+	i = 7;
+    else if (s.length > 8 && std.string.icmp(s[0 .. 8], "https://") == 0)
+	i = 8;
+//    if (icmp(s[0 .. 4], "www.") == 0)
+//	i = 4;
+    else
+	goto Lno;
+
+    size_t lastdot;
+    for (; i < s.length; i++)
+    {
+	auto c = s[i];
+	if (isalnum(c))
+	    continue;
+	if (c == '-' || c == '_' || c == '?' ||
+	    c == '=' || c == '%' || c == '&' ||
+	    c == '/' || c == '+' || c == '#' ||
+	    c == '~')
+	    continue;
+	if (c == '.')
+	{
+	    lastdot = i;
+	    continue;
+	}
+	break;
+    }
+    //if (!lastdot || (i - lastdot != 3 && i - lastdot != 4))
+    if (!lastdot)
+	goto Lno;
+
+    return s[0 .. i];
+
+Lno:
+    return null;
+}
+
+
--- a/lphobos/typeinfos2.d	Tue Nov 20 00:02:35 2007 +0100
+++ b/lphobos/typeinfos2.d	Tue Nov 20 05:29:20 2007 +0100
@@ -1,7 +1,7 @@
 module typeinfos2;
 
 import
-//typeinfo2.to_AC,
+typeinfo2.ti_AC,
 typeinfo2.ti_Adouble,
 typeinfo2.ti_Afloat,
 typeinfo2.ti_Ag,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/aa1.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,6 @@
+module aa1;
+
+void main()
+{
+    int[int] aai;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug70.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,9 @@
+module bug70;
+
+void main()
+{
+    for (;false;)
+    {
+        break;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug71.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,20 @@
+module bug71;
+
+void main()
+{
+    static TypeInfo skipCI(TypeInfo valti)
+    {
+      while (1)
+      {
+    if (valti.classinfo.name.length == 18 &&
+        valti.classinfo.name[9..18] == "Invariant")
+        valti = (cast(TypeInfo_Invariant)valti).next;
+    else if (valti.classinfo.name.length == 14 &&
+        valti.classinfo.name[9..14] == "Const")
+        valti = (cast(TypeInfo_Const)valti).next;
+    else
+        break;
+      }
+      return valti;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug72.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,8 @@
+module bug72;
+
+void main()
+{
+    char c = void;
+    int i = void;
+    c += i;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bug73.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,14 @@
+int find(char[] s, dchar c)
+{
+    // c is a universal character
+    foreach (int i, dchar c2; s)
+    {
+    if (c == c2)
+        return i;
+    }
+    return -1;
+}
+
+void main()
+{
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/switch2.d	Tue Nov 20 05:29:20 2007 +0100
@@ -0,0 +1,12 @@
+module switch2;
+
+void main()
+{
+    int i = 2;
+    switch(i)
+    {
+    case 0: assert(0);
+    case 1,2: printf("hello world\n"); break;
+    default: assert(0);
+    }
+}