Define functions and constants in root scope

This commit is contained in:
aNNiMON 2023-09-03 21:42:23 +03:00 committed by Victor Melnik
parent 16de63f720
commit fe39c1ac00
15 changed files with 344 additions and 188 deletions

View File

@ -1,7 +1,5 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.exceptions.UnknownFunctionException;
import java.util.HashMap;
import java.util.Map;
/**
@ -9,32 +7,21 @@ import java.util.Map;
* @author aNNiMON
*/
public final class Functions {
private static final Map<String, Function> functions;
static {
functions = new HashMap<>();
}
private Functions() { }
public static void clear() {
functions.clear();
}
public static Map<String, Function> getFunctions() {
return functions;
return ScopeHandler.functions();
}
public static boolean isExists(String key) {
return functions.containsKey(key);
public static boolean isExists(String name) {
return ScopeHandler.isFunctionExists(name);
}
public static Function get(String key) {
if (!isExists(key)) throw new UnknownFunctionException(key);
return functions.get(key);
public static Function get(String name) {
return ScopeHandler.getFunction(name);
}
public static void set(String key, Function function) {
functions.put(key, function);
ScopeHandler.setFunction(key, function);
}
}

View File

@ -0,0 +1,68 @@
package com.annimon.ownlang.lib;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
final class RootScope extends Scope {
private final Map<String, Value> constants;
private final Map<String, Function> functions;
RootScope() {
functions = new ConcurrentHashMap<>();
constants = new ConcurrentHashMap<>();
constants.put("true", NumberValue.ONE);
constants.put("false", NumberValue.ZERO);
}
@Override
public boolean isRoot() {
return true;
}
@Override
public boolean contains(String name) {
return super.containsVariable(name)
|| containsConstant(name);
}
public boolean containsConstant(String name) {
return constants.containsKey(name);
}
@Override
public Value get(String name) {
if (containsConstant(name)) {
return getConstant(name);
}
return super.get(name);
}
public Value getConstant(String name) {
return constants.get(name);
}
public void setConstant(String name, Value value) {
constants.put(name, value);
}
public Map<String, Value> getConstants() {
return constants;
}
public boolean containsFunction(String name) {
return functions.containsKey(name);
}
public Function getFunction(String name) {
return functions.get(name);
}
public void setFunction(String name, Function function) {
functions.put(name, function);
}
public Map<String, Function> getFunctions() {
return functions;
}
}

View File

@ -0,0 +1,64 @@
package com.annimon.ownlang.lib;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
sealed class Scope permits RootScope {
final Scope parent;
private final Map<String, Value> variables;
Scope() {
this(null);
}
Scope(Scope parent) {
this.parent = parent;
variables = new ConcurrentHashMap<>();
}
public boolean isRoot() {
return !hasParent();
}
public boolean hasParent() {
return parent != null;
}
public boolean contains(String name) {
return containsVariable(name);
}
public final boolean containsVariable(String name) {
return variables.containsKey(name);
}
public Value get(String name) {
return getVariable(name);
}
public final Value getVariable(String name) {
return variables.get(name);
}
public final void setVariable(String name, Value value) {
variables.put(name, value);
}
public final void removeVariable(String name) {
variables.remove(name);
}
public Map<String, Value> getVariables() {
return variables;
}
static class ScopeFindData {
final boolean isFound;
final Scope scope;
ScopeFindData(boolean isFound, Scope scope) {
this.isFound = isFound;
this.scope = scope;
}
}
}

View File

@ -0,0 +1,129 @@
package com.annimon.ownlang.lib;
import com.annimon.ownlang.exceptions.UnknownFunctionException;
import com.annimon.ownlang.lib.Scope.ScopeFindData;
import java.util.Map;
public final class ScopeHandler {
private static final Object lock = new Object();
private static volatile RootScope rootScope;
private static volatile Scope scope;
static {
ScopeHandler.resetScope();
}
public static Map<String, Value> variables() {
return scope.getVariables();
}
public static Map<String, Value> constants() {
return rootScope.getConstants();
}
public static Map<String, Function> functions() {
return rootScope.getFunctions();
}
/**
* Resets a scope for new program execution
*/
public static void resetScope() {
rootScope = new RootScope();
scope = rootScope;
}
public static void push() {
synchronized (lock) {
scope = new Scope(scope);
}
}
public static void pop() {
synchronized (lock) {
if (!scope.isRoot()) {
scope = scope.parent;
}
}
}
public static boolean isFunctionExists(String name) {
return rootScope.containsFunction(name);
}
public static Function getFunction(String name) {
final var function = rootScope.getFunction(name);
if (function == null) throw new UnknownFunctionException(name);
return function;
}
public static void setFunction(String name, Function function) {
rootScope.setFunction(name, function);
}
public static boolean isVariableOrConstantExists(String name) {
synchronized (lock) {
return findScope(name).isFound;
}
}
public static Value getVariableOrConstant(String name) {
synchronized (lock) {
final ScopeFindData scopeData = findScope(name);
if (scopeData.isFound) {
return scopeData.scope.get(name);
}
}
return NumberValue.ZERO;
}
public static Value getVariable(String name) {
synchronized (lock) {
final ScopeFindData scopeData = findScope(name);
if (scopeData.isFound) {
return scopeData.scope.getVariable(name);
}
}
// TODO should be strict
return NumberValue.ZERO;
}
public static void setVariable(String name, Value value) {
synchronized (lock) {
findScope(name).scope.setVariable(name, value);
}
}
public static void setConstant(String name, Value value) {
rootScope.setConstant(name, value);
}
public static void defineVariableInCurrentScope(String name, Value value) {
synchronized (lock) {
scope.setVariable(name, value);
}
}
public static void removeVariable(String name) {
synchronized (lock) {
findScope(name).scope.removeVariable(name);
}
}
private static ScopeFindData findScope(String name) {
Scope current = scope;
do {
if (current.contains(name)) {
return new ScopeFindData(true, current);
}
} while ((current = current.parent) != null);
return new ScopeFindData(false, scope);
}
}

View File

@ -1,118 +1,34 @@
package com.annimon.ownlang.lib;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author aNNiMON
*/
public final class Variables {
private static final Object lock = new Object();
private static class Scope {
final Scope parent;
final Map<String, Value> variables;
Scope() {
this(null);
}
Scope(Scope parent) {
this.parent = parent;
variables = new ConcurrentHashMap<>();
}
}
private static class ScopeFindData {
boolean isFound;
Scope scope;
}
private static volatile Scope scope;
static {
Variables.clear();
}
private Variables() { }
public static Map<String, Value> variables() {
return scope.variables;
return ScopeHandler.variables();
}
public static boolean isExists(String name) {
return ScopeHandler.isVariableOrConstantExists(name);
}
public static Value get(String name) {
return ScopeHandler.getVariableOrConstant(name);
}
public static void set(String name, Value value) {
ScopeHandler.setVariable(name, value);
}
public static void clear() {
scope = new Scope();
scope.variables.clear();
scope.variables.put("true", NumberValue.ONE);
scope.variables.put("false", NumberValue.ZERO);
}
public static void push() {
synchronized (lock) {
scope = new Scope(scope);
}
}
public static void pop() {
synchronized (lock) {
if (scope.parent != null) {
scope = scope.parent;
}
}
}
public static boolean isExists(String key) {
synchronized (lock) {
return findScope(key).isFound;
}
}
public static Value get(String key) {
synchronized (lock) {
final ScopeFindData scopeData = findScope(key);
if (scopeData.isFound) {
return scopeData.scope.variables.get(key);
}
}
return NumberValue.ZERO;
}
public static void set(String key, Value value) {
synchronized (lock) {
findScope(key).scope.variables.put(key, value);
}
}
public static void define(String key, Value value) {
synchronized (lock) {
scope.variables.put(key, value);
}
}
public static void remove(String key) {
synchronized (lock) {
findScope(key).scope.variables.remove(key);
}
}
/*
* Find scope where variable exists.
/**
* For compatibility with other modules
*/
private static ScopeFindData findScope(String variable) {
final ScopeFindData result = new ScopeFindData();
Scope current = scope;
do {
if (current.variables.containsKey(variable)) {
result.isFound = true;
result.scope = current;
return result;
}
} while ((current = current.parent) != null);
result.isFound = false;
result.scope = scope;
return result;
public static void define(String name, Value value) {
ScopeHandler.setConstant(name, value);
}
}

View File

@ -14,13 +14,13 @@ public class ClassMethod extends UserDefinedFunction {
@Override
public Value execute(Value[] values) {
Variables.push();
Variables.define("this", classInstance.getThisMap());
ScopeHandler.push();
ScopeHandler.defineVariableInCurrentScope("this", classInstance.getThisMap());
try {
return super.execute(values);
} finally {
Variables.pop();
ScopeHandler.pop();
}
}
}

View File

@ -45,21 +45,21 @@ public class UserDefinedFunction implements Function {
}
try {
Variables.push();
ScopeHandler.push();
for (int i = 0; i < size; i++) {
Variables.define(getArgsName(i), values[i]);
ScopeHandler.defineVariableInCurrentScope(getArgsName(i), values[i]);
}
// Optional args if exists
for (int i = size; i < totalArgsCount; i++) {
final Argument arg = arguments.get(i);
Variables.define(arg.name(), arg.valueExpr().eval());
ScopeHandler.defineVariableInCurrentScope(arg.name(), arg.valueExpr().eval());
}
body.execute();
return NumberValue.ZERO;
} catch (ReturnStatement rt) {
return rt.getResult();
} finally {
Variables.pop();
ScopeHandler.pop();
}
}

View File

@ -1,13 +1,12 @@
package com.annimon.ownlang.parser;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.parser.linters.AssignValidator;
import com.annimon.ownlang.parser.linters.UseWithNonStringValueValidator;
import com.annimon.ownlang.parser.linters.DefaultFunctionsOverrideValidator;
import com.annimon.ownlang.lib.Functions;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.lib.ScopeHandler;
import com.annimon.ownlang.parser.ast.Statement;
import com.annimon.ownlang.parser.ast.Visitor;
import com.annimon.ownlang.parser.linters.AssignValidator;
import com.annimon.ownlang.parser.linters.DefaultFunctionsOverrideValidator;
import com.annimon.ownlang.parser.linters.UseWithNonStringValueValidator;
public final class Linter {
@ -36,7 +35,6 @@ public final class Linter {
}
private void resetState() {
Variables.clear();
Functions.getFunctions().clear();
ScopeHandler.resetScope();
}
}

View File

@ -1,10 +1,7 @@
package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.lib.ArrayValue;
import com.annimon.ownlang.lib.MapValue;
import com.annimon.ownlang.lib.Types;
import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.lib.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -42,7 +39,7 @@ public final class DestructuringAssignmentStatement extends InterruptableNode im
for (int i = 0; i < size; i++) {
final String variable = variables.get(i);
if (variable != null) {
Variables.define(variable, array.get(i));
ScopeHandler.defineVariableInCurrentScope(variable, array.get(i));
}
}
}
@ -51,7 +48,7 @@ public final class DestructuringAssignmentStatement extends InterruptableNode im
for (Map.Entry<Value, Value> entry : map) {
final String variable = variables.get(i);
if (variable != null) {
Variables.define(variable, new ArrayValue(
ScopeHandler.defineVariableInCurrentScope(variable, new ArrayValue(
new Value[] { entry.getKey(), entry.getValue() }
));
}

View File

@ -23,7 +23,8 @@ public final class ForeachArrayStatement extends InterruptableNode implements St
@Override
public void execute() {
super.interruptionCheck();
final Value previousVariableValue = Variables.isExists(variable) ? Variables.get(variable) : null;
// TODO removing without checking shadowing is dangerous
final Value previousVariableValue = ScopeHandler.getVariable(variable);
final Value containerValue = container.eval();
switch (containerValue.type()) {
@ -42,15 +43,15 @@ public final class ForeachArrayStatement extends InterruptableNode implements St
// Restore variables
if (previousVariableValue != null) {
Variables.set(variable, previousVariableValue);
ScopeHandler.setVariable(variable, previousVariableValue);
} else {
Variables.remove(variable);
ScopeHandler.removeVariable(variable);
}
}
private void iterateString(String str) {
for (char ch : str.toCharArray()) {
Variables.set(variable, new StringValue(String.valueOf(ch)));
ScopeHandler.setVariable(variable, new StringValue(String.valueOf(ch)));
try {
body.execute();
} catch (BreakStatement bs) {
@ -63,7 +64,7 @@ public final class ForeachArrayStatement extends InterruptableNode implements St
private void iterateArray(ArrayValue containerValue) {
for (Value value : containerValue) {
Variables.set(variable, value);
ScopeHandler.setVariable(variable, value);
try {
body.execute();
} catch (BreakStatement bs) {
@ -76,7 +77,7 @@ public final class ForeachArrayStatement extends InterruptableNode implements St
private void iterateMap(MapValue containerValue) {
for (Map.Entry<Value, Value> entry : containerValue) {
Variables.set(variable, new ArrayValue(new Value[] {
ScopeHandler.setVariable(variable, new ArrayValue(new Value[] {
entry.getKey(),
entry.getValue()
}));

View File

@ -24,8 +24,9 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat
@Override
public void execute() {
super.interruptionCheck();
final Value previousVariableValue1 = Variables.isExists(key) ? Variables.get(key) : null;
final Value previousVariableValue2 = Variables.isExists(value) ? Variables.get(value) : null;
// TODO removing without checking shadowing is dangerous
final Value previousVariableValue1 = ScopeHandler.getVariable(key);
final Value previousVariableValue2 = ScopeHandler.getVariable(value);
final Value containerValue = container.eval();
switch (containerValue.type()) {
@ -44,21 +45,21 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat
// Restore variables
if (previousVariableValue1 != null) {
Variables.set(key, previousVariableValue1);
ScopeHandler.setVariable(key, previousVariableValue1);
} else {
Variables.remove(key);
ScopeHandler.removeVariable(key);
}
if (previousVariableValue2 != null) {
Variables.set(value, previousVariableValue2);
ScopeHandler.setVariable(value, previousVariableValue2);
} else {
Variables.remove(value);
ScopeHandler.removeVariable(value);
}
}
private void iterateString(String str) {
for (char ch : str.toCharArray()) {
Variables.set(key, new StringValue(String.valueOf(ch)));
Variables.set(value, NumberValue.of(ch));
ScopeHandler.setVariable(key, new StringValue(String.valueOf(ch)));
ScopeHandler.setVariable(value, NumberValue.of(ch));
try {
body.execute();
} catch (BreakStatement bs) {
@ -72,8 +73,8 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat
private void iterateArray(ArrayValue containerValue) {
int index = 0;
for (Value v : containerValue) {
Variables.set(key, v);
Variables.set(value, NumberValue.of(index++));
ScopeHandler.setVariable(key, v);
ScopeHandler.setVariable(value, NumberValue.of(index++));
try {
body.execute();
} catch (BreakStatement bs) {
@ -86,8 +87,8 @@ public final class ForeachMapStatement extends InterruptableNode implements Stat
private void iterateMap(MapValue containerValue) {
for (Map.Entry<Value, Value> entry : containerValue) {
Variables.set(key, entry.getKey());
Variables.set(value, entry.getValue());
ScopeHandler.setVariable(key, entry.getKey());
ScopeHandler.setVariable(value, entry.getValue());
try {
body.execute();
} catch (BreakStatement bs) {

View File

@ -64,11 +64,11 @@ public final class FunctionalExpression extends InterruptableNode implements Exp
}
private Function getFunction(String key) {
if (Functions.isExists(key)) {
return Functions.get(key);
if (ScopeHandler.isFunctionExists(key)) {
return ScopeHandler.getFunction(key);
}
if (Variables.isExists(key)) {
final Value variable = Variables.get(key);
if (ScopeHandler.isVariableOrConstantExists(key)) {
final Value variable = ScopeHandler.getVariableOrConstant(key);
if (variable.type() == Types.FUNCTION) {
return ((FunctionValue)variable).getValue();
}

View File

@ -1,11 +1,8 @@
package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.exceptions.PatternMatchingException;
import com.annimon.ownlang.lib.ArrayValue;
import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.Types;
import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.lib.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -44,18 +41,18 @@ public final class MatchExpression extends InterruptableNode implements Expressi
final VariablePattern pattern = (VariablePattern) p;
if (pattern.variable.equals("_")) return evalResult(p.result);
if (Variables.isExists(pattern.variable)) {
if (match(value, Variables.get(pattern.variable)) && optMatches(p)) {
if (ScopeHandler.isVariableOrConstantExists(pattern.variable)) {
if (match(value, ScopeHandler.getVariableOrConstant(pattern.variable)) && optMatches(p)) {
return evalResult(p.result);
}
} else {
Variables.define(pattern.variable, value);
ScopeHandler.defineVariableInCurrentScope(pattern.variable, value);
if (optMatches(p)) {
final Value result = evalResult(p.result);
Variables.remove(pattern.variable);
ScopeHandler.removeVariable(pattern.variable);
return result;
}
Variables.remove(pattern.variable);
ScopeHandler.removeVariable(pattern.variable);
}
}
if ((value.type() == Types.ARRAY) && (p instanceof ListPattern)) {
@ -64,7 +61,7 @@ public final class MatchExpression extends InterruptableNode implements Expressi
// Clean up variables if matched
final Value result = evalResult(p.result);
for (String var : pattern.parts) {
Variables.remove(var);
ScopeHandler.removeVariable(var);
}
return result;
}
@ -105,11 +102,11 @@ public final class MatchExpression extends InterruptableNode implements Expressi
case 1: // match arr { case [x]: x = arr ... }
final String variable = parts.get(0);
Variables.define(variable, array);
ScopeHandler.defineVariableInCurrentScope(variable, array);
if (optMatches(p)) {
return true;
}
Variables.remove(variable);
ScopeHandler.removeVariable(variable);
return false;
default: { // match arr { case [...]: .. }
@ -128,7 +125,7 @@ public final class MatchExpression extends InterruptableNode implements Expressi
private boolean matchListPatternEqualsSize(ListPattern p, List<String> parts, int partsSize, ArrayValue array) {
// Set variables
for (int i = 0; i < partsSize; i++) {
Variables.define(parts.get(i), array.get(i));
ScopeHandler.defineVariableInCurrentScope(parts.get(i), array.get(i));
}
if (optMatches(p)) {
// Clean up will be provided after evaluate result
@ -136,7 +133,8 @@ public final class MatchExpression extends InterruptableNode implements Expressi
}
// Clean up variables if no match
for (String var : parts) {
Variables.remove(var);
// TODO removing without checking shadowing is dangerous
ScopeHandler.removeVariable(var);
}
return false;
}
@ -145,14 +143,14 @@ public final class MatchExpression extends InterruptableNode implements Expressi
// Set element variables
final int lastPart = partsSize - 1;
for (int i = 0; i < lastPart; i++) {
Variables.define(parts.get(i), array.get(i));
ScopeHandler.defineVariableInCurrentScope(parts.get(i), array.get(i));
}
// Set tail variable
final ArrayValue tail = new ArrayValue(arraySize - partsSize + 1);
for (int i = lastPart; i < arraySize; i++) {
tail.set(i - lastPart, array.get(i));
}
Variables.define(parts.get(lastPart), tail);
ScopeHandler.defineVariableInCurrentScope(parts.get(lastPart), tail);
// Check optional condition
if (optMatches(p)) {
// Clean up will be provided after evaluate result
@ -160,7 +158,7 @@ public final class MatchExpression extends InterruptableNode implements Expressi
}
// Clean up variables
for (String var : parts) {
Variables.remove(var);
ScopeHandler.removeVariable(var);
}
return false;
}

View File

@ -1,6 +1,7 @@
package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.exceptions.VariableDoesNotExistsException;
import com.annimon.ownlang.lib.ScopeHandler;
import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.lib.Variables;
@ -30,7 +31,7 @@ public final class VariableExpression extends InterruptableNode implements Expre
@Override
public Value set(Value value) {
Variables.set(name, value);
ScopeHandler.setVariable(name, value);
return value;
}

View File

@ -1,10 +1,7 @@
package com.annimon.ownlang.parser;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.lib.FunctionValue;
import com.annimon.ownlang.lib.Functions;
import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.lib.*;
import com.annimon.ownlang.outputsettings.OutputSettings;
import com.annimon.ownlang.outputsettings.StringOutputSettings;
import com.annimon.ownlang.parser.ast.FunctionDefineStatement;
@ -33,8 +30,7 @@ public class ProgramsTest {
@BeforeEach
public void initialize() {
Variables.clear();
Functions.clear();
ScopeHandler.resetScope();
// Let's mock junit methods as ounit functions
Functions.set("assertEquals", (args) -> {
assertEquals(args[0], args[1]);