Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lexicon suffixes (Fixes #2551) #2553

Merged
merged 2 commits into from
Jul 26, 2019
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
37 changes: 37 additions & 0 deletions kerboscript_tests/lex_suffix_test1.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
local my_lex is lexicon(
"Key0" , "A",
"Key1" , "B",
"Key2" , "C",
"Key3" , "D",
"Key4" , "E",
"Key5" , "F").

print "Expect: ABCDEF".
print "Actual: " + concat_lex(my_lex).

set my_lex:key1 to "_".
set my_lex:key3 to "_".
set my_lex:key5 to "_".

print "Expect: A_C_E_".
print "Actual: " + concat_lex(my_lex).

set my_lex:key1 to { return "%". }.
set my_lex:key3 to { local a is 1. local b is 4. return a+b. }.
set my_lex:key5 to { return 3/2. }.

print "Expect: A%C5E1.5".
print "Actual: " + concat_lex(my_lex).

function concat_lex {
parameter the_lex.

local str is "".
for key in the_lex:keys {
if the_lex[key]:istype("Delegate")
set str to str + the_lex[key]().
else
set str to str + the_lex[key].
}
return str.
}
39 changes: 39 additions & 0 deletions kerboscript_tests/lex_suffix_test2.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
print "Testing using Lex as a psuedo-class.".
print "------------------------------------".


print "Making 'fred', an instance of person'.".
local fred is construct_person("Fred", 23).
print "fred:greet() prints this:".
print fred:greet().

print "Making 'henri', an instance of frenchperson.".
local henri is construct_frenchperson("Henri", 19).
print henri:greet().

function construct_person {
parameter name, age.

local myself is LEX().

set myself:name to name.
set myself:age to age.
set myself:greet to {
return "Hello, my name is " + myself:name +" and I am " + myself:age + " years old.".
}.

return myself.
}.

function construct_frenchperson {
parameter name, age.

local myself is construct_person(name, age).

// This is sort of like overriding a method:
set myself:greet to {
return "Bonjour, Je m'appelle " + myself:name + " et j'ai " + myself:age + " ans.".
}.

return myself.
}
42 changes: 42 additions & 0 deletions kerboscript_tests/lex_suffix_test3.ks
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local empty_lex is lexicon().
local populated_lex is lexicon(
"key0", 0, // valid suffix name
"key1", 10, // valid suffix name
"key2", 20, // valid suffix name
"KEY3", 30, // valid suffix name
// None of the following 3 should be valid suffix names because of the
// spaces:
" SpaceBefore", "----",
"SpaceAfter ", "----",
"Space Between", "----",
V(1,0,0), "----", // not a valid suffix name because Vectors aren't strings.
"zzzzzz", 9999 // valid suffix name
).
print "Expect: True".
print "Actual: " + empty_lex:hassuffix("add"). // built-in suffix for all lex's.
print " ".
print "Expect: True".
print "Actual: " + populated_lex:hassuffix("add"). // built-in suffix for all lex's.
print " ".
print "Expect: False".
print "Actual: " + empty_lex:hassuffix("key2"). // only exists if key2 is in the lex.
print " ".
print "Expect: True".
print "Actual: " + populated_lex:hassuffix("key2"). // only exists if key2 is in the lex.
print " ".
print "Expect: True".
print "Actual: " + populated_lex:hassuffix("kEy3"). // case insensitive check.
print " ".
print "Expect: True".
print "Actual: " + populated_lex:haskey("Space Between"). // key exists
print " ".
print "Expect: False".
print "Actual: " + populated_lex:hassuffix("Space Between"). // but isn't a valid suffix name

// There should be 5 more suffixes in the populated list than in default lex's,
// because 5 of the keys in it form valid identifier strings:
// If this is more than 5, then keys that shouldn't be
// suffixes are getting into the list.
print "Expect: 5".
print "Actual: " + (populated_lex:suffixnames:length - empty_lex:suffixnames:length).

6 changes: 3 additions & 3 deletions src/kOS.Safe.Test/Execution/Config.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using kOS.Safe.Encapsulation;
using kOS.Safe.Encapsulation;
using kOS.Safe.Encapsulation.Suffixes;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -212,7 +212,7 @@ public IList<ConfigKey> GetConfigKeys()
return new List<ConfigKey>();
}

public ISuffixResult GetSuffix(string suffixName)
public ISuffixResult GetSuffix(string suffixName, bool failOkay = false)
{
throw new NotImplementedException();
}
Expand All @@ -222,7 +222,7 @@ public void SaveConfig()
throw new NotImplementedException();
}

