@lmctl-ai/lmctl 0.2.307 → 0.2.308
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/build-info.json +1 -1
- package/dist/cli/lmscript.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -30,7 +30,7 @@ All notable public-preview changes for `@lmctl-ai/lmctl` are recorded here.
|
|
|
30
30
|
|
|
31
31
|
## Unreleased
|
|
32
32
|
|
|
33
|
-
- LMScript strict syntax (issue 002): every simple statement ends with a mandatory `;` (including `system p;` and `x = cmd p;` command statements) and every if/else/while/for body is a `{ }` block; violations are check errors with hints. `scripts/strict-syntax.ts
|
|
33
|
+
- LMScript strict syntax (issue 002): every simple statement ends with a mandatory `;` (including `system p;` and `x = cmd p;` command statements) and every if/else/while/for body is a `{ }` block; violations are check errors with hints. The temporary `scripts/strict-syntax.ts` migration tool was removed after the last migration (operator: clean, break, fix).
|
|
34
34
|
- LMScript gains data-embedding blocks: reserved `_csv_`, `_toml_`, `_props_`, `_json_`, `_cmd_` and `_graphql_` markers are expressions whose body lines follow the statement's mandatory `;` and close with `end _x_`; options and `${}` fields are validated at check time, values are substituted per value after parsing (a _toml_ field must stand alone as a value), and `end _x_` outside a block, mismatched closers and a missing `;` are check errors naming expected/found; `_cmd_` builds argv over several lines with the one-line cmd rules, `system(_cmd_)` runs it and a standalone `_cmd_;` statement runs it. `_graphql_` keeps the executable-syntax/schema-free lint as a string value (`${}` not supported yet). The csv"""/toml"""/javaprops"""/graphql""" literals are removed (each error names its marker). The pinned smol-toml parser stays bundled into the lmscript artifact.
|
|
35
35
|
- `lmctl init` generates example provider slots from installed provider detection. Each managed slot now contains only `provider` and `model1`; model1 is the provider's first live id (Claude Code uses the highest-version undated catalog id). Model2/model3 tier values and the curated model table are removed. Provider slots remain open examples: files may omit managed slots or add user tables, and init refreshes only managed slots present.
|
|
36
36
|
|
package/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sha": "
|
|
1
|
+
{"sha": "e9f0cadcab4a744d77d463e5e5264342bc198f88", "dirty": false, "builtAt": "2026-10-17T18:35:51.000Z"}
|
package/dist/cli/lmscript.js
CHANGED
|
@@ -326,7 +326,7 @@ try {
|
|
|
326
326
|
`){let o=this.commandField;this.commandField=!1;try{this.consumeStringNewline()}finally{this.commandField=o}return""}let i={"\\":"\\",'"':'"',"'":"'",a:"\x07",b:"\b",f:"\f",n:`
|
|
327
327
|
`,r:"\r",t:" ",v:"\v"};if(Object.hasOwn(i,s))return this.advance(),i[s];if(s==="N")throw new P("Named Unicode escapes (\\N) are not supported",n,r,"LEXER",this.file);if(s==="x"||s==="u"||s==="U"){this.advance();let o=s==="x"?2:s==="u"?4:8,a=this.source.slice(this.pos,this.pos+o);if(a.length!==o||!/^[0-9a-fA-F]+$/.test(a))throw new P("Invalid \\"+s+" escape; expected "+o+" hexadecimal digits",n,r,"LEXER",this.file);let l=Number.parseInt(a,16);if(l>1114111||l>=55296&&l<=57343)throw new P("Invalid Unicode scalar in string escape",n,r,"LEXER",this.file);for(let c=0;c<o;c++)this.advance();return String.fromCodePoint(l)}if(/[0-7]/.test(s)){let o=this.advance();for(;o.length<3&&this.peek()!==""&&/[0-7]/.test(this.peek());)o+=this.advance();return String.fromCodePoint(Number.parseInt(o,8))}return this.diagnostics.push({message:"Unknown escape '\\"+s+"'; backslash preserved",severity:"warning",line:n,column:r,file:this.file}),e&&(s==="{"||s==="}")?"\\":"\\"+this.advance()}readModernString(e,n,r,s,i){let o=this.pos;for(let h=0;h<e.length;h++)this.advance();let a=this.peek(),l=i?.triple??this.source.startsWith(a.repeat(3),this.pos),c=a.repeat(l?3:1),u={prefix:e.toLowerCase(),raw:i?.raw??/r/i.test(e),triple:l,formatted:i?.formatted??/f/i.test(e)};if(u.formatted&&this.formattedDepth>=64)throw new P("Formatted-string nesting exceeds limit 64",n,r,"LEXER",this.file);u.formatted&&this.formattedDepth++;for(let h=0;h<c.length;h++)this.advance();if(this.modes.push({kind:"string",delimiter:c}),u.formatted){let h=this.spannedToken("FSTRING_START",e+c,{offset:o,line:n,column:r});h.stringLiteral=u,s.push(h)}let d=this.position(),m=this.position(),p="",f=()=>{if(this.pos>m.offset){let h=this.spannedToken("FSTRING_TEXT",p,m);h.literal=p,s.push(h)}p=""};try{for(;this.pos<this.length;){if(this.source.startsWith(c,this.pos)){u.formatted&&f();let g=this.position();for(let y=0;y<c.length;y++)this.advance();return u.formatted?this.spannedToken("FSTRING_END",c,g):{type:"STRING",value:p,literal:p,line:n,column:r,offset:o,endOffset:this.pos,endLine:this.line,endColumn:this.column,stringLiteral:u}}let h=this.peek();if(u.formatted&&(h==="{"||h==="}"))if(this.peekNext()===h)this.advance(),this.advance(),p+=h;else{if(h==="}")throw new P("Unmatched '}' in formatted-string text; use '}}' for a literal brace",this.line,this.column,"LEXER",this.file);{f();let g=this.position();this.advance();let y=this.spannedToken("FSTRING_FIELD_START","{",g);s.push(y);let b={kind:"field",opener:y,delimiters:[],pendingQuestions:0};this.modes.push(b);try{this.scanTokens(s,b)}finally{this.modes.pop()}m=this.position()}}else if(h==="\r"||h===`
|
|
328
328
|
`){if(!l)throw new P("Unterminated short string across newline",n,r,"LEXER",this.file);p+=this.consumeStringNewline()}else h==="\\"?u.raw?(p+=this.advance(),this.pos<this.length&&!(u.formatted&&(this.peek()==="{"||this.peek()==="}"))&&(p+=this.peek()==="\r"||this.peek()===`
|
|
329
|
-
`?this.consumeStringNewline():this.advance())):p+=this.readModernEscape(u.formatted):p+=this.advance()}throw new P("Unterminated string literal; expected "+c,n,r,"LEXER",this.file)}finally{this.modes.pop(),u.formatted&&this.formattedDepth--}}readIdentifier(e,n){let r="";for(;this.pos<this.length&&this.isAlphaNumeric(this.peek());)r+=this.advance();let s=Object.hasOwn(Co,r)?Co[r]:void 0;if(s){let i;return s==="TRUE"&&(i=!0),s==="FALSE"&&(i=!1),s==="NULL"&&(i=null),{type:s,value:r,literal:i,line:e,column:n}}return{type:"IDENTIFIER",value:r,line:e,column:n}}};ko=Object.freeze({shell_quote:"cmd_render",count:"len",findall:"regex_find_all",has:"has_key",match:"regex_match",test:"regex_test",search:"regex_match",sorted:"sort",length:"len",date_format:"strftime",default_to:"if_null",to_string:"str",regex_split:"split_regex",regexSplit:"split_regex",splitRegex:"split_regex",clock:"now",json:"json_parse",sleep:"sleep_ms",urlget:"http_get",capitalize:"upper_first",startsWith:"starts_with",endsWith:"ends_with",indexOf:"index_of",lastIndexOf:"last_index_of",toUpperCase:"upper",toLowerCase:"lower",parseInt:"num",parseFloat:"num",trimStart:"trim_start",trimLeft:"trim_start",trimEnd:"trim_end",trimRight:"trim_end",strip:"trim",lstrip:"trim_start",rstrip:"trim_end",append:"push",prepend:"unshift",push_back:"push",push_front:"unshift","JSON.parse":"json_parse","JSON.stringify":"json_stringify"});Fl=class t{tokens;current=0;file;strict;strictViolations=[];constructor(e,n,r={}){this.tokens=e,this.file=n,this.strict=r.strict??!0}violation(e){if(!this.strict){this.strictViolations.push(e);return}throw e.kind==="semicolon"?new P("Expected ';' after "+e.what+"; every statement ends with ';'",e.line,e.column,"PARSER",this.file):new P("Expected '{' after "+e.keyword+"; braces are required around every if/else/while/for body (write "+e.keyword+" { ... })",e.line,e.column,"PARSER",this.file)}parseBody(e){if(this.check("LBRACE"))return this.parseStatement();let n=this.previous(),r=this.peek(),s=this.parseStatement(),i=this.previous();return this.violation({kind:"braces",keyword:e,line:r.line,column:r.column,headerLine:n.endLine??n.line,openAt:n.endOffset??0,closeAt:i.endOffset??0}),s}parse(){let e=[],n=this.peek();for(;!this.isAtEnd();)e.push(this.parseDeclaration(!0));let r={kind:"Program",statements:e,line:n.line,column:n.column,file:this.file};return this.file&&$m(r,this.file),r}span(e,n){let r=this.previous();return e.start===void 0&&n.offset!==void 0&&r?.endOffset!==void 0&&(e.start=n.offset,e.end=r.endOffset),e}parseDeclaration(e=!1){let n=this.peek();return this.span(this.parseDeclarationInner(e),n)}parseDeclarationInner(e=!1){try{if(this.match("FN"))return this.parseFunctionDecl();if(this.match("LET"))return this.parseVarDecl();if(this.match("IMPORT")){if(!e)throw new P("'import' statement is only allowed at the top level of a file",this.previous().line,this.previous().column,"PARSER",this.file);return this.parseImportStmt()}return this.parseStatement()}catch(n){throw n instanceof P?n:new P(n instanceof Error?n.message:String(n),this.peek().line,this.peek().column,"PARSER",this.file)}}parsePackageStmt(){let e=this.advance(),n=this.consume("IDENTIFIER","Expected package name");return this.consume("SEMICOLON","Expected ';' after package name"),{kind:"PackageStmt",name:n.value,line:e.line,column:e.column,file:this.file}}parseImportStmt(){let e=this.previous();if(this.check("FSTRING_START")){let s=this.peek();throw new P("Import paths require an unprefixed short string",s.line,s.column,"PARSER",this.file)}let n=this.consume("STRING","Expected file path string after 'import'");if(!n.value.trim())throw new P("Import specifier must not be empty or whitespace-only",e.line,e.column,"CHECKER",this.file);if(/[\0\r\n]/.test(n.value))throw new P("Import specifier must not contain NUL or line breaks",e.line,e.column,"CHECKER",this.file);if(n.stringLiteral?.prefix||n.stringLiteral?.triple)throw new P("Import paths require an unprefixed short string",n.line,n.column,"PARSER",this.file);let r;if(this.check("IDENTIFIER")&&this.peek().value==="as"&&this.peek().line===n.line){let s=this.advance();if(!Oo(n.value)&&!vs(n.value))throw new P("`as` is only supported for schema modules (@owner/repo or *.cli.schema.v1.json); a source module's namespace is its package declaration and a native module's is its name",s.line,s.column,"PARSER",this.file);r=this.consume("IDENTIFIER","Expected a namespace identifier after 'as'").value}return this.endStatement("the import path"),{kind:"ImportStmt",path:n.value,...r!==void 0?{alias:r}:{},hostModule:qv(n.value),line:e.line,column:e.column,file:this.file}}parseFunctionDecl(){let e=this.previous(),n=this.consume("IDENTIFIER","Expected function name after 'fn'");this.consume("LPAREN","Expected '(' after function name");let r=[];if(!this.check("RPAREN"))do{let i=this.consume("IDENTIFIER","Expected parameter name");r.push(i.value)}while(this.match("COMMA"));this.consume("RPAREN","Expected ')' after parameters"),this.consume("LBRACE","Expected '{' before function body");let s=this.parseBlockBody(this.previous());return{kind:"FunctionDeclStmt",name:n.value,params:r,body:s,line:e.line,column:e.column}}parseVarDecl(){let e=this.previous();if(this.match("LPAREN","LBRACKET")){let s=this.previous().type==="LPAREN"?"RPAREN":"RBRACKET",i=[];if(!this.check(s))do{let a=this.consume("IDENTIFIER","Expected variable name in destructuring");i.push(a.value)}while(this.match("COMMA"));this.consume(s,`Expected '${s==="RPAREN"?")":"]"}' after variable list`),this.consume("ASSIGN","Expected '=' after destructuring variable list");let o=this.parseExpression();return this.endStatement("the let declaration"),{kind:"TupleVarDeclStmt",names:i,initializer:o,line:e.line,column:e.column}}let n=this.consume("IDENTIFIER","Expected variable name after 'let'"),r;return this.match("ASSIGN")&&(r=this.parseExpression()),this.endStatement("the let declaration"),{kind:"VarDeclStmt",name:n.value,initializer:r,line:e.line,column:e.column}}parseStatement(){let e=this.peek();return this.span(this.parseStatementInner(),e)}parseStatementInner(){if(this.check("IDENTIFIER")&&this.peek().value==="package"&&["IDENTIFIER","SYSTEM","CMD"].includes(this.tokens[this.current+1]?.type))return this.parsePackageStmt();if(this.match("IMPORT"))throw new P("'import' statement is only allowed at the top level of a file",this.previous().line,this.previous().column,"PARSER",this.file);if(this.match("SYNC"))return this.parseSyncStatement();if(this.match("IF"))return this.parseIfStatement();if(this.match("WHILE"))return this.parseWhileStatement();if(this.match("FOR"))return this.parseForStatement();if(this.match("RETURN"))return this.parseReturnStatement();if(this.match("BREAK"))return this.parseBreakStatement();if(this.match("CONTINUE"))return this.parseContinueStatement();if(this.match("LBRACE"))return this.parseBlockBody(this.previous());if(this.match("RECOVER"))return this.parseRecover();if(this.match("RESUMEPOINT")){let e=this.previous();throw this.check("IDENTIFIER")?new P("resumepoint is only allowed directly in a recover body",e.line,e.column,"PARSER",this.file):this.reservedWord(e)}return this.parseExpressionStatement()}reservedWord(e){return new P(`'${e.value}' is reserved; rename this binding`,e.line,e.column,"PARSER",this.file)}parseRecover(){let e=this.previous();if(!this.match("LBRACE"))throw this.reservedWord(e);let n=[];for(;!this.check("RBRACE")&&!this.isAtEnd();){if(this.match("RESUMEPOINT")){let r=this.previous(),s=this.peek();if(["ASSIGN","LPAREN","SEMICOLON"].includes(s.type))throw this.reservedWord(r);if(s.type!=="IDENTIFIER"||!/^[A-Za-z_][A-Za-z0-9_]*$/.test(s.value)||this.tokens[this.current+1]?.type!=="COLON")throw new P("Expected a resumepoint name (letters, digits and _, not a keyword) followed by ':'",s.line,s.column,"PARSER",this.file);this.advance(),this.advance(),n.push({kind:"ResumePoint",name:s.value,statements:[],line:r.line,column:r.column});continue}if(n.length===0)throw new P("Statements before the first resumepoint are not allowed",this.peek().line,this.peek().column,"PARSER",this.file);n[n.length-1].statements.push(this.parseDeclaration())}if(this.consume("RBRACE","Expected '}' after recover body"),n.length===0)throw new P("recover requires at least one resumepoint",e.line,e.column,"PARSER",this.file);return{kind:"RecoverStmt",points:n,line:e.line,column:e.column}}parseSyncStatement(){let e=this.previous(),n=this.parseExpression();if(this.endStatement("the sync statement"),n.kind==="AssignExpr")return{kind:"SyncAssignStmt",target:n.target,value:n.value,line:e.line,column:e.column};if(n.kind==="IndexAssignExpr")return{kind:"SyncIndexAssignStmt",target:n.target,index:n.index,value:n.value,line:e.line,column:e.column};if(n.kind==="CallExpr")return{kind:"SyncCallStmt",call:n,line:e.line,column:e.column};throw new P("Invalid 'sync' statement target; expected variable assignment ('sync a = ...'), member/index assignment ('sync obj.k = ...'), or container mutation call ('sync push(arr, ...)').",e.line,e.column,"PARSER",this.file)}parseIfStatement(){let e=this.previous();this.consume("LPAREN","Expected '(' after 'if'");let n=this.parseExpression();this.consume("RPAREN","Expected ')' after if condition");let r=this.parseBody("if (...)"),s;return this.match("ELSE")&&(this.match("IF")?s=this.parseIfStatement():s=this.parseBody("else")),{kind:"IfStmt",condition:n,thenBranch:r,elseBranch:s,line:e.line,column:e.column}}parseWhileStatement(){let e=this.previous();this.consume("LPAREN","Expected '(' after 'while'");let n=this.parseExpression();this.consume("RPAREN","Expected ')' after while condition");let r=this.parseBody("while (...)");return{kind:"WhileStmt",condition:n,body:r,line:e.line,column:e.column}}parseForStatement(){let e=this.previous();if(this.consume("LPAREN","Expected '(' after 'for'"),this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="COMMA")throw new P("For-in requires one binding; use 'for (x in expr)'",this.peek().line,this.peek().column,"PARSER",this.file);if(this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="IDENTIFIER"&&this.tokens[this.current+1].value==="in"){let o=this.advance().value;if(this.advance(),this.check("RPAREN"))throw new P("Expected expression after 'in' in for-loop",this.peek().line,this.peek().column,"PARSER",this.file);let a=this.parseExpression();this.consume("RPAREN","Expected ')' after for-in expression");let l=this.parseBody("for (... in ...)");return{kind:"ForInStmt",name:o,iterable:a,body:l,line:e.line,column:e.column}}let n;if(this.match("SEMICOLON"))n=void 0;else if(this.match("LET"))n=this.parseVarDecl();else{let o=this.parseExpression();this.consume("SEMICOLON","Expected ';' after for-loop initializer"),n={kind:"ExprStmt",expr:o,line:o.line,column:o.column}}let r;this.check("SEMICOLON")||(r=this.parseExpression()),this.consume("SEMICOLON","Expected ';' after for-loop condition");let s;this.check("RPAREN")||(s=this.parseExpression()),this.consume("RPAREN","Expected ')' after for clauses");let i=this.parseBody("for (...)");return{kind:"ForStmt",init:n,condition:r,update:s,body:i,line:e.line,column:e.column}}parseReturnStatement(){let e=this.previous(),n;return!this.check("SEMICOLON")&&!this.check("RBRACE")&&!this.isAtEnd()&&(n=this.parseExpression()),this.endStatement("return"),{kind:"ReturnStmt",value:n,line:e.line,column:e.column}}parseBreakStatement(){let e=this.previous();return this.endStatement("break"),{kind:"BreakStmt",line:e.line,column:e.column}}parseContinueStatement(){let e=this.previous();return this.endStatement("continue"),{kind:"ContinueStmt",line:e.line,column:e.column}}parseBlockBody(e){let n=[];for(;!this.check("RBRACE")&&!this.isAtEnd();)n.push(this.parseDeclaration());return this.consume("RBRACE","Expected '}' after block"),{kind:"BlockStmt",statements:n,line:e.line,column:e.column}}parseExpressionStatement(){let e=this.parseExpression();return this.endStatement(e.kind==="CommandExpr"?"the command":e.kind==="AssignExpr"||e.kind==="IndexAssignExpr"||e.kind==="TupleAssignExpr"?"the assignment":"the expression statement"),{kind:"ExprStmt",expr:e,line:e.line,column:e.column}}parseExpression(){return this.parseAssignment()}parseAssignment(){let e=this.parseConditional();if(this.match("ASSIGN")){let n=this.previous(),r=this.parseAssignment();if(e.kind==="IdentifierExpr")return{kind:"AssignExpr",target:e.name,value:r,line:n.line,column:n.column};if(e.kind==="IndexExpr")return{kind:"IndexAssignExpr",target:e.target,index:e.index,value:r,line:n.line,column:n.column};if(e.kind==="TupleExpr"||e.kind==="ArrayExpr"){let s=[];for(let i of e.elements){if(i.kind!=="IdentifierExpr")throw new P("Destructuring assignment target must be an identifier",i.line,i.column,"PARSER",this.file);s.push(i.name)}return{kind:"TupleAssignExpr",targets:s,value:r,line:n.line,column:n.column}}throw new P("Invalid assignment target",e.line,e.column,"PARSER",this.file)}return e}parseConditional(){let e=this.parseLogicalOr();if(!this.match("QUESTION"))return e;let n=this.previous(),r=()=>["COLON","SEMICOLON","RPAREN","RBRACKET","RBRACE","FSTRING_FIELD_END","EOF","COMMA"].includes(this.peek().type);if(r())throw new P("Expected true arm after '?'",this.peek().line,this.peek().column,"PARSER",this.file);let s=this.parseAssignment();if(this.consume("COLON","Expected ':' after conditional true arm"),r())throw new P("Expected false arm after ':'",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.parseAssignment();return{kind:"ConditionalExpr",condition:e,trueArm:s,falseArm:i,line:n.line,column:n.column}}parseLogicalOr(){let e=this.parseLogicalAnd();for(;this.match("OR");){let n=this.previous(),r=this.parseLogicalAnd();e={kind:"BinaryExpr",operator:"||",left:e,right:r,line:n.line,column:n.column}}return e}parseLogicalAnd(){let e=this.parseEquality();for(;this.match("AND");){let n=this.previous(),r=this.parseEquality();e={kind:"BinaryExpr",operator:"&&",left:e,right:r,line:n.line,column:n.column}}return e}parseEquality(){let e=this.parseRelational();for(;this.match("EQUAL","NOT_EQUAL");){let n=this.previous(),r=n.type==="EQUAL"?"==":"!=",s=this.parseRelational();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseRelational(){let e=this.parseAdditive();for(;this.match("LESS","LESS_EQUAL","GREATER","GREATER_EQUAL");){let n=this.previous(),r=n.type==="LESS"?"<":n.type==="LESS_EQUAL"?"<=":n.type==="GREATER"?">":">=",s=this.parseAdditive();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseAdditive(){let e=this.parseMultiplicative();for(;this.match("PLUS","MINUS");){let n=this.previous(),r=n.type==="PLUS"?"+":"-",s=this.parseMultiplicative();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseMultiplicative(){let e=this.parseUnary();for(;this.match("STAR","SLASH","PERCENT");){let n=this.previous(),r=n.type==="STAR"?"*":n.type==="SLASH"?"/":"%",s=this.parseUnary();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseUnary(){if(this.match("NOT","MINUS")){let e=this.previous(),n=e.type==="NOT"?"!":"-",r=this.parseUnary();return{kind:"UnaryExpr",operator:n,operand:r,line:e.line,column:e.column}}return this.parseCallAndIndex()}parseCallAndIndex(){let e=this.parsePrimary();for(;;)if(this.match("LPAREN")){if(e.kind==="IndexExpr"&&!e.dot&&e.index.kind==="LiteralExpr"&&typeof e.index.value=="string")throw new P(_s(e.target)+"["+JSON.stringify(e.index.value)+"](...) is not a call form; write "+_s(e.target)+"."+e.index.value+"(...) for a method or call a named function",e.line,e.column,"PARSER",this.file);let n=e.kind==="IndexExpr"&&e.dot?e:void 0,r=n?void 0:e.kind==="IdentifierExpr"?e.name:void 0;if(!n&&!r)throw new P("Only identifiers or package members can be called directly as functions",e.line,e.column,"PARSER",this.file);let s=[];if(!this.check("RPAREN"))do s.push(this.parseExpression());while(this.match("COMMA"));this.consume("RPAREN","Expected ')' after function arguments"),e=n?{kind:"MethodCallExpr",receiver:n.target,method:n.index.kind==="LiteralExpr"?String(n.index.value):"",args:s,line:n.line,column:n.column,file:this.file}:{kind:"CallExpr",callee:r,args:s,line:e.line,column:e.column}}else if(this.match("LBRACKET")){let n=this.parseExpression();this.consume("RBRACKET","Expected ']' after index expression"),e={kind:"IndexExpr",target:e,index:n,line:e.line,column:e.column}}else if(this.match("DOT")){let n=this.consume("IDENTIFIER","Expected property name after '.'");e={kind:"IndexExpr",target:e,index:{kind:"LiteralExpr",value:n.value,line:n.line,column:n.column},dot:!0,line:n.line,column:n.column}}else break;return e}parsePrimary(){let e=this.peek();if(e.type==="SYSTEM"||e.type==="CMD"){if(this.advance(),e.type==="SYSTEM"&&this.check("LPAREN")&&this.peek().line===e.line)return{kind:"IdentifierExpr",name:"system",line:e.line,column:e.column};if(!this.match("COMMAND_START"))throw this.check("ASSIGN")||this.check("LPAREN")?this.reservedWord(e):new P("command: a program is required; keywords cmd/system are reserved, rename a binding with this name",e.line,e.column,"PARSER",this.file);let n=[],r=[],s=this.peek();for(;!this.check("COMMAND_END")&&!this.isAtEnd();){if(this.match("COMMAND_TEXT")){let l=this.previous();l.literal==="boundary"?(r.length&&n.push({pieces:r,line:s.line,column:s.column,file:this.file}),r=[],s=this.peek()):r.push(l.value);continue}let i=this.consume("FSTRING_FIELD_START","Expected command text or field"),o=this.parseExpression();this.validateFieldExpression(o);let a=this.consume("FSTRING_FIELD_END","Expected '}' after command field");r.push({expression:o,line:i.line,column:i.column,file:this.file,offset:i.offset,endOffset:a.endOffset})}return this.check("COMMAND_END")||this.consume("COMMAND_END","Unterminated command"),{kind:"CommandExpr",mode:e.type==="SYSTEM"?"system":"cmd",words:n,line:e.line,column:e.column,file:this.file}}if(this.check("FSTRING_START"))return this.parseFormattedString();if(this.match("PATH")){let n=this.previous().literal;return{kind:"PathLiteralExpr",mode:n.mode,raw:n.raw,line:e.line,column:e.column,file:this.file}}if(this.match("NUMBER","STRING","TRUE","FALSE","NULL"))return{kind:"LiteralExpr",value:this.previous().literal,line:e.line,column:e.column};if(this.match("MARKER")){if(this.check("ASSIGN"))throw new P(e.value+" is a reserved block marker and cannot be assigned; rename this binding",e.line,e.column,"PARSER",this.file);let n=[];for(;this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="ASSIGN";){let s=this.advance();if(this.advance(),n.some(o=>o.name===s.value))throw new P(e.value+": option '"+s.value+"' is given twice",s.line,s.column,"PARSER",this.file);if(this.check("MARKER"))throw new P(e.value+": a block marker cannot be an option value",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.parseCallAndIndex();n.push({name:s.value,value:i,line:s.line,column:s.column,file:this.file})}let r=(e.block?.lines??[]).map(s=>({line:s.line,column:s.column,pieces:s.pieces.map(i=>typeof i=="string"?i:this.parseBlockField(i,e))}));return{kind:"DataBlockExpr",marker:e.literal,options:n,lines:r,closed:e.block?.closed??!1,line:e.line,column:e.column,file:this.file}}if(this.match("PARALLEL")){let n=this.previous();this.consume("LPAREN","Expected '(' after 'parallel'"),this.consume("LBRACKET","Expected '[' starting array of function calls in 'parallel([ ... ])'");let r=[];if(!this.check("RBRACKET"))do{let s=this.parseExpression();if(s.kind!=="CallExpr")throw new P("Each task in parallel([...]) must be a direct function call expression",s.line,s.column,"PARSER",this.file);r.push(s)}while(this.match("COMMA"));return this.consume("RBRACKET","Expected ']' closing array in 'parallel([ ... ])'"),this.consume("RPAREN","Expected ')' closing 'parallel(...)'"),{kind:"ParallelExpr",tasks:r,line:n.line,column:n.column}}if(this.match("IDENTIFIER"))return{kind:"IdentifierExpr",name:this.previous().value,line:e.line,column:e.column};if(this.match("LPAREN")){let n=this.previous(),r=[],s=!1;if(!this.check("RPAREN"))do if(r.push(this.parseExpression()),this.match("COMMA"))s=!0;else break;while(!this.check("RPAREN")&&!this.isAtEnd());return this.consume("RPAREN","Expected ')' after expression"),r.length===1&&!s?{kind:"GroupingExpr",expression:r[0],line:n.line,column:n.column}:{kind:"TupleExpr",elements:r,line:n.line,column:n.column}}if(this.match("LBRACKET")){let n=[];if(!this.check("RBRACKET"))do n.push(this.parseExpression());while(this.match("COMMA"));return this.consume("RBRACKET","Expected ']' after array elements"),{kind:"ArrayExpr",elements:n,line:e.line,column:e.column}}if(this.match("LBRACE")){let n=this.previous(),r=[];if(!this.check("RBRACE"))do{let s="";if(this.match("IDENTIFIER","STRING","SYSTEM","CMD"))s=this.previous().value;else throw new P("Expected object key (identifier or string)",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.previous();this.consume("COLON","Expected ':' after object key");let o=this.parseExpression();r.push({key:s,value:o,line:i.line,column:i.column})}while(this.match("COMMA"));return this.consume("RBRACE","Expected '}' after object literal"),{kind:"ObjectExpr",properties:r,line:n.line,column:n.column}}throw e.type==="RECOVER"||e.type==="RESUMEPOINT"?this.reservedWord(e):new P(`Unexpected token '${e.value||e.type}' in expression`,e.line,e.column,"PARSER",this.file)}parseFormattedString(){let e=this.advance(),n=[];for(;!this.check("FSTRING_END")&&!this.isAtEnd();){if(this.match("FSTRING_TEXT")){n.push(this.previous().value);continue}let r=this.consume("FSTRING_FIELD_START","Expected formatted-string text or field"),s=this.parseExpression();this.validateFieldExpression(s);let i=this.consume("FSTRING_FIELD_END","Expected '}' after one formatted-string expression");n.push({expression:s,line:r.line,column:r.column,file:this.file,offset:r.offset,endOffset:i.endOffset})}return this.consume("FSTRING_END","Unterminated formatted string"),{kind:"InterpolatedStringExpr",parts:n,line:e.line,column:e.column,file:this.file}}parseBlockField(e,n){let r=e.tokens.slice(1,-1),s=e.tokens[e.tokens.length-1],i=new t([...r,{type:"EOF",value:"",line:s.line,column:s.column}],this.file),o=i.parseExpression();if(!i.isAtEnd()){let a=i.peek();throw new P(n.value+": unexpected '"+a.value+"' in a ${...} field; one expression per field",a.line,a.column,"PARSER",this.file)}return this.validateFieldExpression(o),{expression:o,line:e.line,column:e.column,file:this.file,offset:e.offset,endOffset:e.endOffset}}validateFieldExpression(e){let n=[];switch(e.kind){case"DataBlockExpr":throw new P("_"+e.marker+"_ inside a field is not supported yet; assign the block to a variable first",e.line,e.column,"PARSER",this.file);case"CommandExpr":case"AssignExpr":case"IndexAssignExpr":case"TupleAssignExpr":case"ParallelExpr":throw new P("Assignment and parallel expressions are not permitted in formatted-string fields",e.line,e.column,"PARSER",this.file);case"UnaryExpr":n=[e.operand];break;case"BinaryExpr":n=[e.left,e.right];break;case"ConditionalExpr":n=[e.condition,e.trueArm,e.falseArm];break;case"GroupingExpr":n=[e.expression];break;case"CallExpr":n=e.args;break;case"MethodCallExpr":n=[e.receiver,...e.args];break;case"ArrayExpr":case"TupleExpr":n=e.elements;break;case"ObjectExpr":n=e.properties.map(r=>r.value);break;case"IndexExpr":n=[e.target,e.index];break;case"InterpolatedStringExpr":n=e.parts.flatMap(r=>typeof r=="string"?[]:[r.expression]);break;case"LiteralExpr":case"PathLiteralExpr":case"IdentifierExpr":break}for(let r of n)this.validateFieldExpression(r)}match(...e){for(let n of e)if(this.check(n))return this.advance(),!0;return!1}check(e){return this.isAtEnd()?!1:this.peek().type===e}advance(){return this.isAtEnd()||this.current++,this.previous()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}previous(){return this.tokens[this.current-1]}consume(e,n){if(this.check(e)||e==="IDENTIFIER"&&n.startsWith("Expected property name")&&["SYSTEM","CMD"].includes(this.peek().type))return this.advance();let r=this.peek();throw r.type==="COMMAND_END"?new P("command: allowed only as a simple initializer, return value, or standalone system statement",r.line,r.column,"PARSER",this.file):e==="IDENTIFIER"&&["RECOVER","RESUMEPOINT","SYSTEM","CMD"].includes(r.type)&&!n.startsWith("Expected property name")?this.reservedWord(r):new P(n,r.line,r.column,"PARSER",this.file)}endStatement(e){let n=this.previous();this.check("COMMAND_END")&&(this.advance(),n=this.tokens[this.current-2]??n),!this.match("SEMICOLON")&&this.violation({kind:"semicolon",what:e,line:n.endLine??n.line,column:n.endColumn??n.column,insertAt:n.endOffset??0})}},dL=`# LMScript Standard Library (std.lms)
|
|
329
|
+
`?this.consumeStringNewline():this.advance())):p+=this.readModernEscape(u.formatted):p+=this.advance()}throw new P("Unterminated string literal; expected "+c,n,r,"LEXER",this.file)}finally{this.modes.pop(),u.formatted&&this.formattedDepth--}}readIdentifier(e,n){let r="";for(;this.pos<this.length&&this.isAlphaNumeric(this.peek());)r+=this.advance();let s=Object.hasOwn(Co,r)?Co[r]:void 0;if(s){let i;return s==="TRUE"&&(i=!0),s==="FALSE"&&(i=!1),s==="NULL"&&(i=null),{type:s,value:r,literal:i,line:e,column:n}}return{type:"IDENTIFIER",value:r,line:e,column:n}}};ko=Object.freeze({shell_quote:"cmd_render",count:"len",findall:"regex_find_all",has:"has_key",match:"regex_match",test:"regex_test",search:"regex_match",sorted:"sort",length:"len",date_format:"strftime",default_to:"if_null",to_string:"str",regex_split:"split_regex",regexSplit:"split_regex",splitRegex:"split_regex",clock:"now",json:"json_parse",sleep:"sleep_ms",urlget:"http_get",capitalize:"upper_first",startsWith:"starts_with",endsWith:"ends_with",indexOf:"index_of",lastIndexOf:"last_index_of",toUpperCase:"upper",toLowerCase:"lower",parseInt:"num",parseFloat:"num",trimStart:"trim_start",trimLeft:"trim_start",trimEnd:"trim_end",trimRight:"trim_end",strip:"trim",lstrip:"trim_start",rstrip:"trim_end",append:"push",prepend:"unshift",push_back:"push",push_front:"unshift","JSON.parse":"json_parse","JSON.stringify":"json_stringify"});Fl=class t{tokens;current=0;file;constructor(e,n){this.tokens=e,this.file=n}violation(e){throw e.kind==="semicolon"?new P("Expected ';' after "+e.what+"; every statement ends with ';'",e.line,e.column,"PARSER",this.file):new P("Expected '{' after "+e.keyword+"; braces are required around every if/else/while/for body (write "+e.keyword+" { ... })",e.line,e.column,"PARSER",this.file)}parseBody(e){if(this.check("LBRACE"))return this.parseStatement();let n=this.previous(),r=this.peek(),s=this.parseStatement(),i=this.previous();return this.violation({kind:"braces",keyword:e,line:r.line,column:r.column,headerLine:n.endLine??n.line,openAt:n.endOffset??0,closeAt:i.endOffset??0}),s}parse(){let e=[],n=this.peek();for(;!this.isAtEnd();)e.push(this.parseDeclaration(!0));let r={kind:"Program",statements:e,line:n.line,column:n.column,file:this.file};return this.file&&$m(r,this.file),r}span(e,n){let r=this.previous();return e.start===void 0&&n.offset!==void 0&&r?.endOffset!==void 0&&(e.start=n.offset,e.end=r.endOffset),e}parseDeclaration(e=!1){let n=this.peek();return this.span(this.parseDeclarationInner(e),n)}parseDeclarationInner(e=!1){try{if(this.match("FN"))return this.parseFunctionDecl();if(this.match("LET"))return this.parseVarDecl();if(this.match("IMPORT")){if(!e)throw new P("'import' statement is only allowed at the top level of a file",this.previous().line,this.previous().column,"PARSER",this.file);return this.parseImportStmt()}return this.parseStatement()}catch(n){throw n instanceof P?n:new P(n instanceof Error?n.message:String(n),this.peek().line,this.peek().column,"PARSER",this.file)}}parsePackageStmt(){let e=this.advance(),n=this.consume("IDENTIFIER","Expected package name");return this.consume("SEMICOLON","Expected ';' after package name"),{kind:"PackageStmt",name:n.value,line:e.line,column:e.column,file:this.file}}parseImportStmt(){let e=this.previous();if(this.check("FSTRING_START")){let s=this.peek();throw new P("Import paths require an unprefixed short string",s.line,s.column,"PARSER",this.file)}let n=this.consume("STRING","Expected file path string after 'import'");if(!n.value.trim())throw new P("Import specifier must not be empty or whitespace-only",e.line,e.column,"CHECKER",this.file);if(/[\0\r\n]/.test(n.value))throw new P("Import specifier must not contain NUL or line breaks",e.line,e.column,"CHECKER",this.file);if(n.stringLiteral?.prefix||n.stringLiteral?.triple)throw new P("Import paths require an unprefixed short string",n.line,n.column,"PARSER",this.file);let r;if(this.check("IDENTIFIER")&&this.peek().value==="as"&&this.peek().line===n.line){let s=this.advance();if(!Oo(n.value)&&!vs(n.value))throw new P("`as` is only supported for schema modules (@owner/repo or *.cli.schema.v1.json); a source module's namespace is its package declaration and a native module's is its name",s.line,s.column,"PARSER",this.file);r=this.consume("IDENTIFIER","Expected a namespace identifier after 'as'").value}return this.endStatement("the import path"),{kind:"ImportStmt",path:n.value,...r!==void 0?{alias:r}:{},hostModule:qv(n.value),line:e.line,column:e.column,file:this.file}}parseFunctionDecl(){let e=this.previous(),n=this.consume("IDENTIFIER","Expected function name after 'fn'");this.consume("LPAREN","Expected '(' after function name");let r=[];if(!this.check("RPAREN"))do{let i=this.consume("IDENTIFIER","Expected parameter name");r.push(i.value)}while(this.match("COMMA"));this.consume("RPAREN","Expected ')' after parameters"),this.consume("LBRACE","Expected '{' before function body");let s=this.parseBlockBody(this.previous());return{kind:"FunctionDeclStmt",name:n.value,params:r,body:s,line:e.line,column:e.column}}parseVarDecl(){let e=this.previous();if(this.match("LPAREN","LBRACKET")){let s=this.previous().type==="LPAREN"?"RPAREN":"RBRACKET",i=[];if(!this.check(s))do{let a=this.consume("IDENTIFIER","Expected variable name in destructuring");i.push(a.value)}while(this.match("COMMA"));this.consume(s,`Expected '${s==="RPAREN"?")":"]"}' after variable list`),this.consume("ASSIGN","Expected '=' after destructuring variable list");let o=this.parseExpression();return this.endStatement("the let declaration"),{kind:"TupleVarDeclStmt",names:i,initializer:o,line:e.line,column:e.column}}let n=this.consume("IDENTIFIER","Expected variable name after 'let'"),r;return this.match("ASSIGN")&&(r=this.parseExpression()),this.endStatement("the let declaration"),{kind:"VarDeclStmt",name:n.value,initializer:r,line:e.line,column:e.column}}parseStatement(){let e=this.peek();return this.span(this.parseStatementInner(),e)}parseStatementInner(){if(this.check("IDENTIFIER")&&this.peek().value==="package"&&["IDENTIFIER","SYSTEM","CMD"].includes(this.tokens[this.current+1]?.type))return this.parsePackageStmt();if(this.match("IMPORT"))throw new P("'import' statement is only allowed at the top level of a file",this.previous().line,this.previous().column,"PARSER",this.file);if(this.match("SYNC"))return this.parseSyncStatement();if(this.match("IF"))return this.parseIfStatement();if(this.match("WHILE"))return this.parseWhileStatement();if(this.match("FOR"))return this.parseForStatement();if(this.match("RETURN"))return this.parseReturnStatement();if(this.match("BREAK"))return this.parseBreakStatement();if(this.match("CONTINUE"))return this.parseContinueStatement();if(this.match("LBRACE"))return this.parseBlockBody(this.previous());if(this.match("RECOVER"))return this.parseRecover();if(this.match("RESUMEPOINT")){let e=this.previous();throw this.check("IDENTIFIER")?new P("resumepoint is only allowed directly in a recover body",e.line,e.column,"PARSER",this.file):this.reservedWord(e)}return this.parseExpressionStatement()}reservedWord(e){return new P(`'${e.value}' is reserved; rename this binding`,e.line,e.column,"PARSER",this.file)}parseRecover(){let e=this.previous();if(!this.match("LBRACE"))throw this.reservedWord(e);let n=[];for(;!this.check("RBRACE")&&!this.isAtEnd();){if(this.match("RESUMEPOINT")){let r=this.previous(),s=this.peek();if(["ASSIGN","LPAREN","SEMICOLON"].includes(s.type))throw this.reservedWord(r);if(s.type!=="IDENTIFIER"||!/^[A-Za-z_][A-Za-z0-9_]*$/.test(s.value)||this.tokens[this.current+1]?.type!=="COLON")throw new P("Expected a resumepoint name (letters, digits and _, not a keyword) followed by ':'",s.line,s.column,"PARSER",this.file);this.advance(),this.advance(),n.push({kind:"ResumePoint",name:s.value,statements:[],line:r.line,column:r.column});continue}if(n.length===0)throw new P("Statements before the first resumepoint are not allowed",this.peek().line,this.peek().column,"PARSER",this.file);n[n.length-1].statements.push(this.parseDeclaration())}if(this.consume("RBRACE","Expected '}' after recover body"),n.length===0)throw new P("recover requires at least one resumepoint",e.line,e.column,"PARSER",this.file);return{kind:"RecoverStmt",points:n,line:e.line,column:e.column}}parseSyncStatement(){let e=this.previous(),n=this.parseExpression();if(this.endStatement("the sync statement"),n.kind==="AssignExpr")return{kind:"SyncAssignStmt",target:n.target,value:n.value,line:e.line,column:e.column};if(n.kind==="IndexAssignExpr")return{kind:"SyncIndexAssignStmt",target:n.target,index:n.index,value:n.value,line:e.line,column:e.column};if(n.kind==="CallExpr")return{kind:"SyncCallStmt",call:n,line:e.line,column:e.column};throw new P("Invalid 'sync' statement target; expected variable assignment ('sync a = ...'), member/index assignment ('sync obj.k = ...'), or container mutation call ('sync push(arr, ...)').",e.line,e.column,"PARSER",this.file)}parseIfStatement(){let e=this.previous();this.consume("LPAREN","Expected '(' after 'if'");let n=this.parseExpression();this.consume("RPAREN","Expected ')' after if condition");let r=this.parseBody("if (...)"),s;return this.match("ELSE")&&(this.match("IF")?s=this.parseIfStatement():s=this.parseBody("else")),{kind:"IfStmt",condition:n,thenBranch:r,elseBranch:s,line:e.line,column:e.column}}parseWhileStatement(){let e=this.previous();this.consume("LPAREN","Expected '(' after 'while'");let n=this.parseExpression();this.consume("RPAREN","Expected ')' after while condition");let r=this.parseBody("while (...)");return{kind:"WhileStmt",condition:n,body:r,line:e.line,column:e.column}}parseForStatement(){let e=this.previous();if(this.consume("LPAREN","Expected '(' after 'for'"),this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="COMMA")throw new P("For-in requires one binding; use 'for (x in expr)'",this.peek().line,this.peek().column,"PARSER",this.file);if(this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="IDENTIFIER"&&this.tokens[this.current+1].value==="in"){let o=this.advance().value;if(this.advance(),this.check("RPAREN"))throw new P("Expected expression after 'in' in for-loop",this.peek().line,this.peek().column,"PARSER",this.file);let a=this.parseExpression();this.consume("RPAREN","Expected ')' after for-in expression");let l=this.parseBody("for (... in ...)");return{kind:"ForInStmt",name:o,iterable:a,body:l,line:e.line,column:e.column}}let n;if(this.match("SEMICOLON"))n=void 0;else if(this.match("LET"))n=this.parseVarDecl();else{let o=this.parseExpression();this.consume("SEMICOLON","Expected ';' after for-loop initializer"),n={kind:"ExprStmt",expr:o,line:o.line,column:o.column}}let r;this.check("SEMICOLON")||(r=this.parseExpression()),this.consume("SEMICOLON","Expected ';' after for-loop condition");let s;this.check("RPAREN")||(s=this.parseExpression()),this.consume("RPAREN","Expected ')' after for clauses");let i=this.parseBody("for (...)");return{kind:"ForStmt",init:n,condition:r,update:s,body:i,line:e.line,column:e.column}}parseReturnStatement(){let e=this.previous(),n;return!this.check("SEMICOLON")&&!this.check("RBRACE")&&!this.isAtEnd()&&(n=this.parseExpression()),this.endStatement("return"),{kind:"ReturnStmt",value:n,line:e.line,column:e.column}}parseBreakStatement(){let e=this.previous();return this.endStatement("break"),{kind:"BreakStmt",line:e.line,column:e.column}}parseContinueStatement(){let e=this.previous();return this.endStatement("continue"),{kind:"ContinueStmt",line:e.line,column:e.column}}parseBlockBody(e){let n=[];for(;!this.check("RBRACE")&&!this.isAtEnd();)n.push(this.parseDeclaration());return this.consume("RBRACE","Expected '}' after block"),{kind:"BlockStmt",statements:n,line:e.line,column:e.column}}parseExpressionStatement(){let e=this.parseExpression();return this.endStatement(e.kind==="CommandExpr"?"the command":e.kind==="AssignExpr"||e.kind==="IndexAssignExpr"||e.kind==="TupleAssignExpr"?"the assignment":"the expression statement"),{kind:"ExprStmt",expr:e,line:e.line,column:e.column}}parseExpression(){return this.parseAssignment()}parseAssignment(){let e=this.parseConditional();if(this.match("ASSIGN")){let n=this.previous(),r=this.parseAssignment();if(e.kind==="IdentifierExpr")return{kind:"AssignExpr",target:e.name,value:r,line:n.line,column:n.column};if(e.kind==="IndexExpr")return{kind:"IndexAssignExpr",target:e.target,index:e.index,value:r,line:n.line,column:n.column};if(e.kind==="TupleExpr"||e.kind==="ArrayExpr"){let s=[];for(let i of e.elements){if(i.kind!=="IdentifierExpr")throw new P("Destructuring assignment target must be an identifier",i.line,i.column,"PARSER",this.file);s.push(i.name)}return{kind:"TupleAssignExpr",targets:s,value:r,line:n.line,column:n.column}}throw new P("Invalid assignment target",e.line,e.column,"PARSER",this.file)}return e}parseConditional(){let e=this.parseLogicalOr();if(!this.match("QUESTION"))return e;let n=this.previous(),r=()=>["COLON","SEMICOLON","RPAREN","RBRACKET","RBRACE","FSTRING_FIELD_END","EOF","COMMA"].includes(this.peek().type);if(r())throw new P("Expected true arm after '?'",this.peek().line,this.peek().column,"PARSER",this.file);let s=this.parseAssignment();if(this.consume("COLON","Expected ':' after conditional true arm"),r())throw new P("Expected false arm after ':'",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.parseAssignment();return{kind:"ConditionalExpr",condition:e,trueArm:s,falseArm:i,line:n.line,column:n.column}}parseLogicalOr(){let e=this.parseLogicalAnd();for(;this.match("OR");){let n=this.previous(),r=this.parseLogicalAnd();e={kind:"BinaryExpr",operator:"||",left:e,right:r,line:n.line,column:n.column}}return e}parseLogicalAnd(){let e=this.parseEquality();for(;this.match("AND");){let n=this.previous(),r=this.parseEquality();e={kind:"BinaryExpr",operator:"&&",left:e,right:r,line:n.line,column:n.column}}return e}parseEquality(){let e=this.parseRelational();for(;this.match("EQUAL","NOT_EQUAL");){let n=this.previous(),r=n.type==="EQUAL"?"==":"!=",s=this.parseRelational();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseRelational(){let e=this.parseAdditive();for(;this.match("LESS","LESS_EQUAL","GREATER","GREATER_EQUAL");){let n=this.previous(),r=n.type==="LESS"?"<":n.type==="LESS_EQUAL"?"<=":n.type==="GREATER"?">":">=",s=this.parseAdditive();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseAdditive(){let e=this.parseMultiplicative();for(;this.match("PLUS","MINUS");){let n=this.previous(),r=n.type==="PLUS"?"+":"-",s=this.parseMultiplicative();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseMultiplicative(){let e=this.parseUnary();for(;this.match("STAR","SLASH","PERCENT");){let n=this.previous(),r=n.type==="STAR"?"*":n.type==="SLASH"?"/":"%",s=this.parseUnary();e={kind:"BinaryExpr",operator:r,left:e,right:s,line:n.line,column:n.column}}return e}parseUnary(){if(this.match("NOT","MINUS")){let e=this.previous(),n=e.type==="NOT"?"!":"-",r=this.parseUnary();return{kind:"UnaryExpr",operator:n,operand:r,line:e.line,column:e.column}}return this.parseCallAndIndex()}parseCallAndIndex(){let e=this.parsePrimary();for(;;)if(this.match("LPAREN")){if(e.kind==="IndexExpr"&&!e.dot&&e.index.kind==="LiteralExpr"&&typeof e.index.value=="string")throw new P(_s(e.target)+"["+JSON.stringify(e.index.value)+"](...) is not a call form; write "+_s(e.target)+"."+e.index.value+"(...) for a method or call a named function",e.line,e.column,"PARSER",this.file);let n=e.kind==="IndexExpr"&&e.dot?e:void 0,r=n?void 0:e.kind==="IdentifierExpr"?e.name:void 0;if(!n&&!r)throw new P("Only identifiers or package members can be called directly as functions",e.line,e.column,"PARSER",this.file);let s=[];if(!this.check("RPAREN"))do s.push(this.parseExpression());while(this.match("COMMA"));this.consume("RPAREN","Expected ')' after function arguments"),e=n?{kind:"MethodCallExpr",receiver:n.target,method:n.index.kind==="LiteralExpr"?String(n.index.value):"",args:s,line:n.line,column:n.column,file:this.file}:{kind:"CallExpr",callee:r,args:s,line:e.line,column:e.column}}else if(this.match("LBRACKET")){let n=this.parseExpression();this.consume("RBRACKET","Expected ']' after index expression"),e={kind:"IndexExpr",target:e,index:n,line:e.line,column:e.column}}else if(this.match("DOT")){let n=this.consume("IDENTIFIER","Expected property name after '.'");e={kind:"IndexExpr",target:e,index:{kind:"LiteralExpr",value:n.value,line:n.line,column:n.column},dot:!0,line:n.line,column:n.column}}else break;return e}parsePrimary(){let e=this.peek();if(e.type==="SYSTEM"||e.type==="CMD"){if(this.advance(),e.type==="SYSTEM"&&this.check("LPAREN")&&this.peek().line===e.line)return{kind:"IdentifierExpr",name:"system",line:e.line,column:e.column};if(!this.match("COMMAND_START"))throw this.check("ASSIGN")||this.check("LPAREN")?this.reservedWord(e):new P("command: a program is required; keywords cmd/system are reserved, rename a binding with this name",e.line,e.column,"PARSER",this.file);let n=[],r=[],s=this.peek();for(;!this.check("COMMAND_END")&&!this.isAtEnd();){if(this.match("COMMAND_TEXT")){let l=this.previous();l.literal==="boundary"?(r.length&&n.push({pieces:r,line:s.line,column:s.column,file:this.file}),r=[],s=this.peek()):r.push(l.value);continue}let i=this.consume("FSTRING_FIELD_START","Expected command text or field"),o=this.parseExpression();this.validateFieldExpression(o);let a=this.consume("FSTRING_FIELD_END","Expected '}' after command field");r.push({expression:o,line:i.line,column:i.column,file:this.file,offset:i.offset,endOffset:a.endOffset})}return this.check("COMMAND_END")||this.consume("COMMAND_END","Unterminated command"),{kind:"CommandExpr",mode:e.type==="SYSTEM"?"system":"cmd",words:n,line:e.line,column:e.column,file:this.file}}if(this.check("FSTRING_START"))return this.parseFormattedString();if(this.match("PATH")){let n=this.previous().literal;return{kind:"PathLiteralExpr",mode:n.mode,raw:n.raw,line:e.line,column:e.column,file:this.file}}if(this.match("NUMBER","STRING","TRUE","FALSE","NULL"))return{kind:"LiteralExpr",value:this.previous().literal,line:e.line,column:e.column};if(this.match("MARKER")){if(this.check("ASSIGN"))throw new P(e.value+" is a reserved block marker and cannot be assigned; rename this binding",e.line,e.column,"PARSER",this.file);let n=[];for(;this.check("IDENTIFIER")&&this.tokens[this.current+1]?.type==="ASSIGN";){let s=this.advance();if(this.advance(),n.some(o=>o.name===s.value))throw new P(e.value+": option '"+s.value+"' is given twice",s.line,s.column,"PARSER",this.file);if(this.check("MARKER"))throw new P(e.value+": a block marker cannot be an option value",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.parseCallAndIndex();n.push({name:s.value,value:i,line:s.line,column:s.column,file:this.file})}let r=(e.block?.lines??[]).map(s=>({line:s.line,column:s.column,pieces:s.pieces.map(i=>typeof i=="string"?i:this.parseBlockField(i,e))}));return{kind:"DataBlockExpr",marker:e.literal,options:n,lines:r,closed:e.block?.closed??!1,line:e.line,column:e.column,file:this.file}}if(this.match("PARALLEL")){let n=this.previous();this.consume("LPAREN","Expected '(' after 'parallel'"),this.consume("LBRACKET","Expected '[' starting array of function calls in 'parallel([ ... ])'");let r=[];if(!this.check("RBRACKET"))do{let s=this.parseExpression();if(s.kind!=="CallExpr")throw new P("Each task in parallel([...]) must be a direct function call expression",s.line,s.column,"PARSER",this.file);r.push(s)}while(this.match("COMMA"));return this.consume("RBRACKET","Expected ']' closing array in 'parallel([ ... ])'"),this.consume("RPAREN","Expected ')' closing 'parallel(...)'"),{kind:"ParallelExpr",tasks:r,line:n.line,column:n.column}}if(this.match("IDENTIFIER"))return{kind:"IdentifierExpr",name:this.previous().value,line:e.line,column:e.column};if(this.match("LPAREN")){let n=this.previous(),r=[],s=!1;if(!this.check("RPAREN"))do if(r.push(this.parseExpression()),this.match("COMMA"))s=!0;else break;while(!this.check("RPAREN")&&!this.isAtEnd());return this.consume("RPAREN","Expected ')' after expression"),r.length===1&&!s?{kind:"GroupingExpr",expression:r[0],line:n.line,column:n.column}:{kind:"TupleExpr",elements:r,line:n.line,column:n.column}}if(this.match("LBRACKET")){let n=[];if(!this.check("RBRACKET"))do n.push(this.parseExpression());while(this.match("COMMA"));return this.consume("RBRACKET","Expected ']' after array elements"),{kind:"ArrayExpr",elements:n,line:e.line,column:e.column}}if(this.match("LBRACE")){let n=this.previous(),r=[];if(!this.check("RBRACE"))do{let s="";if(this.match("IDENTIFIER","STRING","SYSTEM","CMD"))s=this.previous().value;else throw new P("Expected object key (identifier or string)",this.peek().line,this.peek().column,"PARSER",this.file);let i=this.previous();this.consume("COLON","Expected ':' after object key");let o=this.parseExpression();r.push({key:s,value:o,line:i.line,column:i.column})}while(this.match("COMMA"));return this.consume("RBRACE","Expected '}' after object literal"),{kind:"ObjectExpr",properties:r,line:n.line,column:n.column}}throw e.type==="RECOVER"||e.type==="RESUMEPOINT"?this.reservedWord(e):new P(`Unexpected token '${e.value||e.type}' in expression`,e.line,e.column,"PARSER",this.file)}parseFormattedString(){let e=this.advance(),n=[];for(;!this.check("FSTRING_END")&&!this.isAtEnd();){if(this.match("FSTRING_TEXT")){n.push(this.previous().value);continue}let r=this.consume("FSTRING_FIELD_START","Expected formatted-string text or field"),s=this.parseExpression();this.validateFieldExpression(s);let i=this.consume("FSTRING_FIELD_END","Expected '}' after one formatted-string expression");n.push({expression:s,line:r.line,column:r.column,file:this.file,offset:r.offset,endOffset:i.endOffset})}return this.consume("FSTRING_END","Unterminated formatted string"),{kind:"InterpolatedStringExpr",parts:n,line:e.line,column:e.column,file:this.file}}parseBlockField(e,n){let r=e.tokens.slice(1,-1),s=e.tokens[e.tokens.length-1],i=new t([...r,{type:"EOF",value:"",line:s.line,column:s.column}],this.file),o=i.parseExpression();if(!i.isAtEnd()){let a=i.peek();throw new P(n.value+": unexpected '"+a.value+"' in a ${...} field; one expression per field",a.line,a.column,"PARSER",this.file)}return this.validateFieldExpression(o),{expression:o,line:e.line,column:e.column,file:this.file,offset:e.offset,endOffset:e.endOffset}}validateFieldExpression(e){let n=[];switch(e.kind){case"DataBlockExpr":throw new P("_"+e.marker+"_ inside a field is not supported yet; assign the block to a variable first",e.line,e.column,"PARSER",this.file);case"CommandExpr":case"AssignExpr":case"IndexAssignExpr":case"TupleAssignExpr":case"ParallelExpr":throw new P("Assignment and parallel expressions are not permitted in formatted-string fields",e.line,e.column,"PARSER",this.file);case"UnaryExpr":n=[e.operand];break;case"BinaryExpr":n=[e.left,e.right];break;case"ConditionalExpr":n=[e.condition,e.trueArm,e.falseArm];break;case"GroupingExpr":n=[e.expression];break;case"CallExpr":n=e.args;break;case"MethodCallExpr":n=[e.receiver,...e.args];break;case"ArrayExpr":case"TupleExpr":n=e.elements;break;case"ObjectExpr":n=e.properties.map(r=>r.value);break;case"IndexExpr":n=[e.target,e.index];break;case"InterpolatedStringExpr":n=e.parts.flatMap(r=>typeof r=="string"?[]:[r.expression]);break;case"LiteralExpr":case"PathLiteralExpr":case"IdentifierExpr":break}for(let r of n)this.validateFieldExpression(r)}match(...e){for(let n of e)if(this.check(n))return this.advance(),!0;return!1}check(e){return this.isAtEnd()?!1:this.peek().type===e}advance(){return this.isAtEnd()||this.current++,this.previous()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}previous(){return this.tokens[this.current-1]}consume(e,n){if(this.check(e)||e==="IDENTIFIER"&&n.startsWith("Expected property name")&&["SYSTEM","CMD"].includes(this.peek().type))return this.advance();let r=this.peek();throw r.type==="COMMAND_END"?new P("command: allowed only as a simple initializer, return value, or standalone system statement",r.line,r.column,"PARSER",this.file):e==="IDENTIFIER"&&["RECOVER","RESUMEPOINT","SYSTEM","CMD"].includes(r.type)&&!n.startsWith("Expected property name")?this.reservedWord(r):new P(n,r.line,r.column,"PARSER",this.file)}endStatement(e){let n=this.previous();this.check("COMMAND_END")&&(this.advance(),n=this.tokens[this.current-2]??n),!this.match("SEMICOLON")&&this.violation({kind:"semicolon",what:e,line:n.endLine??n.line,column:n.endColumn??n.column,insertAt:n.endOffset??0})}},dL=`# LMScript Standard Library (std.lms)
|
|
330
330
|
|
|
331
331
|
# String Utilities
|
|
332
332
|
fn is_blank(s) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmctl-ai/lmctl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.308",
|
|
4
4
|
"description": "A provider-agnostic control plane for teams of AI coding agents — across providers, with independent review and durable memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://lmctl.com",
|