Skip to content

Commit

Permalink
Add digit separators to long numbers in numeric tooltip (compiler-exp…
Browse files Browse the repository at this point in the history
…lorer#6221)

Long numbers are sometimes hard to read in the numeric tooltip. To
improve readability, this PR adds language-specific digit separators to
the numeric tooltip.

Decimal numbers are grouped into chunks of three digits while
hexadecimal numbers are grouped into chunks of length four.

The digit separator is language-specific and chosen so that the number
is a valid token in the source language.

[Examples](https://godbolt.org/z/s86cMbjeK):
* for C++, hovering the number `8583909746840200552` shows this tooltip:
`8'583'909'746'840'200'552 = 0x7720'2C6F'6C6C'6568 =
6.5188685003648344e+265`
* for Python, hovering the number `-12345678` shows this tooltip:
`-123_456_789 = 0xFFFF_FFFF_F8A4_32EB = -2.66427945e+34f`

For languages that don’t have a `digitSeparator` set, the tooltip is not
changed.
  • Loading branch information
narpfel authored Mar 6, 2024
1 parent b658e25 commit 10c6074
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 8 deletions.
29 changes: 28 additions & 1 deletion lib/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type DefKeys =
| 'logoUrl'
| 'logoUrlDark'
| 'monacoDisassembly'
| 'tooltip';
| 'tooltip'
| 'digitSeparator';
type LanguageDefinition = Pick<Language, DefKeys>;

const definitions: Record<LanguageKey, LanguageDefinition> = {
Expand All @@ -63,6 +64,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: 'clangformat',
previewFilter: /^\s*#include/,
monacoDisassembly: null,
digitSeparator: "'",
},
ada: {
name: 'Ada',
Expand Down Expand Up @@ -97,6 +99,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
'android-kotlin': {
name: 'Android Kotlin',
Expand All @@ -108,6 +111,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
assembly: {
name: 'Assembly',
Expand All @@ -130,6 +134,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: 'clangformat',
previewFilter: /^\s*#include/,
monacoDisassembly: null,
digitSeparator: "'",
},
c3: {
name: 'C3',
Expand Down Expand Up @@ -163,6 +168,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
logoUrlDark: null,
formatter: null,
monacoDisassembly: null,
digitSeparator: "'",
},
circt: {
name: 'CIRCT',
Expand Down Expand Up @@ -229,6 +235,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: "'",
},
mlir: {
name: 'MLIR',
Expand All @@ -251,6 +258,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: /^\s*#include/,
monacoDisassembly: null,
digitSeparator: "'",
},
cppx_blue: {
name: 'Cppx-Blue',
Expand All @@ -273,6 +281,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: "'",
},
cpp2_cppfront: {
name: 'Cpp2-cppfront',
Expand All @@ -284,6 +293,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: 'cppp',
digitSeparator: "'",
},
crystal: {
name: 'Crystal',
Expand All @@ -295,6 +305,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
csharp: {
name: 'C#',
Expand All @@ -306,6 +317,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
cuda: {
name: 'CUDA C++',
Expand All @@ -317,6 +329,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: "'",
},
d: {
name: 'D',
Expand Down Expand Up @@ -394,6 +407,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
haskell: {
name: 'Haskell',
Expand All @@ -405,6 +419,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
hlsl: {
name: 'HLSL',
Expand Down Expand Up @@ -460,6 +475,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
julia: {
name: 'Julia',
Expand All @@ -471,6 +487,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
kotlin: {
name: 'Kotlin',
Expand All @@ -482,6 +499,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
llvm: {
name: 'LLVM IR',
Expand Down Expand Up @@ -548,6 +566,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: "'",
},
ocaml: {
name: 'OCaml',
Expand Down Expand Up @@ -603,6 +622,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
racket: {
name: 'Racket',
Expand All @@ -625,6 +645,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: 'asmruby',
digitSeparator: '_',
},
rust: {
name: 'Rust',
Expand All @@ -636,6 +657,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: 'rustfmt',
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
snowball: {
name: 'Snowball',
Expand All @@ -658,6 +680,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
solidity: {
name: 'Solidity',
Expand Down Expand Up @@ -691,6 +714,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
tablegen: {
name: 'LLVM TableGen',
Expand Down Expand Up @@ -724,6 +748,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
v: {
name: 'V',
Expand Down Expand Up @@ -768,6 +793,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
javascript: {
name: 'Javascript',
Expand All @@ -779,6 +805,7 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
formatter: null,
previewFilter: null,
monacoDisassembly: null,
digitSeparator: '_',
},
gimple: {
name: 'GIMPLE',
Expand Down
23 changes: 23 additions & 0 deletions shared/common-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,26 @@ const EscapeRE = new RegExp(`(?:${Object.keys(EscapeMap).join('|')})`, 'g');
export function escapeHTML(text: string) {
return text.replace(EscapeRE, str => EscapeMap[str]);
}

function splitIntoChunks(s: string, chunkSize: number): string[] {
const chunks: string[] = [];
const isNegative = s.slice(0, 1) === '-';
if (isNegative) {
s = s.slice(1);
}
const firstChunkLength = s.length % chunkSize;
if (firstChunkLength !== 0) {
chunks.push(s.slice(0, firstChunkLength));
}
for (let i = firstChunkLength; i < s.length; i += chunkSize) {
chunks.push(s.slice(i, i + chunkSize));
}
if (isNegative) {
chunks[0] = '-' + (chunks[0] ?? '');
}
return chunks;
}

export function addDigitSeparator(n: string, digitSeparator: string, chunkSize: number): string {
return splitIntoChunks(n, chunkSize).join(digitSeparator);
}
22 changes: 16 additions & 6 deletions static/panes/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import {CompilerShared} from '../compiler-shared.js';
import {SentryCapture} from '../sentry.js';
import {LLVMIrBackendOptions} from '../compilation/ir.interfaces.js';
import {InstructionSet} from '../instructionsets.js';
import {escapeHTML} from '../../shared/common-utils.js';
import {addDigitSeparator, escapeHTML} from '../../shared/common-utils.js';
import {CompilerVersionInfo, setCompilerVersionPopoverForPane} from '../widgets/compiler-version-info.js';

const toolIcons = require.context('../../views/resources/logos', false, /\.(png|svg)$/);
Expand Down Expand Up @@ -3496,7 +3496,15 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
return null;
}

public static getNumericToolTip(value: string) {
public static getNumericToolTip(value: string, digitSeparator?: string) {
const formatNumber = (number, base, chunkSize) => {
const numberString = number.toString(base).toUpperCase();
if (digitSeparator !== undefined) {
return addDigitSeparator(numberString, digitSeparator, chunkSize);
} else {
return numberString;
}
};
const numericValue = this.parseNumericValue(value);
if (numericValue === null) return null;

Expand All @@ -3507,14 +3515,14 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
if (this.ptxFloat64.test(value)) return view.getFloat64(0, true).toPrecision(17);

// Decimal representation.
let result = numericValue.toString(10);
let result = formatNumber(numericValue, 10, 3);

// Hexadecimal representation.
if (numericValue.isNegative()) {
const masked = bigInt('ffffffffffffffff', 16).and(numericValue);
result += ' = 0x' + masked.toString(16).toUpperCase();
result += ' = 0x' + formatNumber(masked, 16, 4);
} else {
result += ' = 0x' + numericValue.toString(16).toUpperCase();
result += ' = 0x' + formatNumber(numericValue, 16, 4);
}

// Float32/64 representation.
Expand Down Expand Up @@ -3627,7 +3635,9 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
e.target.position.lineNumber,
currentWord.endColumn,
);
const numericToolTip = Compiler.getNumericToolTip(word);
const lang = this.compiler?.lang;
const language = lang === undefined ? undefined : languages[lang];
const numericToolTip = Compiler.getNumericToolTip(word, language?.digitSeparator);
if (numericToolTip) {
this.decorations.numericToolTip = [
{
Expand Down
23 changes: 22 additions & 1 deletion test/common-utils-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import {escapeHTML} from '../shared/common-utils.js';
import {addDigitSeparator, escapeHTML} from '../shared/common-utils.js';

describe('HTML Escape Test Cases', () => {
it('should prevent basic injection', () => {
Expand All @@ -32,3 +32,24 @@ describe('HTML Escape Test Cases', () => {
escapeHTML('\'"`>').should.equal(`&#x27;&quot;&#x60;&gt;`);
});
});

describe('digit separator', () => {
it('handles short numbers', () => {
addDigitSeparator('42', '_', 3).should.equal('42');
});
it('handles long numbers', () => {
addDigitSeparator('1234', '_', 3).should.equal('1_234');
addDigitSeparator('123456789', "'", 3).should.equal("123'456'789");
addDigitSeparator('1234567890', "'", 3).should.equal("1'234'567'890");
});
it('handles hex numbers', () => {
addDigitSeparator('AABBCCDD12345678', '_', 4).should.equal('AABB_CCDD_1234_5678');
addDigitSeparator('01AABBCCDD12345678', '_', 4).should.equal('01_AABB_CCDD_1234_5678');
});
it('handles negative numbers', () => {
addDigitSeparator('-42', '_', 3).should.equal('-42');
addDigitSeparator('-420', '_', 3).should.equal('-420');
addDigitSeparator('-4200', '_', 3).should.equal('-4_200');
addDigitSeparator('-123456789', '_', 3).should.equal('-123_456_789');
});
});
1 change: 1 addition & 0 deletions types/languages.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,5 @@ export interface Language {
tooltip?: string;
/** Default compiler for the language. This is populated when handed to the frontend. */
defaultCompiler?: string;
digitSeparator?: string;
}

0 comments on commit 10c6074

Please sign in to comment.