public bool SetSuffix(string suffixName, object value)
public bool SetSuffix(string suffixName, object value, bool failOkay = false)
{
throw new NotImplementedException();
}
Expand Down
2 changes: 1 addition & 1 deletion src/kOS.Safe/Compilation/KS/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ private void VisitTernary(ParseNode node)
/// Handles the short-circuit logic of boolean OR and boolean AND
/// chains. It is like VisitExpressionChain (see elsewhere) but
/// in this case it has the special logic to short circuit and skip
/// executing the righthand expression if it can. (The generic VisitExpressionXhain
/// executing the righthand expression if it can. (The generic VisitExpressionChain
/// always evaluates both the left and right sides of the operator first, then
/// does the operation).
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/kOS.Safe/Compilation/KS/KSScript.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using kOS.Safe.Exceptions;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using kOS.Safe.Persistence;

namespace kOS.Safe.Compilation.KS
Expand Down
7 changes: 6 additions & 1 deletion src/kOS.Safe/Compilation/KS/kRISC.tpg
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ COLON -> @":";
IN -> @"in\b";
ARRAYINDEX -> @"#";
ALL -> @"all\b";
IDENTIFIER -> @"[_\p{L}]\w*";

// WARNING - IF YOU EDIT THE REGEX FOR IDENTIFIER ON THE NEXT LINE,
// THEN ALSO EDIT kOS.Safe.Utilities.StringUtil.IsValidIdentifier()
// TO USE THE SAME REGEX !!!!!
IDENTIFIER -> @"[_\p{L}]\w*"; //<---- Important - see above Comment!!!!!

FILEIDENT -> @"[_\p{L}]\w*(\.[_\p{L}]\w*)*";
INTEGER -> @"\d[_\d]*";
DOUBLE -> @"(\d+(?:_\d*)*)?\.\d+(?:_\d*)*";
Expand Down
6 changes: 3 additions & 3 deletions src/kOS.Safe/Encapsulation/ISuffixed.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using kOS.Safe.Encapsulation.Suffixes;
using kOS.Safe.Encapsulation.Suffixes;

namespace kOS.Safe.Encapsulation
{
public interface ISuffixed
{
bool SetSuffix(string suffixName, object value);
ISuffixResult GetSuffix(string suffixName);
bool SetSuffix(string suffixName, object value, bool failOkay = false);
ISuffixResult GetSuffix(string suffixName, bool failOkay = false);
}
}
101 changes: 101 additions & 0 deletions src/kOS.Safe/Encapsulation/Lexicon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ public int GetHashCode(TI obj)
}

private IDictionary<Structure, Structure> internalDictionary;
private IDictionary<Structure, SetSuffix<Structure>> keySuffixes;
private bool caseSensitive;

public Lexicon()
{
internalDictionary = new Dictionary<Structure, Structure>(new LexiconComparer<Structure>());
keySuffixes = new Dictionary<Structure, SetSuffix<Structure>>(new LexiconComparer<Structure>());
caseSensitive = false;
InitalizeSuffixes();
}
Expand Down Expand Up @@ -137,6 +139,13 @@ private void SetCaseSensitivity(BooleanValue value)
internalDictionary = newCase ?
new Dictionary<Structure, Structure>() :
new Dictionary<Structure, Structure>(new LexiconComparer<Structure>());

// Regardless of whether or not the lexicon itself is case sensitive,
// the key Suffixes have to be IN-sensitive because they are getting
// values who's case got squashed by the compiler. This needs to
// be documented well in the user docs (i.e. using the suffix syntax
// cannot detect the difference between keys that differ only in case).
keySuffixes = new Dictionary<Structure, SetSuffix<Structure>>(new LexiconComparer<Structure>());
}

private BooleanValue HasValue(Structure value)
Expand Down Expand Up @@ -293,6 +302,98 @@ public override string ToString()
return new SafeSerializationMgr(null).ToString(this);
}

// Try to call the normal SetSuffix that all structures do, but if that fails,
// then try to use this suffix name as a key and set the value in the lexicon
// at that key. This can insert new key values in the lexicon, just like
// doing `set x["foo"] to y.` can.
public override bool SetSuffix(string suffixName, object value, bool failOkay = false)
{
if (base.SetSuffix(suffixName, value, true))
return true;

// If the above fails, then fallback on the key technique:
internalDictionary[new StringValue(suffixName)] = FromPrimitiveWithAssert(value);
return true;
}

