v. vanilla 2.0

restored/stdlib.inc

← All source files · Download this file

9,565 bytes · SHA256 ba50240fee087595ca98983581d73d486eeb6df1d9c8b13849f24b52dcc5475f

// Pure, bounded standard-library additions. Included after utilities.inc.
// Never change legacy numeric parsing/formatting or mutation contracts here.
static bool standardCommand(const CodeLine& c) {
    const auto& op=c.arg[0].text;
    if(op=="text" || op=="number") return true;
    if(c.arg.size()<2) return false;
    const auto& sub=c.arg[1].text;
    if(op=="array") return sub=="sort" || sub=="reverse" || sub=="slice" || sub=="find" || sub=="remove" || sub=="unique";
    if(op=="json") return sub=="append" || sub=="insert" || sub=="remove" || sub=="length" || sub=="contains" || sub=="merge";
    return false;
}
static void standardValidate(const CodeLine& c) {
    count(c,3,6);
    const auto& a=c.arg; const auto& op=a[0].text; const auto& sub=a[1].text;
    auto dest=[&](std::size_t k) { destination(a[k],c.source); };
    dest(2);
    if(op=="array") {
        if(sub=="reverse" || sub=="unique") count(c,4,4);
        else if(sub=="slice") count(c,6,6);
        else count(c,5,5);
        dest(3);
        if(sub=="sort" && a[4].text!="numeric" && a[4].text!="lexical") fail(c.source,"array sort mode must be numeric or lexical");
    } else if(op=="json") {
        if(sub=="insert" || sub=="contains") count(c,5,5);
        else if(sub=="merge") count(c,6,6);
        else count(c,4,4);
        if(sub=="length" || sub=="contains" || sub=="merge") dest(3);
        if(sub=="merge") {
            dest(4);
            if(a[5].text!="left" && a[5].text!="right" && a[5].text!="error") fail(c.source,"json merge policy must be left, right or error");
        }
    } else if(op=="text") {
        if(sub=="contains" || sub=="startswith" || sub=="endswith") count(c,5,5);
        else if(sub=="padleft" || sub=="padright") count(c,6,6);
        else fail(c.source,"unknown text operation");
    } else {
        if(sub=="valid" || sub=="convert") count(c,4,4);
        else if(sub=="format") count(c,5,5);
        else fail(c.source,"unknown number operation");
    }
}
static bool standardNumeric(const std::string& text,double& n) {
    // Decimal only, optional leading +, no whitespace, locale-independent.
    if(text.empty()) return false;
    const char* first=text.data(); const char* last=first+text.size();
    if(*first=='+') { ++first; if(first==last || *first=='-' || *first=='+') return false; }
    auto r=std::from_chars(first,last,n,std::chars_format::general);
    return r.ec==std::errc{} && r.ptr==last && std::isfinite(n);
}
static double standardNumber(const std::string& text,const Source& s) {
    double n=0;
    if(!standardNumeric(text,n)) fail(s,"expected finite decimal number");
    return n;
}
static std::size_t standardIndex(const std::string& text,std::size_t maximum,const Source& s) {
    auto n=standardNumber(text,s);
    if(n<0 || n>static_cast<double>(maximum) || std::trunc(n)!=n) fail(s,"expected integer in bounds");
    return static_cast<std::size_t>(n);
}
static void standardCommit(Frame& f,const Token& t,variable result,const Source& s) {
    if(result.value.size()>valueLimit) failLimit(s,"value size limit");
    auto& target=lookup(f,t,s);
    // Single-target transaction; rollback even on fatal frame-budget failure.
    std::swap(target,result);
    try { frameCheck(f,s); } catch(...) { std::swap(target,result); throw; }
}
static Json standardJsonItem(Frame& f,const Token& t,const Source& s) {
    if(!t.literal && !t.text.empty() && t.text[0]=='$') {
        auto& v=lookup(f,t,s);
        if(v.structured) return v.structured->data;
    }
    return Json(value(f,t,s));
}
static void standardExecute(Frame& f,const CodeLine& c) {
    const auto& a=c.arg; const auto& s=c.source; const auto& op=a[0].text; const auto& sub=a[1].text;
    lookup(f,a[2],s);
    auto val=[&](std::size_t k) { return value(f,a[k],s); };
    variable result;
    if(op=="array") {
        const auto& source=array(f,a[3],s);
        if(sub=="find") {
            auto needle=val(4); auto it=std::find(source.begin(),source.end(),needle);
            result.value=it==source.end() ? "-1" : std::to_string(it-source.begin());
        } else {
            result.array=true;
            if(sub=="slice") {
                auto start=standardIndex(val(4),source.size(),s);
                auto length=standardIndex(val(5),source.size()-start,s);
                result.items.assign(source.begin()+static_cast<std::ptrdiff_t>(start),source.begin()+static_cast<std::ptrdiff_t>(start+length));
            } else if(sub=="unique") {
                std::set<std::string_view> seen;
                for(const auto& item:source) if(seen.insert(item).second) result.items.push_back(item);
            } else {
                result.items=source;
                if(sub=="reverse") std::reverse(result.items.begin(),result.items.end());
                else if(sub=="remove") {
                    auto index=standardIndex(val(4),source.size(),s);
                    if(index==source.size()) fail(s,"array index out of bounds");
                    result.items.erase(result.items.begin()+static_cast<std::ptrdiff_t>(index));
                } else if(a[4].text=="lexical") std::stable_sort(result.items.begin(),result.items.end());
                else {
                    // Parse once before sorting; comparison never throws or reparses.
                    std::vector<std::pair<double,std::size_t>> keys; keys.reserve(source.size());
                    for(std::size_t i=0;i<source.size();++i) keys.emplace_back(standardNumber(source[i],s),i);
                    std::stable_sort(keys.begin(),keys.end(),[](const auto& x,const auto& y) { return x.first<y.first; });
                    for(std::size_t i=0;i<keys.size();++i) result.items[i]=source[keys[i].second];
                }
            }
        }
    } else if(op=="text") {
        auto text=val(3);
        if(sub=="padleft" || sub=="padright") {
            auto width=standardIndex(val(4),valueLimit,s); auto pad=val(5);
            if(pad.empty()) fail(s,"padding must not be empty");
            auto needed=width>text.size() ? width-text.size() : 0;
            std::string padding; padding.reserve(needed);
            while(padding.size()<needed) padding.append(pad,0,std::min(pad.size(),needed-padding.size()));
            result.value=sub=="padleft" ? padding+text : text+padding;
        } else {
            auto needle=val(4); bool yes=false;
            if(sub=="contains") yes=text.find(needle)!=std::string::npos;
            else if(sub=="startswith") yes=text.size()>=needle.size() && text.compare(0,needle.size(),needle)==0;
            else yes=text.size()>=needle.size() && text.compare(text.size()-needle.size(),needle.size(),needle)==0;
            result.value=yes ? "1" : "0";
        }
    } else if(op=="number") {
        auto text=val(3); double n=0;
        if(sub=="valid") result.value=standardNumeric(text,n) ? "1" : "0";
        else {
            n=standardNumber(text,s); if(n==0) n=0;
            // Max binary64 fixed representation plus 100 fractional places fits.
            char buffer[512]; std::to_chars_result r;
            if(sub=="format") r=std::to_chars(buffer,buffer+sizeof(buffer),n,std::chars_format::fixed,static_cast<int>(standardIndex(val(4),100,s)));
            else r=std::to_chars(buffer,buffer+sizeof(buffer),n,std::chars_format::general);
            if(r.ec!=std::errc{}) fail(s,"number formatting failed");
            result.value.assign(buffer,r.ptr);
            if(sub=="format" && result.value[0]=='-' && result.value.find_first_not_of("-0.")==std::string::npos) result.value.erase(0,1);
        }
    } else {
        const auto& source=jsonValue(f,a[(sub=="append" || sub=="insert" || sub=="remove") ? 2 : 3],s);
        Json copy;
        bool structured=true;
        if(sub=="length") {
            if(!source.is_structured()) fail(s,"json length requires array or object");
            result.value=std::to_string(source.size()); structured=false;
        } else if(sub=="contains") {
            if(!source.is_array()) fail(s,"json contains requires array");
            auto item=standardJsonItem(f,a[4],s);
            result.value=std::find(source.begin(),source.end(),item)!=source.end() ? "1" : "0"; structured=false;
        } else if(sub=="merge") {
            const auto& right=jsonValue(f,a[4],s);
            if(!source.is_object() || !right.is_object()) fail(s,"json merge requires objects");
            copy=source;
            for(auto it=right.begin();it!=right.end();++it) {
                bool exists=copy.contains(it.key());
                if(exists && a[5].text=="error") fail(s,"duplicate JSON merge key");
                if(!exists || a[5].text=="right") copy[it.key()]=it.value();
            }
        } else {
            if(!source.is_array()) fail(s,"JSON mutation requires array");
            auto index=sub=="append" ? source.size() : standardIndex(val(3),source.size(),s);
            if(sub=="remove" && index==source.size()) fail(s,"JSON array index out of bounds");
            copy=source;
            if(sub=="remove") copy.erase(copy.begin()+static_cast<Json::difference_type>(index));
            else {
                auto item=standardJsonItem(f,a[sub=="append" ? 3 : 4],s);
                copy.insert(copy.begin()+static_cast<Json::difference_type>(index),std::move(item));
            }
        }
        if(structured) {
            result.structuredBytes=jsonSize(copy,s);
            result.structured=std::make_shared<const Structured>(std::move(copy));
        }
    }
    standardCommit(f,a[2],std::move(result),s);
}