aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS/CONVENTIONS/TYPES.md2
-rw-r--r--doc/language/cpsc-language-overview.7.md3
-rw-r--r--doc/language/function/cpsc-function-int.7.md32
-rw-r--r--include/cps/CodeGen.h1
-rw-r--r--lib/CodeGen/Builtins.h1
-rw-r--r--lib/CodeGen/ExpressionGen.cc25
-rw-r--r--lib/CodeGen/TypeCheckGen.cc6
7 files changed, 66 insertions, 4 deletions
diff --git a/AGENTS/CONVENTIONS/TYPES.md b/AGENTS/CONVENTIONS/TYPES.md
index 1c0d548..f5764d4 100644
--- a/AGENTS/CONVENTIONS/TYPES.md
+++ b/AGENTS/CONVENTIONS/TYPES.md
@@ -28,7 +28,7 @@ declared type names to distinguish `(Type)operand` from `(expression)`.
**Builtin scalars are strictly typed.** `CodeGen::emitBinaryOp` (ExpressionGen.cc)
rejects arithmetic on BOOLEAN/CHAR, AND/OR on non-BOOLEAN, and comparisons between
different kinds (INTEGER with REAL is the only mixed pair); `emitCoercedExpr` allows
-only INTEGER->REAL between the four builtin scalars. BOOLEAN (i1) and CHAR (i8)
+only INTEGER->REAL between the four builtin scalars. REAL->INTEGER therefore errors (with an `INT(...)` hint); `INT` is a compiler builtin (`Builtins.h`, `CodeGen::emitIntBuiltin`) truncating toward zero with a range check. BOOLEAN (i1) and CHAR (i8)
compare unsigned. C scalars keep C semantics.
**User-kind typing is nominal, and checked upstream only.** Enum/Record/Pointer
diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md
index 05001bd..b1376d8 100644
--- a/doc/language/cpsc-language-overview.7.md
+++ b/doc/language/cpsc-language-overview.7.md
@@ -10,7 +10,7 @@ Keywords are uppercase and case-sensitive. Identifiers, including built-in funct
Assign with ASCII `<-`: `Count <- Count + 1`. Compare with `=`, `<>`, `<`, `>`, `<=`, and `>=`. Standard-library STRING comparisons compare contents, case-sensitively. Use `+`, `-`, `*`, and `/` for arithmetic; built-in `/` always returns REAL. Use `&` to join strings.
-User-defined types can overload operators with OVERLOAD FUNCTION OPERATOR; expressions then use the declared result type. See [cpsc-keyword-operator(7)](keyword/cpsc-keyword-operator.7.md). STRING cannot be implicitly converted to BOOLEAN. Typing is strict: arithmetic and unary minus accept only INTEGER and REAL, a comparison needs operands of the same type (or INTEGER with REAL), and BOOLEAN, CHAR and INTEGER never convert into one another implicitly (only INTEGER converts to REAL). Use ASC and CHR to move between CHAR and INTEGER.
+User-defined types can overload operators with OVERLOAD FUNCTION OPERATOR; expressions then use the declared result type. See [cpsc-keyword-operator(7)](keyword/cpsc-keyword-operator.7.md). STRING cannot be implicitly converted to BOOLEAN. Typing is strict: arithmetic and unary minus accept only INTEGER and REAL, a comparison needs operands of the same type (or INTEGER with REAL), and BOOLEAN, CHAR and INTEGER never convert into one another implicitly (only INTEGER converts to REAL). Use ASC and CHR to move between CHAR and INTEGER. REAL never converts to INTEGER implicitly; use INT.
Precedence, from highest to lowest: unary operators, `* / DIV MOD`, `+ - &`, comparisons, AND, OR. Parentheses override precedence.
@@ -83,6 +83,7 @@ These ordinary functions and the STRING class come from the embedded standard li
# BUILT-IN FUNCTIONS
- [cpsc-function-eof(7)](function/cpsc-function-eof.7.md): test for the end of a text file.
+- [cpsc-function-int(7)](function/cpsc-function-int.7.md): take the integer part of a number.
- [cpsc-function-malloc(7)](function/cpsc-function-malloc.7.md): allocate raw memory.
- [cpsc-function-realloc(7)](function/cpsc-function-realloc.7.md): resize an explicit allocation.
- [cpsc-function-free(7)](function/cpsc-function-free.7.md): release an explicit allocation.
diff --git a/doc/language/function/cpsc-function-int.7.md b/doc/language/function/cpsc-function-int.7.md
new file mode 100644
index 0000000..245ae70
--- /dev/null
+++ b/doc/language/function/cpsc-function-int.7.md
@@ -0,0 +1,32 @@
+% CPSC-FUNCTION-INT(7)
+
+# NAME
+
+cpsc-function-int - take the integer part of a number
+
+# SYNOPSIS
+
+```text
+INT(value : REAL) RETURNS INTEGER
+INT(value : INTEGER) RETURNS INTEGER
+```
+
+# DESCRIPTION
+
+Built into the compiler, so it also works with `-no-std`.
+
+Return the integer part of a REAL, truncating toward zero: `INT(27.5415)` is 27 and `INT(-27.9)` is -27. An INTEGER argument is returned unchanged. A NaN or a value outside the INTEGER range stops the program with a runtime error.
+
+REAL never converts to INTEGER implicitly, so assignments, FOR bounds, array bounds and indices, arguments and RETURN values of type REAL are compile errors; wrap them in INT.
+
+# EXAMPLE
+
+```text
+DECLARE Whole : INTEGER
+Whole <- INT(27.5415)
+OUTPUT Whole
+```
+
+# SEE ALSO
+
+[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-div(7)](../keyword/cpsc-keyword-div.7.md).
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index b31cd3d..3b0296f 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -201,6 +201,7 @@ class CodeGen {
// Read a value from resolved storage.
llvm::Value *loadFromLValue(const LValueInfo &LV);
llvm::Value *emitAllocationSize(ExprAST *Expr);
+ llvm::Value *emitIntBuiltin(ExprAST *Arg, int Line);
llvm::Value *emitCall(const std::string &Callee,
const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line,
const std::vector<std::string> *Explicit = nullptr);
diff --git a/lib/CodeGen/Builtins.h b/lib/CodeGen/Builtins.h
index 2163b3e..84ba765 100644
--- a/lib/CodeGen/Builtins.h
+++ b/lib/CodeGen/Builtins.h
@@ -18,6 +18,7 @@ inline constexpr BuiltinInfo Builtins[] = {
{"MALLOC", 1, "C_TYPE(\"void*\")"},
{"REALLOC", 2, "C_TYPE(\"void*\")"},
{"FREE", 1, "C_TYPE(\"void*\")"},
+ {"INT", 1, "INTEGER"},
};
inline const BuiltinInfo *findBuiltin(const std::string &Name) {
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 0434348..51d7087 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -251,6 +251,7 @@ Value *CodeGen::emitCall(const std::string &Callee,
if (!Address) return nullptr;
return Memory->emitRelease(Address, Line);
}
+ if (Name == "INT") return emitIntBuiltin(ArgExprs[0].get(), Line);
if (Name == "EOF") {
Value *FileName = emitFileNameOperand(ArgExprs[0].get(), "EOF");
if (!FileName) return nullptr;
@@ -264,6 +265,30 @@ Value *CodeGen::emitCall(const std::string &Callee,
return FuncGen->emitCall(*Sig, Args, Variadic);
}
+// INT(x): the integer part of a REAL, truncated toward zero. NaN and values
+// outside the INTEGER range are a runtime error.
+Value *CodeGen::emitIntBuiltin(ExprAST *Arg, int Line) {
+ SaveAndRestore<int> Location(DiagnosticLine, Line ? Line : DiagnosticLine);
+ const TypeInfo *Info = getExprTypeInfo(Arg);
+ if (isWholeArrayVar(Arg) || !Info ||
+ (Info->Kind != TypeKind::Integer && Info->Kind != TypeKind::Real)) {
+ reportError("INT expects an INTEGER or REAL argument");
+ return nullptr;
+ }
+ Value *V = emitExpr(Arg);
+ if (!V || Info->Kind == TypeKind::Integer) return V;
+
+ Type *DoubleTy = Type::getDoubleTy(*TheContext);
+ Value *AtLeastMin = Builder->CreateFCmpOGE(V, ConstantFP::get(DoubleTy, -9223372036854775808.0), "int_ge_min");
+ Value *BelowMax = Builder->CreateFCmpOLT(V, ConstantFP::get(DoubleTy, 9223372036854775808.0), "int_lt_max");
+ Value *InRange = Builder->CreateAnd(AtLeastMin, BelowMax, "int_in_range");
+ Value *OutOfRange = Builder->CreateNot(InRange, "int_out_of_range");
+ Value *Msg = Builder->CreateGlobalString("[Fatal] line %d: INT argument out of INTEGER range\n",
+ "err_int_range", 0, TheModule.get());
+ RuntimeChecker->emitCheck(OutOfRange, Msg, {RuntimeChecker->lineValue(Line)});
+ return Builder->CreateFPToSI(V, Type::getInt64Ty(*TheContext), "int_value");
+}
+
Value *CodeGen::emitAllocationSize(ExprAST *Expr) {
SaveAndRestore<int> Location(DiagnosticLine, Expr->getLine() ? Expr->getLine() : DiagnosticLine);
const TypeInfo *Info = getExprTypeInfo(Expr);
diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc
index 8d54221..7cdf6ea 100644
--- a/lib/CodeGen/TypeCheckGen.cc
+++ b/lib/CodeGen/TypeCheckGen.cc
@@ -109,8 +109,10 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) {
bool Allowed = Src->Kind == Target->Kind ||
(Src->Kind == TypeKind::Integer && Target->Kind == TypeKind::Real);
if (!Allowed) {
- reportError("Cannot use a value of type %s where %s is expected",
- Src->Name.c_str(), Target->Name.c_str());
+ reportError("Cannot use a value of type %s where %s is expected%s",
+ Src->Name.c_str(), Target->Name.c_str(),
+ Src->Kind == TypeKind::Real && Target->Kind == TypeKind::Integer
+ ? " (use INT(...) to convert)" : "");
return nullptr;
}
}