// Try to get the suffix the normal way that all structures do, but if
// that fails, then try to get the value in the lexicon who's key is
// this suffix name. (This implements using keys with the "colon" suffix
// syntax for issue #2551.)
public override ISuffixResult GetSuffix(string suffixName, bool failOkay = false)
{
ISuffixResult baseResult = base.GetSuffix(suffixName, true);
if (baseResult != null)
return baseResult;

// If the above fails, but this suffix IS the name of a key in the
// dictionary, then try to use the key-suffix we made earlier
// (or make a new one and use it now)
// ---------------------------------------------------------------

StringValue suffixAsStruct = new StringValue(suffixName);

if (internalDictionary.ContainsKey(suffixAsStruct)) // even if keySuffixes has the value, it doesn't count if the key isn't there anymore.
{
SetSuffix<Structure> theSuffix;
if (keySuffixes.TryGetValue(suffixAsStruct, out theSuffix))
{
return theSuffix.Get();
}
else // make a new suffix then since this is the first time it got mentioned this way:
{
theSuffix = new SetSuffix<Structure>(() => internalDictionary[suffixAsStruct], value => internalDictionary[suffixAsStruct] = value);
keySuffixes.Add(suffixAsStruct, theSuffix);
return theSuffix.Get();
}
}
else
{
// This will error out, but we may as well also remove this key
// from the list of suffixes:
keySuffixes.Remove(suffixAsStruct);

if (failOkay)
return null;
else
throw new KOSSuffixUseException("get", suffixName, this);
}
}

public override BooleanValue HasSuffix(StringValue suffixName)
{
if (base.HasSuffix(suffixName))
return true;
if (internalDictionary.ContainsKey(suffixName))
{
// It can only be a suffix if it is a valid identifier pattern, else the
// parser won't let the colon suffix syntax see it to pass it to GetSuffix()
// or SetSuffix():
return StringUtil.IsValidIdentifier(suffixName);
}
return false;
}

/// <summary>
/// Like normal Structure.GetSuffixNames except it also adds all
/// the keys that would validly work with the colon suffix syntax
/// to the list.
/// </summary>
/// <returns></returns>
public override ListValue<StringValue> GetSuffixNames()
{
ListValue<StringValue> theList = base.GetSuffixNames();

foreach (Structure key in internalDictionary.Keys)
{
StringValue keyStr = key as StringValue;
if (keyStr != null && StringUtil.IsValidIdentifier(keyStr))
{
theList.Add(keyStr);
}
}
return new ListValue<StringValue>(theList.OrderBy(item => item.ToString()));
}
public override Dump Dump()
{
var result = new DumpWithHeader
Expand Down
38 changes: 30 additions & 8 deletions src/kOS.Safe/Encapsulation/Structure.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
Expand Down Expand Up @@ -130,19 +130,28 @@ private static IDictionary<string, ISuffix> GetStaticSuffixesForType(Type curren
}
}

public virtual bool SetSuffix(string suffixName, object value)
/// <summary>
/// Set a suffix of this structure that has suffixName to the given value.
/// If failOkay is false then it will throw exception if it fails to find the suffix.
/// If failOkay is true then it will continue happily if it fails to find the suffix.
/// </summary>
/// <param name="suffixName"></param>
/// <param name="value"></param>
/// <param name="failOkay"></param>
/// <returns>false if failOkay was true and it failed to find the suffix</returns>
public virtual bool SetSuffix(string suffixName, object value, bool failOkay = false)
{
callInitializeSuffixes();
var suffixes = GetStaticSuffixesForType(GetType());

if (!ProcessSetSuffix(suffixes, suffixName, value))
if (!ProcessSetSuffix(suffixes, suffixName, value, failOkay))
{
return ProcessSetSuffix(instanceSuffixes, suffixName, value);
return ProcessSetSuffix(instanceSuffixes, suffixName, value, failOkay);
}
return false;
}

private bool ProcessSetSuffix(IDictionary<string, ISuffix> suffixes, string suffixName, object value)
private bool ProcessSetSuffix(IDictionary<string, ISuffix> suffixes, string suffixName, object value, bool failOkay = false)
{
ISuffix suffix;
if (suffixes.TryGetValue(suffixName, out suffix))
Expand All @@ -153,12 +162,22 @@ private bool ProcessSetSuffix(IDictionary<string, ISuffix> suffixes, string suff
settable.Set(value);
return true;
}
throw new KOSSuffixUseException("set", suffixName, this);
if (failOkay)
return false;
else
throw new KOSSuffixUseException("set", suffixName, this);
}
return false;
}

public virtual ISuffixResult GetSuffix(string suffixName)
/// <summary>
/// Get the suffix with this name, or if it fails to find it, then either
/// throw exception or merely return null. (Will return null only if failOkay is true).
/// </summary>
/// <param name="suffixName"></param>
/// <param name="failOkay"></param>
/// <returns></returns>
public virtual ISuffixResult GetSuffix(string suffixName, bool failOkay = false)
{
callInitializeSuffixes();
ISuffix suffix;
Expand All @@ -171,7 +190,10 @@ public virtual ISuffixResult GetSuffix(string suffixName)

if (!suffixes.TryGetValue(suffixName, out suffix))
{
throw new KOSSuffixUseException("get",suffixName,this);
if (failOkay)
return null;
else
throw new KOSSuffixUseException("get",suffixName,this);
}
return suffix.Get();
}
Expand Down
Loading