Add environment variable access with $NAME syntax

Lexes $NAME as TK_ENVVAR, compiles reads as __getenv("NAME") calls
and writes ($NAME = expr) as __setenv("NAME", expr) calls. Runtime
functions wrap getenv/setenv/unsetenv with automatic tostring coercion.
This commit is contained in:
Cormac Shannon
2026-02-28 18:08:09 +00:00
parent a773398cab
commit 882a90be48
4 changed files with 94 additions and 17 deletions

39
linit.c
View File

@@ -13,12 +13,14 @@
#include <stddef.h>
#include <stdlib.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "llimits.h"
#include "lcmd.h"
/*
@@ -40,30 +42,39 @@ static const luaL_Reg stdlibs[] = {
};
/*
** Stub __command function for backtick command syntax.
** Returns a table {code=0, stdout=cmdstring, stderr=""}.
** The real implementation will be provided by issue #03.
*/
static int luaB_command (lua_State *L) {
const char *cmd = luaL_checkstring(L, 1);
lua_createtable(L, 0, 3);
lua_pushinteger(L, 0);
lua_setfield(L, -2, "code");
lua_pushstring(L, cmd);
lua_setfield(L, -2, "stdout");
lua_pushliteral(L, "");
lua_setfield(L, -2, "stderr");
static int luaB_getenv (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
const char *val = getenv(name);
if (val == NULL)
lua_pushnil(L);
else
lua_pushstring(L, val);
return 1;
}
static int luaB_setenv (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
if (lua_isnoneornil(L, 2))
unsetenv(name);
else {
const char *val = luaL_tolstring(L, 2, NULL);
setenv(name, val, 1);
}
return 0;
}
/*
** Register shell built-in globals (called after standard libraries).
*/
static void opencommand (lua_State *L) {
lua_pushcfunction(L, luaB_command);
lua_setglobal(L, "__command");
lua_pushcfunction(L, luaB_getenv);
lua_setglobal(L, "__getenv");
lua_pushcfunction(L, luaB_setenv);
lua_setglobal(L, "__setenv");
}

17
llex.c
View File

@@ -50,7 +50,7 @@ static const char *const luaX_tokens [] = {
"//", "..", "...", "==", ">=", "<=", "~=",
"<<", ">>", "::", "<eof>",
"<number>", "<integer>", "<name>", "<string>",
"<command>", "<command_interp>"
"<command>", "<command_interp>", "<envvar>"
};
@@ -108,6 +108,9 @@ static const char *txtToken (LexState *ls, int token) {
case TK_FLT: case TK_INT:
save(ls, '\0');
return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff));
case TK_ENVVAR:
save(ls, '\0');
return luaO_pushfstring(ls->L, "'$%s'", luaZ_buffer(ls->buff));
default:
return luaX_token2str(ls, token);
}
@@ -633,6 +636,18 @@ static int llex (LexState *ls, SemInfo *seminfo) {
case '`': { /* explicit command `ls -l` with ${} interpolation */
return read_command(ls, seminfo);
}
case '$': { /* environment variable $NAME */
next(ls);
if (lislalpha(ls->current)) { /* $NAME */
do {
save_and_next(ls);
} while (lislalnum(ls->current));
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
luaZ_bufflen(ls->buff));
return TK_ENVVAR;
}
else return '$'; /* bare $ as single-char token */
}
case '.': { /* '.', '..', '...', or number */
save_and_next(ls);
if (check_next1(ls, '.')) {

3
llex.h
View File

@@ -41,7 +41,8 @@ enum RESERVED {
TK_DBCOLON, TK_EOS,
TK_FLT, TK_INT, TK_NAME, TK_STRING,
TK_COMMAND,
TK_COMMAND_INTERP
TK_COMMAND_INTERP,
TK_ENVVAR
};
/* number of reserved words */

View File

@@ -1302,8 +1302,25 @@ static void commandexp (LexState *ls, expdesc *v) {
}
static void codeenvget (LexState *ls, expdesc *v, TString *name) {
FuncState *fs = ls->fs;
int base, line;
expdesc func, arg;
TString *fname = luaX_newstring(ls, "__getenv", 8);
line = ls->linenumber;
buildglobal(ls, fname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info;
codestring(&arg, name);
luaK_exp2nextreg(fs, &arg);
init_exp(v, VCALL, luaK_codeABC(fs, OP_CALL, base, 2, 2));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base + 1);
}
static void primaryexp (LexState *ls, expdesc *v) {
/* primaryexp -> NAME | '(' expr ')' | COMMAND */
/* primaryexp -> NAME | '(' expr ')' | COMMAND | ENVVAR */
switch (ls->t.token) {
case '(': {
int line = ls->linenumber;
@@ -1321,6 +1338,11 @@ static void primaryexp (LexState *ls, expdesc *v) {
commandexp(ls, v);
return;
}
case TK_ENVVAR: {
codeenvget(ls, v, ls->t.seminfo.ts);
luaX_next(ls);
return;
}
default: {
luaX_syntaxerror(ls, "unexpected symbol");
}
@@ -2160,6 +2182,30 @@ static void retstat (LexState *ls) {
}
static void envstat (LexState *ls) {
/* envstat -> '$' NAME '=' expr */
FuncState *fs = ls->fs;
TString *name = ls->t.seminfo.ts;
int base, line;
expdesc func, arg, val;
TString *fname;
line = ls->linenumber;
luaX_next(ls); /* skip TK_ENVVAR */
checknext(ls, '=');
fname = luaX_newstring(ls, "__setenv", 8);
buildglobal(ls, fname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info;
codestring(&arg, name);
luaK_exp2nextreg(fs, &arg);
expr(ls, &val);
luaK_exp2nextreg(fs, &val);
init_exp(&val, VCALL, luaK_codeABC(fs, OP_CALL, base, 3, 1));
luaK_fixline(fs, line);
fs->freereg = cast_byte(base);
}
static void statement (LexState *ls) {
int line = ls->linenumber; /* may be needed for error messages */
enterlevel(ls);
@@ -2225,6 +2271,10 @@ static void statement (LexState *ls) {
gotostat(ls, line);
break;
}
case TK_ENVVAR: { /* stat -> '$' NAME '=' expr */
envstat(ls);
break;
}
#if defined(LUA_COMPAT_GLOBAL)
case TK_NAME: {
/* compatibility code to parse global keyword when "global"