Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/java/io/jawk/Awk.java
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ protected final <T extends AwkProgram> T compileProgram(
lastAst = null;
if (!scripts.isEmpty()) {
// Parse all script sources into a single AST
AwkParser parser = new AwkParser(this.extensionFunctions);
AwkParser parser = new AwkParser(this.extensionFunctions, settings.isAllowArraysOfArrays());
AstNode ast = parser.parse(scripts);
lastAst = ast;
if (ast != null) {
Expand Down Expand Up @@ -539,7 +539,7 @@ protected final <T extends AwkExpression> T compileExpression(
new StringReader(expression));

// Parse the expression
AwkParser parser = new AwkParser(this.extensionFunctions);
AwkParser parser = new AwkParser(this.extensionFunctions, settings.isAllowArraysOfArrays());
AstNode ast = parser.parseExpression(expressionSource);

// Attempt to traverse the syntax tree and build
Expand Down
242 changes: 231 additions & 11 deletions src/main/java/io/jawk/backend/AVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import io.jawk.intermediate.Opcode;
import io.jawk.intermediate.PositionTracker;
import io.jawk.intermediate.UninitializedObject;
import io.jawk.jrt.AssocArray;
import io.jawk.jrt.AwkRuntimeException;
import io.jawk.jrt.AwkSink;
import io.jawk.jrt.BlockManager;
Expand Down Expand Up @@ -649,7 +650,12 @@ private void executeTuples(PositionTracker position)
// display $0
push(jrt.jrtGetInputField(0).toString().length());
} else {
push(pop().toString().length());
Object value = pop();
if (value instanceof Map) {
push((long) ((Map<?, ?>) value).size());
} else {
push(jrt.toAwkString(value).length());
}
}
position.next();
break;
Expand Down Expand Up @@ -804,6 +810,20 @@ private void executeTuples(PositionTracker position)
position.next();
break;
}
case ASSIGN_MAP_ELEMENT: {
// stack[0] = array index
// stack[1] = associative array
// stack[2] = value
Object arrIdx = pop();
Map<Object, Object> array = toMap(pop());
Object rhs = pop();
if (rhs == null) {
rhs = BLANK;
}
assignMapElement(array, arrIdx, rhs);
position.next();
break;
}
case PLUS_EQ_ARRAY:
case MINUS_EQ_ARRAY:
case MULT_EQ_ARRAY:
Expand All @@ -825,6 +845,7 @@ private void executeTuples(PositionTracker position)
double val = JRT.toDouble(rhs);

Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
checkScalar(arrIdx);
Object o = array.get(arrIdx);
double origVal = JRT.toDouble(o);

Expand Down Expand Up @@ -861,6 +882,59 @@ private void executeTuples(PositionTracker position)
position.next();
break;
}
case PLUS_EQ_MAP_ELEMENT:
case MINUS_EQ_MAP_ELEMENT:
case MULT_EQ_MAP_ELEMENT:
case DIV_EQ_MAP_ELEMENT:
case MOD_EQ_MAP_ELEMENT:
case POW_EQ_MAP_ELEMENT: {
// stack[0] = array index
// stack[1] = associative array
// stack[2] = value
Object arrIdx = pop();
Map<Object, Object> array = toMap(pop());
Object rhs = pop();
if (rhs == null) {
rhs = BLANK;
}

double val = JRT.toDouble(rhs);
checkScalar(arrIdx);
Object o = array.get(arrIdx);
double origVal = JRT.toDouble(o);
double newVal;

switch (opcode) {
case PLUS_EQ_MAP_ELEMENT:
newVal = origVal + val;
break;
case MINUS_EQ_MAP_ELEMENT:
newVal = origVal - val;
break;
case MULT_EQ_MAP_ELEMENT:
newVal = origVal * val;
break;
case DIV_EQ_MAP_ELEMENT:
newVal = origVal / val;
break;
case MOD_EQ_MAP_ELEMENT:
newVal = origVal % val;
break;
case POW_EQ_MAP_ELEMENT:
newVal = Math.pow(origVal, val);
break;
default:
throw new Error("Invalid op code here: " + opcode);
}

if (JRT.isActuallyLong(newVal)) {
assignMapElement(array, arrIdx, (long) Math.rint(newVal));
} else {
assignMapElement(array, arrIdx, newVal);
}
position.next();
break;
}

