Skip to content

Commit 3722844

Browse files
committed
Feature: Golf function, variable and #define names.
1 parent a3efeb5 commit 3722844

20 files changed

+374
-54
lines changed

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,15 @@ After writing a Shadertoy shader, usually from my boilerplate starting code, the
2121

2222
It occurred to me all of these steps can be automated.
2323

24-
## What Is It *Not* For?
25-
This is *not* a tool to compete with certain other tools to absolutely MINIMIZE the size of the code - Useful for preparing GLSL for use in 4KB graphics demos, etc.
24+
## What It *Can* Do
25+
Over time more and more functionality has been added to this tool which can be used to '[GOLF](https://en.wikipedia.org/wiki/Code_golf)' code.
26+
This is an ongoing process, and other tools are much more likely to produce more compact/compressible code (Especially when preparing GLSL for use in 4KB graphics demos, etc). However, GLSL Shader Shrinker can...
2627

27-
GLSL Shader Shrink will _not_:
2828
* Rename functions and variable to single-characters.
2929
* Inline functions.
3030
* Introduce ```#define``` macros to minimize the code character count.
31-
* Replace code with a 'more compressible' equivalent.
32-
* ...or otherwise '[GOLF](https://en.wikipedia.org/wiki/Code_golf)' anything.
3331

34-
...although some of these items might be suggested as a 'hint'.
32+
(Some of of these items might be suggested as a 'hint' even when not GOLFing.)
3533

3634
## Example (Shadertoy Starting Point)
3735
A small snippet of GLSL which shows **some** of the optimizations available.
@@ -90,7 +88,7 @@ Next choose the level of processing you want to apply.
9088

9189
* Maximum processing - All options enabled.
9290
* Minimal processing - Minimal changes (Mostly code reformatting).
93-
* Custom options - Toggle exactly which processing features you require.
91+
* Custom options - Toggle exactly which processing features you require (Including GOLFing options)
9492

