nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
function_file.hpp
1// runtime/include/nk/runtime/function_file.hpp
2//
3// MATLAB `run`/file-execution semantics for FUNCTION files
4// (bugs/opened/lang/run-invokes-nullary-function-file.md): a file whose
5// first code construct is a function definition EXECUTES that function
6// when run — a nullary function runs to completion; a function with
7// required inputs is still CALLED and fails with the natural
8// "not enough input arguments" (exactly what MATLAB R2025b does —
9// verified on the fieldtest corpus: extract_firms_data.m).
10//
11// Header-only: shared by the `run` builtin (runtime) and the native CLI
12// (apps/nk); the WASM CLI mirrors the same scan in JS.
13
14#pragma once
15
16#include <cctype>
17#include <string>
18
19namespace nk::runtime {
20
21// Returns true and fills `name` with the PRIMARY function's identifier if
22// `src` is a function file (first non-comment, non-blank construct is a
23// function definition). Returns false for scripts.
24inline bool primaryFunctionOfFile(const std::string &src, std::string &name)
25{
26 std::string line;
27 bool haveFirst = false;
28 size_t i = 0;
29 const size_t n = src.size();
30 auto trim = [](std::string &s) {
31 size_t a = 0, b = s.size();
32 while (a < b && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
33 while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1]))) --b;
34 s = s.substr(a, b - a);
35 };
36 while (i < n && !haveFirst) {
37 // read one physical line
38 line.clear();
39 while (i < n && src[i] != '\n') line += src[i++];
40 if (i < n) ++i;
41 while (!line.empty() && (line.back() == '\r')) line.pop_back();
42 // strip a trailing comment (naive: first '%' — function signature
43 // lines do not carry literal % in strings in practice)
44 size_t pct = line.find('%');
45 if (pct != std::string::npos) line.resize(pct);
46 trim(line);
47 if (line.empty()) continue;
48 if (line.rfind("function", 0) == 0
49 && (line.size() == 8 || std::isspace(static_cast<unsigned char>(line[8])))) {
50 haveFirst = true;
51 // join continuations ("...") of the signature line
52 while (line.size() >= 3 && line.compare(line.size() - 3, 3, "...") == 0 && i < n) {
53 line.resize(line.size() - 3);
54 std::string more;
55 while (i < n && src[i] != '\n') more += src[i++];
56 if (i < n) ++i;
57 size_t p = more.find('%');
58 if (p != std::string::npos) more.resize(p);
59 trim(more);
60 line += more;
61 }
62 } else {
63 return false; // first code construct is not a function → script
64 }
65 }
66 if (!haveFirst)
67 return false;
68
69 // Parse the signature: function [out] = name(args) | function name(args)
70 size_t eq = line.find('=');
71 size_t namePos = std::string::npos;
72 if (eq != std::string::npos) {
73 // ensure the '=' is before any '(' (output-list form)
74 size_t lp = line.find('(');
75 if (lp != std::string::npos && lp < eq) eq = std::string::npos;
76 }
77 if (eq != std::string::npos) {
78 namePos = eq + 1;
79 } else {
80 namePos = 8; // past "function"
81 }
82 while (namePos < line.size() && std::isspace(static_cast<unsigned char>(line[namePos])))
83 ++namePos;
84 if (namePos >= line.size()) return false;
85 if (!std::isalpha(static_cast<unsigned char>(line[namePos])) && line[namePos] != '_')
86 return false;
87 size_t end = namePos;
88 while (end < line.size()
89 && (std::isalnum(static_cast<unsigned char>(line[end])) || line[end] == '_'))
90 ++end;
91 name = line.substr(namePos, end - namePos);
92 return !name.empty();
93}
94
95} // namespace nk::runtime
Value eq(const Value &a, const Value &b, std::pmr::memory_resource *mr=nullptr)
Elementwise equality (y = a == b).