6502bench/Asm65/Label.cs

110 lines
4.6 KiB
C#

/*
* Copyright 2018 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Text.RegularExpressions;
namespace Asm65 {
/// <summary>
/// Utility classes for working with labels.
///
/// The decision of whether to treat labels as case-sensitive or case-insensitive is
/// encapsulated here. All code should be case-preserving, but the comparison method
/// and "normal form" are defined here.
/// </summary>
public static class Label {
// Arbitrary choice for SourceGen. Different assemblers have different limits.
public const int MAX_LABEL_LEN = 32;
public const bool LABELS_CASE_SENSITIVE = true;
/// <summary>
/// String comparer to use when comparing labels.
/// </summary>
/// <remarks>
/// Usage:
/// if (Label.LABEL_COMPARER.Equals(label1, label2)) { ... }
///
/// We may want case-insensitive string compares, and we want the "invariant culture"
/// version for consistent results across users in multiple locales. (The labels are
/// expected to be ASCII strings, so the latter isn't crucial unless we change the
/// allowed set.)
/// </remarks>
public static readonly StringComparer LABEL_COMPARER = LABELS_CASE_SENSITIVE ?
StringComparer.InvariantCulture :
StringComparer.InvariantCultureIgnoreCase;
/// <summary>
/// Regex pattern for a valid label.
///
/// ASCII-only, starts with letter or underscore, followed by at least
/// one alphanumeric or underscore. Some assemblers may allow single-letter
/// labels, but I don't want to risk confusion with A/S/X/Y. So either we
/// reserve those, or we just mandate a two-character minimum.
/// </summary>
private static string sValidLabelPattern = @"^[a-zA-Z_][a-zA-Z0-9_]+$";
private static Regex sValidLabelCharRegex = new Regex(sValidLabelPattern);
/// <summary>
/// Validates a label, confirming that it is correctly formed.
/// </summary>
/// <param name="label">Label to validate.</param>
/// <returns>True if the label is correctly formed.</returns>
public static bool ValidateLabel(string label) {
if (label == null || label.Length > MAX_LABEL_LEN) {
return false;
}
MatchCollection matches = sValidLabelCharRegex.Matches(label);
return matches.Count == 1;
}
/// <summary>
/// Performs a detailed validation of a symbol label, breaking out different failure
/// causes for the benefit of code that reports errors to the user.
/// </summary>
/// <param name="label">Label to examine.</param>
/// <param name="isLenValid">True if the label has a valid length.</param>
/// <param name="isFirstCharValid">True if the first character is valid.</param>
/// <returns>True if the label is valid.</returns>
public static bool ValidateLabelDetail(string label, out bool isLenValid,
out bool isFirstCharValid) {
bool isValid = ValidateLabel(label);
if (isValid) {
isLenValid = isFirstCharValid = true;
return true;
}
// Something is wrong. Check length.
isLenValid = (label.Length >= 2 && label.Length <= MAX_LABEL_LEN);
// Check first char for alphanumeric or underscore.
isFirstCharValid = label.Length > 0 &&
((label[0] >= 'A' && label[0] <= 'Z') || (label[0] >= 'a' && label[0] <= 'z') ||
label[0] == '_');
return isValid;
}
/// <summary>
/// Returns "normal form" of label. This matches LABEL_COMPARER behavior.
/// </summary>
/// <param name="label">Label to transform.</param>
/// <returns>Transformed label.</returns>
public static string ToNormal(string label) {
return LABELS_CASE_SENSITIVE ? label : label.ToUpperInvariant();
}
}
}