9593
### Step 3 - Exporting GLSL Code
9694
Export the 'shrunk' GLSL.
@@ -146,6 +144,7 @@ In most cases they can be worked-around using a set of 'custom' settings which d
146144
* [Perform Simple Arithmetic](#perform-simple-arithmetic)
147145
* [Replace Functions Calls With Result](#replace-functions-calls-with-result)
148146
* [Move constant parameters to within called functions](#move-constant-parameters-to-within-called-functions)
147+
* [GOLF user defined code names](#golf-user-defined-code-names)
149148
## Remove Comments
150149
Remove all C/C++ -style comments from the code.
151150
#### Before
@@ -614,3 +613,20 @@ float f() {
614613
```
615614

616615
---
616+
## GOLF user defined code names
617+
Reduce the size of the code by renaming user-defined names.
618+
Attempts are made to keep some of the letters of the object to rename.
619+
#### Before
620+
```c
621+
float sum(float number, float anotherNumber) {
622+
return number + anotherNumber;
623+
}
624+
```
625+
#### After
626+
```c
627+
float s(float n, float a) {
628+
return n + a;
629+
}
630+
```
631+
632+
---

ShaderShrinker/Shrinker.Lexer/StringExtensions.cs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,7 @@ public static int GetCodeCharCount(this string glsl)
4343
glsl = glsl.Remove(startComment, endComment - startComment + 2);
4444
}
4545

46-
return glsl.Split('\r', '\n').Sum(o => o.GetCodeCharCountForLine());
47-
}
48-
49-
private static int GetCodeCharCountForLine(this string line)
50-
{
51-
return line.Count(ch => !char.IsWhiteSpace(ch));
52-
//var commentIndex = line.IndexOf("//");
53-
//if (commentIndex >= 0)
54-
// line = line.Substring(0, commentIndex);
55-
//commentIndex = line.IndexOf("/*");
56-
//if (commentIndex >= 0)
57-
// line = line.Substring(0, commentIndex);
58-
59-
//var sb = new StringBuilder(line.Trim());
60-
//int l;
61-
//do
62-
//{
63-
// l = sb.Length;
64-
// sb.Replace(" ", null)
65-
// .Replace("\t", null)
66-
// .Replace("\r", null)
67-
// .Replace("\n", null);
68-
//} while (sb.Length != l);
69-
70-
//return sb.Length;
46+
return glsl.Split('\r', '\n').Sum(o => o.Count(ch => !char.IsWhiteSpace(ch)));
7147
}
7248

7349
public static bool IsNewline(this char ch) => ch == '\n' || ch == '\r';

ShaderShrinker/Shrinker.Parser/CustomOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class CustomOptions
4747
public bool SimplifyBranching { get; set; }
4848
public bool ReplaceFunctionCallsWithResult { get; set; }
4949
public bool MoveConstantParametersIntoCalledFunctions { get; set; }
50+
public bool GolfNames { get; set; }
5051

5152
private CustomOptions()
5253
{
@@ -56,12 +57,13 @@ public static CustomOptions All()
5657
{
5758
var options = SetAllOptions(true);
5859
options.KeepHeaderComments = false;
60+
options.GolfNames = false;
5961
return options;
6062
}
6163

6264
public static CustomOptions None() => SetAllOptions(false);
6365

64-
private static CustomOptions SetAllOptions(bool value)
66+
public static CustomOptions SetAllOptions(bool value)
6567
{
6668
var options = new CustomOptions();
6769

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="GolfExtensions.cs">
3+
// Copyright (c) 2022 Dean Edis. All rights reserved.
4+
// </copyright>
5+
// <summary>
6+
// This code is provided on an "as is" basis and without warranty of any kind.
7+
// We do not warrant or make any representations regarding the use or
8+
// results of use of this code.
9+
// </summary>
10+
// -----------------------------------------------------------------------
11+
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using Shrinker.Parser.SyntaxNodes;
15+
16+
namespace Shrinker.Parser.Optimizations
17+
{
18+
public static class GolfExtensions
19+
{
20+
/// <summary>
21+
/// Replace named objects (functions/variables) with short 'golfed' versions.
22+
/// </summary>
23+
public static void GolfNames(this SyntaxNode rootNode)
24+
{
25+
var nameMap = BuildGolfRenameMap(rootNode).Where(o => o.Key != o.Value).ToDictionary(o => o.Key, o => o.Value);
26+
27+
foreach (var node in rootNode.TheTree.OfType<IRenamable>().Where(o => nameMap.ContainsKey(NameWithoutDotSuffix(o.Name))).ToList())
28+
{
29+
if (!((SyntaxNode)node).HasAncestor<StructDefinitionSyntaxNode>())
30+
node.Rename(nameMap[NameWithoutDotSuffix(node.Name)]);
31+
}
32+
}
33+
34+
private static string NameWithoutDotSuffix(string name) => name.Split('.').First();
35+
36+
private static Dictionary<string, string> BuildGolfRenameMap(SyntaxNode rootNode)
37+
{
38+
var originalNames = rootNode.FindUserDefinedNames();
39+
var nameMap = originalNames.ToDictionary(o => o, o => o);
40+
41+
foreach (var kvp in nameMap.Where(o => o.Value.Length > 1))
42+
{
43+
// Try to abbreviate to one letter.
44+
var candidate = $"{kvp.Value.First()}";
45+
46+
const string Letters = "abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
47+
if (nameMap.ContainsValue(candidate))
48+
{
49+
// Name is used - Assign unused single-letter.
50+
var ch = Letters.FirstOrDefault(o => !nameMap.ContainsValue($"{o}"));
51+
if (ch != 0)
52+
candidate = $"{ch}";
53+
}
54+
55+
if (nameMap.ContainsValue(candidate))
56+
{
57+
// Name is used - Concatenate first and last characters.
58+
candidate = $"{kvp.Value.First()}{kvp.Value.Last()}";
59+
}
60+
61+
if (nameMap.ContainsValue(candidate))
62+
{
63+
// Name is used - Try upper case.
64+
candidate = candidate.ToUpper();
65+
}
66+
67+
if (nameMap.ContainsValue(candidate))
68+
{
69+
// Name is used - Try lower case.
70+
candidate = candidate.ToLower();
71+
}
72+
73+
var n = 0;
74+
while (nameMap.ContainsValue(candidate))
75+
{
76+
// Name is used - Try letter and number.
77+
foreach (var ch in Letters)
78+
{
79+
candidate = $"{ch}{n}";
80+
if (!nameMap.ContainsValue(candidate))
81+
break;
82+
}
83+
84+
n++;
85+
}
86+
87+
// Update the map.
88+
nameMap[kvp.Key] = $"{candidate}";
89+
}
90+
91+
return nameMap;
92+
}
93+
}
94+
}

ShaderShrinker/Shrinker.Parser/Shrinker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public static SyntaxNode Simplify(this SyntaxNode rootNode, CustomOptions option
107107
repeatSimplifications |= rootNode.MoveConstantParametersIntoCalledFunctions();
108108
}
109109

110+
if (options.GolfNames)
111+
rootNode.GolfNames();
112+
110113
return rootNode;
111114
}
112115
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionCallSyntaxNode.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ namespace Shrinker.Parser.SyntaxNodes
1717
/// <summary>
1818
/// Represents a user-defined function.
1919
/// </summary>
20-
public class FunctionCallSyntaxNode : SyntaxNode
20+
public class FunctionCallSyntaxNode : SyntaxNode, IRenamable
2121
{
22-
public string Name { get; }
22+
public string Name { get; private set; }
23+
2324
public RoundBracketSyntaxNode Params => (RoundBracketSyntaxNode)Children[0];
2425

2526
public FunctionCallSyntaxNode(GenericSyntaxNode nameNode, RoundBracketSyntaxNode brackets) : this(nameNode?.Token?.Content)
@@ -45,5 +46,10 @@ private FunctionCallSyntaxNode(string name)
4546
/// </summary>
4647
public FunctionDefinitionSyntaxNode GetCallee() =>
4748
this.Root().FunctionDefinitions().FirstOrDefault(o => o.Name == Name && Params.GetCsv().Count() == o.Params.GetCsv().Count());
49+
50+
public void Rename(string newName)
51+
{
52+
Name = newName;
53+
}
4854
}
4955
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionSyntaxNodeBase.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace Shrinker.Parser.SyntaxNodes
1717
{
18-
public abstract class FunctionSyntaxNodeBase : SyntaxNode
18+
public abstract class FunctionSyntaxNodeBase : SyntaxNode, IRenamable
1919
{
2020
public string ReturnType { get; protected set; }
2121
public string Name => Children[0].Token.Content;
@@ -37,5 +37,7 @@ public List<GenericSyntaxNode> ParamNames
3737
}
3838

3939
public bool IsMain() => Name.StartsWith("main");
40+
41+
public void Rename(string newName) => Children[0].ReplaceWith(new GenericSyntaxNode(newName));
4042
}
4143
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/GenericSyntaxNode.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Shrinker.Parser.SyntaxNodes
1616
{
17-
public class GenericSyntaxNode : SyntaxNode
17+
public class GenericSyntaxNode : SyntaxNode, IRenamable
1818
{
1919
public GenericSyntaxNode(IToken token) : base(token)
2020
{
@@ -31,14 +31,13 @@ public GenericSyntaxNode(string s) : base(new AlphaNumToken(s))
3131
/// </summary>
3232
public VariableDeclarationSyntaxNode FindVarDeclaration()
3333
{
34-
var varName = Token.Content.Split('.').First();
3534
var node = (SyntaxNode)this;
3635

3736
while (node != null)
3837
{
3938
var decl = node.Parent?.Children
4039
.OfType<VariableDeclarationSyntaxNode>()
41-
.FirstOrDefault(o => o.IsDeclared(varName));
40+
.FirstOrDefault(o => o.IsDeclared(Name));
4241
if (decl != null)
4342
return decl;
4443

@@ -52,5 +51,18 @@ public VariableDeclarationSyntaxNode FindVarDeclaration()
5251
public bool IsVarName(string varName) => this.HasNodeContent(varName);
5352

5453
protected override SyntaxNode CreateSelf() => new GenericSyntaxNode(Token.Clone());
54+
55+
public string Name => Token.Content.Split('.').First();
56+
57+
public void Rename(string newName)
58+
{
59+
if (Token.Content.Contains('.'))
60+
{
61+
// Looks like a variable name referencing a named element. (E.g. foo.x) - Rename the prefix only.
62+
newName += Token.Content.Substring(Name.Length);
63+
}
64+
65+
Token = new AlphaNumToken(newName);
66+
}
5567
}
5668
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="IRenamable.cs">
3+
// Copyright (c) 2021 Dean Edis. (Twitter: @deanthecoder) All rights reserved.
4+
// </copyright>
5+
// <summary>
6+
// This code is provided on an "as is" basis and without warranty of any kind.
7+
// We do not warrant or make any representations regarding the use or
8+
// results of use of this code.
9+
// </summary>
10+
// -----------------------------------------------------------------------
11+
12+
namespace Shrinker.Parser.SyntaxNodes;
13+
14+
public interface IRenamable
15+
{
16+
string Name { get; }
17+
18+
void Rename(string newName);
19+
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/SyntaxNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public IEnumerable<SyntaxNode> SelfAndNextSiblings
6969

7070
public IEnumerable<SyntaxNode> NextSiblings => SelfAndNextSiblings.Skip(1);
7171

72-
public IToken Token { get; }
72+
public IToken Token { get; protected set; }
7373

7474
public IReadOnlyList<SyntaxNode> Children => m_children;
7575

0 commit comments

Comments
 (0)