case ASSIGN_AS_INPUT: {
// stack[0] = value
Expand Down Expand Up @@ -1020,6 +1094,7 @@ private void executeTuples(PositionTracker position)
boolean isGlobal = position.boolArg(1);
Map<Object, Object> aa = ensureMapVariable(position.intArg(0), isGlobal);
Object key = pop();
checkScalar(key);
Object o = aa.get(key);
double ans = JRT.toDouble(o) + 1;
if (JRT.isActuallyLong(ans)) {
Expand All @@ -1037,6 +1112,39 @@ private void executeTuples(PositionTracker position)
boolean isGlobal = position.boolArg(1);
Map<Object, Object> aa = ensureMapVariable(position.intArg(0), isGlobal);
Object key = pop();
checkScalar(key);
Object o = aa.get(key);
double ans = JRT.toDouble(o) - 1;
if (JRT.isActuallyLong(ans)) {
aa.put(key, (long) Math.rint(ans));
} else {
aa.put(key, ans);
}
position.next();
break;
}
case INC_MAP_REF: {
// stack[0] = array index
// stack[1] = associative array
Object key = pop();
checkScalar(key);
Map<Object, Object> aa = toMap(pop());
Object o = aa.get(key);
double ans = JRT.toDouble(o) + 1;
if (JRT.isActuallyLong(ans)) {
aa.put(key, (long) Math.rint(ans));
} else {
aa.put(key, ans);
}
position.next();
break;
}
case DEC_MAP_REF: {
// stack[0] = array index
// stack[1] = associative array
Object key = pop();
checkScalar(key);
Map<Object, Object> aa = toMap(pop());
Object o = aa.get(key);
double ans = JRT.toDouble(o) - 1;
if (JRT.isActuallyLong(ans)) {
Expand Down Expand Up @@ -1105,17 +1213,36 @@ private void executeTuples(PositionTracker position)
case DEREF_ARRAY: {
// stack[0] = array index
Object idx = pop(); // idx
Object array = pop(); // map
if (!(array instanceof Map)) {
throw new AwkRuntimeException("Attempting to index a non-associative-array.");
}
@SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>) array;
checkScalar(idx);
Map<Object, Object> map = toMap(pop());
Object o = JRT.getAwkValue(map, idx);
push(o);
position.next();
break;
}
case ENSURE_ARRAY_ELEMENT: {
// stack[0] = array index
// stack[1] = associative array
Object idx = pop();
Map<Object, Object> map = toMap(pop());
push(ensureArrayInArray(map, idx));
position.next();
break;
}
case PEEK_ARRAY_ELEMENT: {
// stack[0] = array index
Object idx = pop();
checkScalar(idx);
Map<Object, Object> map = toMap(pop());
if (map instanceof AssocArray && !JRT.containsAwkKey(map, idx)) {
push(BLANK);
} else {
Object value = map.get(idx);
push(value != null ? value : BLANK);
}
position.next();
break;
}
case SRAND: {
// arg[0] = numArgs (where 0 = no args, anything else = one argument)
// stack[0] = seed (only if numArgs != 0)
Expand Down Expand Up @@ -1322,6 +1449,21 @@ private void executeTuples(PositionTracker position)
position.next();
break;
}
case SUB_FOR_MAP_REFERENCE: {
// arg[0] = isGsub
// stack[0] = array index
// stack[1] = associative array
// stack[2] = original variable value
// stack[3] = replacement string
// stack[4] = ere
Object arrIdx = pop();
Map<Object, Object> array = toMap(pop());
String newString = execSubOrGSub(position, 0);
assignMapElement(array, arrIdx, newString);
pop();
position.next();
break;
}
case SPLIT: {
// arg[0] = num args
// stack[0] = field_sep (only if num args == 3)
Expand Down Expand Up @@ -1582,6 +1724,11 @@ private void executeTuples(PositionTracker position)
}
case KEYLIST: {
Object o = pop();
if (o == null || o instanceof UninitializedObject) {
push(new ArrayDeque<>());
position.next();
break;
}
if (!(o instanceof Map)) {
throw new AwkRuntimeException(
position.lineNumber(),
Expand Down Expand Up @@ -1849,14 +1996,20 @@ private void executeTuples(PositionTracker position)
long count = position.intArg(0);
// String s;
if (count == 1) {
push(jrt.toAwkString(pop()));
Object value = pop();
checkScalar(value);
push(jrt.toAwkString(value));
} else {
StringBuilder sb = new StringBuilder();
sb.append(jrt.toAwkString(pop()));
Object value = pop();
checkScalar(value);
sb.append(jrt.toAwkString(value));
String subsep = jrt.toAwkString(jrt.getSUBSEPVar());
for (int i = 1; i < count; i++) {
sb.insert(0, subsep);
sb.insert(0, jrt.toAwkString(pop()));
value = pop();
checkScalar(value);
sb.insert(0, jrt.toAwkString(value));
}
push(sb.toString());
}
Expand All @@ -1871,12 +2024,23 @@ private void executeTuples(PositionTracker position)
boolean isGlobal = position.boolArg(1);
Map<Object, Object> aa = getMapVariable(offset, isGlobal);
Object key = pop();
checkScalar(key);
if (aa != null) {
aa.remove(key);
}
position.next();
break;
}
case DELETE_MAP_ELEMENT: {
// stack[0] = array index
// stack[1] = associative array
Object key = pop();
checkScalar(key);
Map<Object, Object> aa = toMap(pop());
aa.remove(key);
position.next();
break;
}
case DELETE_ARRAY: {
// arg[0] = offset
// arg[1] = isGlobal
Expand Down Expand Up @@ -1954,6 +2118,12 @@ private void executeTuples(PositionTracker position)
// stack[1] = key to check
Object arr = pop();
Object arg = pop();
checkScalar(arg);
if (arr == null || arr instanceof UninitializedObject) {
push(ZERO);
position.next();
break;
}
if (!(arr instanceof Map)) {
throw new AwkRuntimeException("Attempting to test membership on a non-associative-array.");
}
Expand Down Expand Up @@ -2378,7 +2548,11 @@ private void assign(long l, Object value, boolean isGlobal, PositionTracker posi
* Awk array element assignment functionality.
*/
private void assignArray(long offset, Object arrIdx, Object rhs, boolean isGlobal) {
Map<Object, Object> array = ensureMapVariable(offset, isGlobal);
assignMapElement(ensureMapVariable(offset, isGlobal), arrIdx, rhs);
}

private void assignMapElement(Map<Object, Object> array, Object arrIdx, Object rhs) {
checkScalar(arrIdx);
array.put(arrIdx, rhs);
push(rhs);
}
Expand Down Expand Up @@ -2616,6 +2790,13 @@ private Map<Object, Object> getMapVariable(long offset, boolean isGlobal) {
return toMap(value);
}

/**
* Casts an AWK value to an associative array.
*
* @param value value to validate
* @return the associative array value
* @throws AwkRuntimeException when {@code value} is scalar
*/
private Map<Object, Object> toMap(Object value) {
if (!(value instanceof Map)) {
throw new AwkRuntimeException("Attempting to treat a scalar as an array.");
Expand All @@ -2625,6 +2806,45 @@ private Map<Object, Object> toMap(Object value) {
return map;
}

/**
* Ensures a value is scalar before using it in a scalar-only context such as
* a subscript component.
*
* @param value value to validate
* @throws AwkRuntimeException when {@code value} is an array
*/
private void checkScalar(Object value) {
if (value instanceof Map) {
throw new AwkRuntimeException("Attempting to use an array in a scalar context.");
}
}
Comment thread
bertysentry marked this conversation as resolved.

/**
* Returns the nested associative array stored in {@code map[key]}, creating it
* when the key is undefined.
*
* @param map containing array
* @param key nested-array key
* @return the nested associative array stored at {@code key}
* @throws AwkRuntimeException when {@code key} is scalar-incompatible or when
* the existing slot contains a scalar
*/
private Map<Object, Object> ensureArrayInArray(Map<Object, Object> map, Object key) {
checkScalar(key);
Object value = JRT.getAwkValue(map, key);
if (value == null || value.equals(BLANK) || value instanceof UninitializedObject) {
Map<Object, Object> nested = newAwkArray();
map.put(key, nested);
return nested;
}
if (!(value instanceof Map)) {
throw new AwkRuntimeException("Attempting to use a scalar as an array.");
}
@SuppressWarnings("unchecked")
Map<Object, Object> nested = (Map<Object, Object>) value;
return nested;
}

private static final UninitializedObject BLANK = new UninitializedObject();

private static final class SingleRecordInputSource implements InputSource {
Expand Down
Loading
Loading