diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-17 05:27:14 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-17 05:27:14 +0000 |
| commit | e782898e420c7f2abd83b97c160194d0e70598dd (patch) | |
| tree | f1d95ee7b2766ac2f443df0cb6d5ba419085649e | |
| parent | 42c1649264153940de143741337c38042c32e60b (diff) | |
feat(calls): support standalone calls and void pointer casts
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 4 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-c_type.7.md | 2 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-call.7.md | 2 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-function.7.md | 2 | ||||
| -rw-r--r-- | include/cps/CodeGen.h | 2 | ||||
| -rw-r--r-- | include/cps/FunctionGen.h | 4 | ||||
| -rw-r--r-- | lib/CodeGen/CTypeGen.cc | 3 | ||||
| -rw-r--r-- | lib/CodeGen/ExpressionGen.cc | 213 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 11 | ||||
| -rw-r--r-- | lib/CodeGen/NativeTypeSystem.cc | 1 | ||||
| -rw-r--r-- | lib/CodeGen/StatementGen.cc | 4 | ||||
| -rw-r--r-- | lib/Parser/Parser.cc | 7 | ||||
| -rw-r--r-- | tests/i/function-call-statements/case.json | 1 | ||||
| -rw-r--r-- | tests/i/function-call-statements/expected.txt | 6 | ||||
| -rw-r--r-- | tests/i/function-call-statements/notes.md | 5 | ||||
| -rw-r--r-- | tests/i/function-call-statements/program.cps | 36 |
16 files changed, 182 insertions, 121 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index d1454e6..bb1b836 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -131,7 +131,9 @@ The parser drops a failed statement and resyncs at the next token that can start a statement or end a block (`syncToStatementStart`), then continues; the failure is always diagnosed and reflected in the exit code. -**Calls dispatch on the declared signature.** `FunctionGen::emitPrototype` records every +**Calls share one dispatch path.** `CodeGen::emitCall` handles expression calls, CALL statements and bare `name(args)` statements; statement calls discard results. Explicit casts may bridge C void* and nominal pseudocode pointer types. + +**User calls dispatch on the declared signature.** `FunctionGen::emitPrototype` records every function's pseudocode signature (`FuncSig`: return type name + per-param `ParamSig` {type name, mode, array rank/bounds}); `CodeGen::marshalCallArgs` — the single call-marshalling path for both call-expr and call-stmt — requires BYREF arguments to be diff --git a/doc/language/keyword/cpsc-keyword-c_type.7.md b/doc/language/keyword/cpsc-keyword-c_type.7.md index 5b8ebd3..3951e26 100644 --- a/doc/language/keyword/cpsc-keyword-c_type.7.md +++ b/doc/language/keyword/cpsc-keyword-c_type.7.md @@ -19,7 +19,7 @@ Supported base types are _Bool; char, signed char and unsigned char; signed and C widths, char signedness and floating-point formats come from the compiler's build platform. Generated programs must use the same platform ABI; cross-target compilation and EXTERN declarations are not implemented. INTEGER remains signed 64-bit, CHAR remains an unsigned byte, and REAL remains double precision. In particular, C_CHAR need not have CHAR's signedness. -A cast has unary precedence and converts its following operand. Declare a type alias before using its name in a cast. Numeric, character and boolean values can be explicitly converted between pseudocode and C types. STRING can be cast to char* and back, and C pointer types can be cast to other C pointer types. Pointer/integer casts and casts involving the original pseudocode enums, records or pointer types are unsupported. Casts are values, not assignable locations. +A cast has unary precedence and converts its following operand. Declare a type alias before using its name in a cast. Numeric, character and boolean values can be explicitly converted between pseudocode and C types. STRING can be cast to char* and back, and C pointer types can be cast to other C pointer types. C void* and original pseudocode pointer types can be explicitly cast to each other. Pointer/integer casts and casts involving pseudocode enums or whole records are unsupported. Casts are values, not assignable locations. C/pseudocode assignment, argument passing and mixed arithmetic require an explicit cast, including ordinary literals: use n <- (C_INT)1 and n + (C_INT)1. Different C scalar types use C++ numeric conversions, integer promotions and usual arithmetic conversions. BYREF requires the same type and performs no conversion. Comparisons and logical operations return BOOLEAN. The / operator always performs real division and returns REAL, including with two C integer operands; DIV and MOD accept C integers after promotion. C values used directly as IF/WHILE conditions need a cast to BOOLEAN. diff --git a/doc/language/keyword/cpsc-keyword-call.7.md b/doc/language/keyword/cpsc-keyword-call.7.md index 0972e5e..2d7c553 100644 --- a/doc/language/keyword/cpsc-keyword-call.7.md +++ b/doc/language/keyword/cpsc-keyword-call.7.md @@ -12,7 +12,7 @@ CALL name(arguments) # DESCRIPTION -Invoke a previously declared procedure with its parameters. Empty argument lists may be written `CALL name()` or `CALL name`. Use function calls in expressions to obtain their results. +Invoke a previously declared procedure with its parameters. Empty argument lists may be written `CALL name()` or `CALL name`. Use function calls in expressions to obtain their results. As compiler extensions, CALL also accepts functions and discards their results, and `name(arguments)` may stand alone without CALL for either a function or a procedure. Built-ins use the same argument checks in expressions and statements. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-function.7.md b/doc/language/keyword/cpsc-keyword-function.7.md index a177afa..52efa90 100644 --- a/doc/language/keyword/cpsc-keyword-function.7.md +++ b/doc/language/keyword/cpsc-keyword-function.7.md @@ -17,6 +17,8 @@ ENDFUNCTION Call a function in an expression as `name(arguments)`. A call may precede its top-level definition. RETURNS specifies the result type; omitting it defaults to INTEGER. RETURN supplies a value of that type. Whole-array results are unsupported. +As a compiler extension, `name(arguments)` may also be a complete statement that discards the result. This differs from guide section 8.2, which requires function calls to occur within expressions. Built-in functions support the same form. + Parameters use `name : type`, optionally prefixed by BYVAL. BYVAL is the default; BYREF is forbidden for functions (guide section 8.3). Array parameters accept `ARRAY OF type` (one dimension) or `ARRAY[lower:upper, ...] OF type` (integer literal bounds). BYVAL arrays are copied. RETURNS, RETURN, BYVAL, and ENDFUNCTION belong here. # EXAMPLE diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 883cf71..8f84004 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -106,6 +106,8 @@ class CodeGen { bool emitLValue(ExprAST *E, LValueInfo &Out); // The rvalue read: load + the STRING null->"" guard. llvm::Value *loadFromLValue(const LValueInfo &LV); + llvm::Value *emitCall(const std::string &Callee, + const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line); bool marshalCallArgs(const std::string &Callee, const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line, diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index 4bf4519..fd874d2 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -87,9 +87,7 @@ public: llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function<void(StmtAST*)> &StmtEmitter, unsigned ScopeID); - llvm::Value *emitCallExpr(CallExprAST *Call, const std::vector<llvm::Value*> &Args); - - void emitCallStmt(CallStmtAST *Call, const std::vector<llvm::Value*> &Args); + llvm::Value *emitCall(const std::string &Callee, const std::vector<llvm::Value*> &Args); void emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal); }; diff --git a/lib/CodeGen/CTypeGen.cc b/lib/CodeGen/CTypeGen.cc index 7623103..b30814e 100644 --- a/lib/CodeGen/CTypeGen.cc +++ b/lib/CodeGen/CTypeGen.cc @@ -62,6 +62,9 @@ Value *CodeGen::emitCast(CastExprAST *Cast) { return nullptr; } const TypeInfo *Source = getExprTypeInfo(Operand); + if (Source && ((Source->Kind == TypeKind::Pointer && Target->CIdentity == "void*") || + (Source->CIdentity == "void*" && Target->Kind == TypeKind::Pointer))) + return emitExpr(Operand); if (!Source || Source->isVoid() || Source->isUserKind() || Target->isUserKind()) { reportError("Unsupported operand or target for cast to %s", Target->Name.c_str()); return nullptr; diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index b3655fd..fa2581f 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -124,110 +124,8 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine()); } - if (auto *Call = dynamic_cast<CallExprAST*>(Expr)) { - std::string Name = Call->getCallee(); - if (const BuiltinInfo *B = findBuiltin(Name)) { - Name = B->Name; - if (Call->getArgs().size() != B->Arity) { - reportError("%s expects %u arg%s", B->Name, B->Arity, B->Arity == 1 ? "" : "s"); - return nullptr; - } - } - if (Name == "LENGTH") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!Str) return nullptr; - return StrHandler->emitLength(Str); - } - if (Name == "MID") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - Value *Start = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); - Value *Len = emitCoercedExpr(Call->getArgs()[2].get(), resolveType("INTEGER")); - if (!Str || !Start || !Len) return nullptr; - return StrHandler->emitMid(Str, Start, Len); - } - if (Name == "RIGHT") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - Value *Len = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); - if (!Str || !Len) return nullptr; - return StrHandler->emitRight(Str, Len); - } - if (Name == "LEFT") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - Value *Len = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); - if (!Str || !Len) return nullptr; - return StrHandler->emitLeft(Str, Len); - } - if (Name == "LCASE") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!Str) return nullptr; - return StrHandler->emitLCase(Str); - } - if (Name == "UCASE") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!Str) return nullptr; - return StrHandler->emitUCase(Str); - } - if (Name == "ASC") { - const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); - if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { - reportError("ASC expects a CHAR or STRING (got %s)", ArgType->Name.c_str()); - return nullptr; - } - Value *ArgVal = emitExpr(Call->getArgs()[0].get()); - if (!ArgVal) return nullptr; - Value *CharVal = nullptr; - if (ArgType && ArgType->isChar()) { - CharVal = coerceValueToType(ArgVal, resolveType("CHAR")); - } else { - CharVal = Builder->CreateLoad(Type::getInt8Ty(*TheContext), ArgVal, "char_load"); - } - Value *AscVal = Builder->CreateZExt(CharVal, Type::getInt32Ty(*TheContext), "asc_val"); - return coerceValueToType(AscVal, resolveType("INTEGER")); - } - if (Name == "CHR") { - Value *IntVal = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("INTEGER")); - if (!IntVal) return nullptr; - Value *CharVal = Builder->CreateTrunc(IntVal, Type::getInt8Ty(*TheContext), "chr_val"); - return coerceValueToType(CharVal, resolveType("STRING")); - } - if (Name == "IS_NUM") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!Str) return nullptr; - return StrConvHandler->emitIsNum(Str); - } - if (Name == "NUM_TO_STR") { - const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); - if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { - reportError("NUM_TO_STR expects a number (got %s)", ArgType->Name.c_str()); - return nullptr; - } - Value *NumV = emitExpr(Call->getArgs()[0].get()); - if (!NumV) return nullptr; - bool IsReal = NumV->getType()->isDoubleTy(); - if (NumV->getType()->isIntegerTy(8)) { - NumV = coerceValueToType(NumV, resolveType("INTEGER")); - } - return StrConvHandler->emitNumToStr(NumV, IsReal); - } - if (Name == "STR_TO_NUM") { - Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!Str) return nullptr; - return StrConvHandler->emitStrToNum(Str, true); - } - if (Name == "EOF") { - if (isWholeArrayVar(Call->getArgs()[0].get())) { - reportError("Cannot use a whole array as the EOF filename"); - return nullptr; - } - Value *FileName = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); - if (!FileName) return nullptr; - return Files->emitEof(FileName, Call->getLine()); - } - - std::vector<Value*> Args; - if (!marshalCallArgs(Name, Call->getArgs(), Call->getLine(), Args)) return nullptr; - return FuncGen->emitCallExpr(Call, Args); - } + if (auto *Call = dynamic_cast<CallExprAST*>(Expr)) + return emitCall(Call->getCallee(), Call->getArgs(), Call->getLine()); if (auto *D = dynamic_cast<DesignatorExprAST*>(Expr)) { LValueInfo LV; @@ -274,3 +172,110 @@ Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT, } return ArithHandler->emitBinaryOp(Op, L, R, Line); } + +Value *CodeGen::emitCall(const std::string &Callee, + const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line) { + SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine); + std::string Name = Callee; + if (const BuiltinInfo *B = findBuiltin(Name)) { + Name = B->Name; + if (ArgExprs.size() != B->Arity) { + reportError("%s expects %u arg%s", B->Name, B->Arity, B->Arity == 1 ? "" : "s"); + return nullptr; + } + } + if (Name == "LENGTH") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!Str) return nullptr; + return StrHandler->emitLength(Str); + } + if (Name == "MID") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + Value *Start = emitCoercedExpr(ArgExprs[1].get(), resolveType("INTEGER")); + Value *Len = emitCoercedExpr(ArgExprs[2].get(), resolveType("INTEGER")); + if (!Str || !Start || !Len) return nullptr; + return StrHandler->emitMid(Str, Start, Len); + } + if (Name == "RIGHT") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + Value *Len = emitCoercedExpr(ArgExprs[1].get(), resolveType("INTEGER")); + if (!Str || !Len) return nullptr; + return StrHandler->emitRight(Str, Len); + } + if (Name == "LEFT") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + Value *Len = emitCoercedExpr(ArgExprs[1].get(), resolveType("INTEGER")); + if (!Str || !Len) return nullptr; + return StrHandler->emitLeft(Str, Len); + } + if (Name == "LCASE") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!Str) return nullptr; + return StrHandler->emitLCase(Str); + } + if (Name == "UCASE") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!Str) return nullptr; + return StrHandler->emitUCase(Str); + } + if (Name == "ASC") { + const TypeInfo *ArgType = getExprTypeInfo(ArgExprs[0].get()); + if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { + reportError("ASC expects a CHAR or STRING (got %s)", ArgType->Name.c_str()); + return nullptr; + } + Value *ArgVal = emitExpr(ArgExprs[0].get()); + if (!ArgVal) return nullptr; + Value *CharVal = nullptr; + if (ArgType && ArgType->isChar()) { + CharVal = coerceValueToType(ArgVal, resolveType("CHAR")); + } else { + CharVal = Builder->CreateLoad(Type::getInt8Ty(*TheContext), ArgVal, "char_load"); + } + Value *AscVal = Builder->CreateZExt(CharVal, Type::getInt32Ty(*TheContext), "asc_val"); + return coerceValueToType(AscVal, resolveType("INTEGER")); + } + if (Name == "CHR") { + Value *IntVal = emitCoercedExpr(ArgExprs[0].get(), resolveType("INTEGER")); + if (!IntVal) return nullptr; + Value *CharVal = Builder->CreateTrunc(IntVal, Type::getInt8Ty(*TheContext), "chr_val"); + return coerceValueToType(CharVal, resolveType("STRING")); + } + if (Name == "IS_NUM") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!Str) return nullptr; + return StrConvHandler->emitIsNum(Str); + } + if (Name == "NUM_TO_STR") { + const TypeInfo *ArgType = getExprTypeInfo(ArgExprs[0].get()); + if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { + reportError("NUM_TO_STR expects a number (got %s)", ArgType->Name.c_str()); + return nullptr; + } + Value *NumV = emitExpr(ArgExprs[0].get()); + if (!NumV) return nullptr; + bool IsReal = NumV->getType()->isDoubleTy(); + if (NumV->getType()->isIntegerTy(8)) { + NumV = coerceValueToType(NumV, resolveType("INTEGER")); + } + return StrConvHandler->emitNumToStr(NumV, IsReal); + } + if (Name == "STR_TO_NUM") { + Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!Str) return nullptr; + return StrConvHandler->emitStrToNum(Str, true); + } + if (Name == "EOF") { + if (isWholeArrayVar(ArgExprs[0].get())) { + reportError("Cannot use a whole array as the EOF filename"); + return nullptr; + } + Value *FileName = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); + if (!FileName) return nullptr; + return Files->emitEof(FileName, Line); + } + + std::vector<Value*> Args; + if (!marshalCallArgs(Name, ArgExprs, Line, Args)) return nullptr; + return FuncGen->emitCall(Name, Args); +} diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index a155f60..10bd3fa 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -243,14 +243,9 @@ static Value *GenerateCall(llvm::Module &Module, return Exceptions.emitCall(CalleeF, Args, "calltmp"); } -llvm::Value *FunctionGen::emitCallExpr(CallExprAST *Call, const std::vector<llvm::Value*> &Args) { - const FuncSig *Sig = getSignature(Call->getCallee()); - return GenerateCall(Module, Exceptions, Sig ? Sig->Name : Call->getCallee(), Args, HadError); -} - -void FunctionGen::emitCallStmt(CallStmtAST *Call, const std::vector<llvm::Value*> &Args) { - const FuncSig *Sig = getSignature(Call->getCallee()); - GenerateCall(Module, Exceptions, Sig ? Sig->Name : Call->getCallee(), Args, HadError); +llvm::Value *FunctionGen::emitCall(const std::string &Callee, const std::vector<llvm::Value*> &Args) { + const FuncSig *Sig = getSignature(Callee); + return GenerateCall(Module, Exceptions, Sig ? Sig->Name : Callee, Args, HadError); } void FunctionGen::emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal) { diff --git a/lib/CodeGen/NativeTypeSystem.cc b/lib/CodeGen/NativeTypeSystem.cc index a4fcee0..2dbbc49 100644 --- a/lib/CodeGen/NativeTypeSystem.cc +++ b/lib/CodeGen/NativeTypeSystem.cc @@ -106,6 +106,7 @@ void TypeSystem::registerBuiltins() { defineCAlias("C_CHAR", "char", Err); defineCAlias("C_STRING", "char*", Err); defineCAlias("C_DOUBLE", "double", Err); + importCType("void*", Err); } diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 2c80437..b648ec6 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -55,9 +55,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } if (auto *Call = dynamic_cast<CallStmtAST*>(Stmt)) { - std::vector<Value*> Args; - if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Call->getLine(), Args)) return; - FuncGen->emitCallStmt(Call, Args); + emitCall(Call->getCallee(), Call->getArgs(), Call->getLine()); return; } diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index d4d04d2..36b0a6a 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -221,6 +221,13 @@ std::unique_ptr<StmtAST> Parser::ParseAssignStmt() { int Line = Lex.getLine(); getNextToken(); + if (CurTok == '(') { + getNextToken(); + auto Args = ParseExprList(')', true); + if (!Args) return nullptr; + return std::make_unique<CallStmtAST>(Name, std::move(*Args), Line); + } + std::vector<DesignatorAccess> Accesses; if (!ParseDesignatorSuffix(Accesses)) return nullptr; diff --git a/tests/i/function-call-statements/case.json b/tests/i/function-call-statements/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/function-call-statements/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/function-call-statements/expected.txt b/tests/i/function-call-statements/expected.txt new file mode 100644 index 0000000..d2576ac --- /dev/null +++ b/tests/i/function-call-statements/expected.txt @@ -0,0 +1,6 @@ +17 +23 +7 +19 +[Fatal] line 33: Division by zero +caught diff --git a/tests/i/function-call-statements/notes.md b/tests/i/function-call-statements/notes.md new file mode 100644 index 0000000..c7e0c0a --- /dev/null +++ b/tests/i/function-call-statements/notes.md @@ -0,0 +1,5 @@ +# Trace notes + +Human review: pending. + +This case exercises compiler extensions. Pointer casts preserve the addresses of INTEGER 17 and C int 23. Echo prints 7 even when its result is discarded. Discarded LENGTH calls print nothing. Two BYREF procedure calls increment 17 to 19. The argument division on line 33 throws before Echo runs; CATCH prints caught. diff --git a/tests/i/function-call-statements/program.cps b/tests/i/function-call-statements/program.cps new file mode 100644 index 0000000..403414b --- /dev/null +++ b/tests/i/function-call-statements/program.cps @@ -0,0 +1,36 @@ +TYPE Raw = C_TYPE("void*") +TYPE NativePointer = C_TYPE("int*") +TYPE IntPointer = ^INTEGER +DECLARE Number : INTEGER +DECLARE Pointer : IntPointer +DECLARE Address : Raw +DECLARE Native : C_INT +DECLARE NativeAddress : NativePointer +Number <- 17 +Pointer <- ^Number +Address <- (Raw)Pointer +Pointer <- (IntPointer)Address +OUTPUT Pointer^ +Native <- (C_INT)23 +NativeAddress <- ^Native +Address <- (Raw)NativeAddress +NativeAddress <- (NativePointer)Address +OUTPUT NativeAddress^ +FUNCTION Echo(Value : INTEGER) RETURNS INTEGER + OUTPUT Value + RETURN Value +ENDFUNCTION +Echo(7) +LENGTH("discard") +CALL LENGTH("also discard") +PROCEDURE Change(BYREF Value : INTEGER) + Value <- Value + 1 +ENDPROCEDURE +Change(Number) +CALL Change(Number) +OUTPUT Number +TRY + Echo(1 DIV 0) +CATCH + OUTPUT "caught" +ENDTRY |
