// 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(maximum) || std::trunc(n)!=n) fail(s,"expected integer in bounds"); return static_cast(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(start),source.begin()+static_cast(start+length)); } else if(sub=="unique") { std::set 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(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> keys; keys.reserve(source.size()); for(std::size_t i=0;itext.size() ? width-text.size() : 0; std::string padding; padding.reserve(needed); while(padding.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(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(index)); else { auto item=standardJsonItem(f,a[sub=="append" ? 3 : 4],s); copy.insert(copy.begin()+static_cast(index),std::move(item)); } } if(structured) { result.structuredBytes=jsonSize(copy,s); result.structured=std::make_shared(std::move(copy)); } } standardCommit(f,a[2],std::move(result),s); }