New Language: V (#5297)

V (`vlang`) is a simple, fast and safe general purpose programming
language compiling to human-readable C.

#### Key features of V:

- simplicity, "only one way to do things"
- performance as fast as C
- safety: no null, no globals, no undefined behavior, immutability
- automatic C to V translation, good C interoperability
- hot code reloading
- flexible memory management (GC by default, manual, arena allocation,
autofree)
- other compilation backends like JavaScript, `native` (wip), `wasm`
(wip) or interpreted

#### Links:

Source code: https://github.com/vlang/v
Official website: https://vlang.io
Web playground: https://play.vlang.io/

infra PR: https://github.com/compiler-explorer/infra/pull/1058

#### Things this PR adds:

- General support for V
- Code inspection for the C, Go and JavaScript backends
- Support for the v formatter `v fmt`
- Some V example code

#### Things not implemented:

- Support for binary-output backends like `native` and `wasm`
- Support for running programs

<!-- THIS COMMENT IS INVISIBLE IN THE FINAL PR, BUT FEEL FREE TO REMOVE
IT
Thanks for taking the time to improve CE. We really appreciate it.
Before opening the PR, please make sure that the tests & linter pass
their checks,
  by running `make check`.
In the best case scenario, you are also adding tests to back up your
changes,
  but don't sweat it if you don't. We can discuss them at a later date.
Feel free to append your name to the CONTRIBUTORS.md file
Thanks again, we really appreciate this!
-->

---------

Co-authored-by: Matt Godbolt <matt@godbolt.org>
This commit is contained in:
Spydr
2023-08-16 05:16:36 +02:00
committed by GitHub
parent ed6ea09811
commit 0bf769e245
18 changed files with 596 additions and 1 deletions

4
.github/labeler.yml vendored
View File

@@ -181,6 +181,10 @@ lang-toit:
lang-typescript:
- lib/compilers/typescript.js
- etc/config/typescript.*.properties
lang-v:
- lib/compilers/v.ts
- etc/config/v.*.properties
- static/modes/v-mode.ts
lang-vala:
- lib/compilers/vala.ts
- etc/config/vala.*.properties

View File

@@ -133,4 +133,5 @@ From oldest to newest contributor, we would like to thank:
- [Vlad Serebrennikov](https://github.com/endilll)
- [Mauro Baladés](https://github.com/mauro-balades)
- [Jorge López](https://github.com/jolopezl)
- [Spydr06](https://github.com/spydr06)
- [Simon Sobisch](https://github.com/GitMensch)

View File

@@ -41,7 +41,7 @@ useninja=false
ld=/usr/bin/ld
readelf=/usr/bin/readelf
formatters=clangformat:rustfmt:gofmt:dartformat
formatters=clangformat:rustfmt:gofmt:dartformat:vfmt
formatter.clangformat.name=clang-format
formatter.clangformat.exe=/opt/compiler-explorer/clang-trunk/bin/clang-format
formatter.clangformat.styles=Google:LLVM:Mozilla:Chromium:WebKit:Microsoft:GNU
@@ -59,6 +59,10 @@ formatter.dartformat.name=dartformat
formatter.dartformat.exe=/opt/compiler-explorer/dart-2.16.1/bin/dart
formatter.dartformat.styles=
formatter.dartformat.type=dartformat
formatter.vfmt.name=vfmt
formatter.vfmt.exe=/opt/compiler-explorer/v-2023.30/v
formatter.vfmt.styles=
formatter.vfmt.type=vfmt
thirdPartyIntegrationEnabled=true
statusTrackingEnabled=true

View File

@@ -0,0 +1,17 @@
compilers=&v
compilerType=v
versionFlag=--version
objdumper=/opt/compiler-explorer/gcc-12.1.0/bin/objdump
defaultCompiler=v04
externalparser=CEAsmParser
externalparser.exe=/usr/local/bin/asm-parser
supportsBinary=false
supportsExecute=false
group.v.compilers=v04
group.v.groupName=V
group.v.licenseName=MIT License
group.v.licenseLink=https://raw.githubusercontent.com/vlang/v/master/LICENSE
compiler.v04.exe=/opt/compiler-explorer/v-2023.30/v
compiler.v04.name=V 0.4

View File

@@ -0,0 +1,15 @@
# Default settings for V
objdumper=objdump
supportsBinary=false
supportsExecute=false
versionFlag=--version
compilerType=v
compilers=v
defaultCompiler=v
compiler.v.exe=/usr/local/bin/v
compiler.v.name=V
libs=

View File

@@ -0,0 +1,12 @@
import arrays
fn sum_array(array []int) !int {
return arrays.reduce(array, fn (acc int, i int) int {
return acc + i
})
}
fn main() {
a := [1, 2, 3, 4, 5]
println(sum_array(a) or { 0 })
}

7
examples/v/default.v Normal file
View File

@@ -0,0 +1,7 @@
fn square(num int) int {
return num * num
}
fn main() {
println(square(3))
}

3
examples/v/helloworld.v Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println('Hello, World!')
}

View File

@@ -109,6 +109,7 @@ export {TinyCCompiler} from './tinyc.js';
export {ToitCompiler} from './toit.js';
export {TurboCCompiler} from './turboc.js';
export {TypeScriptNativeCompiler} from './typescript-native.js';
export {VCompiler} from './v.js';
export {ValaCompiler} from './vala.js';
export {VBCompiler} from './dotnet.js';
export {V8Compiler} from './v8.js';

211
lib/compilers/v.ts Normal file
View File

@@ -0,0 +1,211 @@
// Copyright (c) 2023, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import path from 'path';
import {unwrap} from '../assert.js';
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
import {BaseCompiler} from '../base-compiler.js';
import { ExecutionOptions } from '../../types/compilation/compilation.interfaces.js';
const V_DEFAULT_BACKEND = 'c';
export class VCompiler extends BaseCompiler {
outputFileExt = `.${V_DEFAULT_BACKEND}`;
static get key() {
return 'v';
}
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions?: string[]) {
const options = unwrap(userOptions);
if (options) {
if (options.includes('-h') || options.includes('--help')) {
return [];
}
const backend = this.getBackendFromOptions(options);
const outputFileExt = this.getFileExtForBackend(backend);
if (outputFileExt !== undefined) {
this.outputFileExt = outputFileExt;
}
}
const compilerOptions: string[] = [];
if (!filters.binary) {
compilerOptions.push('-o');
compilerOptions.push(this.filename(this.patchOutputFilename(outputFilename)));
}
if (!filters.labels) {
compilerOptions.push('-skip-unused');
}
return compilerOptions;
}
override async processAsm(result: any, filters, options: string[]): Promise<any> {
const backend = this.getBackendFromOptions(options);
switch (backend) {
case 'c':
case 'js':
case 'js_node':
case 'js_browser':
case 'js_freestanding':
case 'go':
return this.processCLike(result, filters);
default:
return this.asm.process(result.asm, filters);
}
}
override getSharedLibraryPathsAsArguments(libraries, libDownloadPath) {
return [];
}
override getSharedLibraryLinks(libraries: any[]): string[] {
return [];
}
override getOutputFilename(dirPath: string, outputFilebase: string, key?: any): string {
return path.join(dirPath, 'output' + this.outputFileExt);
}
override getDefaultExecOptions(): ExecutionOptions & { env: Record<string, string>; } {
const options = super.getDefaultExecOptions();
options.env['VMODULES'] = path.join(path.dirname(this.compiler.exe), '.vmodules');
return options;
}
getBackendFromOptions(options: string[]): string {
const backendOpt = options.indexOf('-b');
if (backendOpt >= 0 && options[backendOpt + 1]) return options[backendOpt + 1].toLowerCase();
if (options.includes('-native')) return 'native';
if (options.includes('-interpret')) return 'interpret';
return V_DEFAULT_BACKEND; // default V backend
}
getFileExtForBackend(backend: string): string | undefined {
switch (backend) {
case 'c':
case 'go':
case 'wasm':
return '.' + backend;
case 'js':
case 'js_node':
case 'js_browser':
case 'js_freestanding':
return '.js';
case 'native':
return '';
default:
return undefined;
}
}
patchOutputFilename(outputFilename: string): string {
const parts = outputFilename.split('.');
if (this.outputFileExt === '') {
parts.pop();
return parts.join('.');
}
parts[parts.length - 1] = this.outputFileExt.split('.')[1];
return parts.join('.');
}
removeUnusedLabels(input: string[]): string[] {
const output: string[] = [];
const lineRe = /^.*main__.*$/;
const mainFunctionCall = '\tmain__main();';
let scopeDepth = 0;
let insertNewLine = false;
for (const lineNo in input) {
const line = input[lineNo];
if (!line) continue;
if (insertNewLine) {
output.push('');
insertNewLine = false;
}
if ((scopeDepth === 0 && line.match(lineRe) && line !== mainFunctionCall) || scopeDepth > 0) {
const opening = (line.match(/{/g) || []).length - 1;
const closing = (line.match(/}/g) || []).length - 1;
scopeDepth += opening - closing;
output.push(line);
insertNewLine = scopeDepth === 0;
}
}
return output;
}
removeWhitespaceLines = (input: string[]) => input.map(line => line.trimStart()).filter(line => line !== '');
removeComments = (input: string[]) =>
input
.filter(line => !line.trimStart().startsWith('//'))
.map(line => line.split('//')[0].replaceAll(/(\/\*).*?(\*\/)/g, ''));
removeDirectives = (input: string[]) => input.filter(line => !line.trimStart().startsWith('#'));
async processCLike(result, filters): Promise<any> {
let lines = result.asm.split('\n');
// remove non-user defined code
if (!filters.labels) lines = this.removeUnusedLabels(lines);
// remove comments
if (!filters.commentOnly) lines = this.removeComments(lines);
// remove whitespace
if (filters.trim) lines = this.removeWhitespaceLines(lines);
// remove preprocessor directives
if (!filters.directives) lines = this.removeDirectives(lines);
// finally, remove unnecessary newlines to make the output nicer
const finalLines: string[] = [];
let emptyLineEncountered = false;
for (const lineNo in lines) {
const line = lines[lineNo];
if (line.trimStart() === '') {
if (emptyLineEncountered) continue;
emptyLineEncountered = true;
} else emptyLineEncountered = false;
finalLines.push(line);
}
return {asm: finalLines.map(line => ({text: line}))};
}
}

View File

@@ -26,3 +26,4 @@ export {ClangFormatFormatter} from './clang-format.js';
export {DartFormatFormatter} from './dartformat.js';
export {GoFmtFormatter} from './gofmt.js';
export {RustFmtFormatter} from './rustfmt.js';
export {VFmtFormatter} from './vfmt.js';

52
lib/formatters/vfmt.ts Normal file
View File

@@ -0,0 +1,52 @@
// Copyright (c) 2023, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {UnprocessedExecResult} from '../../types/execution/execution.interfaces.js';
import {FormatOptions} from './base.interfaces.js';
import {BaseFormatter} from './base.js';
import * as exec from '../exec.js';
export class VFmtFormatter extends BaseFormatter {
static get key() {
return 'vfmt';
}
/**
* Format the provided source code
*
* This function does not use any options, because v fmt does not have any
* options except the default provided ones (v fmt -w <file.v>).
*/
override async format(source: string, options: FormatOptions): Promise<UnprocessedExecResult> {
return await exec.execute(this.formatterInfo.exe, ['fmt', '-w'], {input: source});
}
/**
* v fmt has no styling options
*/
override isValidStyle(style: string): boolean {
return true;
}
}

View File

@@ -648,6 +648,17 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
previewFilter: null,
monacoDisassembly: null,
},
v: {
name: 'V',
monaco: 'v',
extensions: ['.v', '.vsh'],
alias: [],
logoUrl: 'v.svg',
logoUrlDark: null,
formatter: 'vfmt',
previewFilter: null,
monacoDisassembly: 'nc',
},
vala: {
name: 'Vala',
monaco: 'vala',

View File

@@ -104,3 +104,4 @@ register('nc', 'clangformat', false);
register('go', 'gofmt', true);
register('rust', 'rustfmt', true);
register('dart', 'dartformat', true);
register('v', 'vfmt', true);

View File

@@ -57,5 +57,6 @@ import './ocaml-mode';
import './openclc-mode';
import './ptx-mode';
import './spirv-mode';
import './v-mode';
import './vala-mode';
import './zig-mode';

252
static/modes/v-mode.ts Normal file
View File

@@ -0,0 +1,252 @@
// Copyright (c) 2023, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import * as monaco from 'monaco-editor';
function definition(): monaco.languages.IMonarchLanguage {
return {
defaultToken: 'invalid',
keywords: [
'__global',
'_likely_',
'_unlikely_',
'as',
'asm',
'assert',
'atomic',
'break',
'const',
'continue',
'defer',
'dump',
'else',
'enum',
'false',
'fn',
'for',
'go',
'goto',
'if',
'import',
'in',
'interface',
'is',
'isreftype',
'it',
'like',
'lock',
'match',
'module',
'mut',
'nil',
'none',
'offsetof',
'or',
'pub',
'return',
'rlock',
'select',
'shared',
'sizeof',
'spawn',
'static',
'struct',
'true',
'type',
'typeof',
'union',
'unsafe',
'volatile',
],
typeKeywords: [
'i8',
'u8',
'i16',
'u16',
'int',
'u32',
'i64',
'u64',
'f32',
'f64',
'string',
'map',
'struct',
'bool',
'voidptr',
'charptr',
'isize',
'usize',
],
operators: [
'+',
'-',
'*',
'/',
'%',
'^',
'~',
'|',
'#',
'&',
'++',
'--',
'&&',
'||',
'!',
'.',
'!in',
'!is',
';',
':',
'<-',
'=',
':=',
'+=',
'-=',
'*=',
'/=',
'%=',
'|=',
'&=',
'>>=',
'>>>=',
'<<=',
'==',
'!=',
'>',
'<',
'>=',
'<=',
'?',
'<<',
'>>',
'>>>',
'$',
],
symbols: /[=><!~?:&|+\-*/^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
tokenizer: {
root: [
// identifiers and keywords
[
/[a-z_$][\w$]*/,
{
cases: {
'@typeKeywords': 'keyword',
'@keywords': 'keyword',
'@default': 'identifier',
},
},
],
[/@[a-zA-Z_$]*/, 'builtin.identifier'],
[/[A-Z][\w$]*/, 'type.identifier'], // to show class names nicely
// whitespace
{include: '@whitespace'},
// delimiters and operators
[/[{}()[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[
/@symbols/,
{
cases: {
'@operators': 'operator',
'@default': '',
},
},
],
// numbers
[/[0-9_]*\.[0-9_]+([eE][-+]?[0-9_]+)?/, 'number.float'],
[/0[xX][0-9a-fA-F_]*[0-9a-fA-F_]/, 'number.hex'],
[/0o[0-7_]*[0-7_]/, 'number.octal'],
[/0[bB][0-1_]*[0-1_]/, 'number.binary'],
[/[0-9_]+/, 'number'],
// delimiter: after number because of .\d floats
[/[;,.]/, 'delimiter'],
// single-quoted strings
[/'([^'\\]|\\.)*$/, 'string.invalid'],
[/c?\\\\.*$/, 'string'],
[/c?'/, 'string', '@single_quoted_string'],
// double-quoted strings
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
[/c?\\\\.*$/, 'string'],
[/c?"/, 'string', '@double_quoted_string'],
// runes
[/`[^\\`]`/, 'string'],
[/(`)(@escapes)(`)/, ['string', 'string.escape', 'string']],
[/`/, 'string.invalid'],
],
whitespace: [
[/[ \r\n]+/, 'white'],
[/\/\*/, 'comment', '@comment'],
[/\/\+/, 'comment', '@comment'],
[/\/\/.*$/, 'comment'],
[/\t/, 'comment.invalid'],
],
comment: [
[/[^/*]+/, 'comment'],
[/\/\*/, 'comment', '@comment'],
[/\*\//, 'comment', '@pop'],
[/[/*]/, 'comment'],
],
single_quoted_string: [
[/[^\\']+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/'/, 'string', '@pop'],
],
double_quoted_string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@pop'],
],
},
};
}
const def = definition();
monaco.languages.register({id: 'v'});
monaco.languages.setMonarchTokensProvider('v', def);
export = def;

View File

@@ -79,6 +79,7 @@ export type LanguageKey =
| 'swift'
| 'toit'
| 'typescript'
| 'v'
| 'vala'
| 'vb'
| 'zig';

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 500 500" width="500px" height="500px"><defs><clipPath id="_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4"><rect width="500" height="500"/></clipPath></defs><g clip-path="url(#_clipPath_8TWIgR1z3pxinjWBiigzcEIrVJKv9Gq4)"><path d=" M 318.422 453.543 L 463.705 49.541 C 466.168 42.689 462.285 37.693 455.037 38.392 L 340.786 49.398 C 333.539 50.097 325.71 56.246 323.316 63.121 L 188.843 449.216 C 186.447 456.091 190.414 461.673 197.695 461.673 L 308.901 461.673 C 312.541 461.673 316.497 458.893 317.729 455.466 L 318.422 453.543 Z " fill="rgb(83,107,138)"/><defs><filter id="Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y" x="-200%" y="-200%" width="400%" height="400%" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feGaussianBlur xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" stdDeviation="6.440413594258542"/><feOffset xmlns="http://www.w3.org/2000/svg" dx="0" dy="0" result="pf_100_offsetBlur"/><feFlood xmlns="http://www.w3.org/2000/svg" flood-color="#000000" flood-opacity="0.65"/><feComposite xmlns="http://www.w3.org/2000/svg" in2="pf_100_offsetBlur" operator="in" result="pf_100_dropShadow"/><feBlend xmlns="http://www.w3.org/2000/svg" in="SourceGraphic" in2="pf_100_dropShadow" mode="normal"/></filter></defs><g filter="url(#Hmac7mZraFWHw0G84Yxj4QuzeTFp0E7Y)"><path d=" M 301.848 455.466 L 241.359 280.725 L 250 275.324 L 311.57 453.543 L 301.848 455.466 Z " fill="rgb(235,235,235)"/></g><path d=" M 44.963 38.392 L 159.214 49.398 C 166.461 50.097 174.298 56.243 176.704 63.115 L 314.022 455.448 C 315.224 458.885 313.245 461.673 309.604 461.673 L 197.695 461.673 C 190.414 461.673 182.502 456.111 180.038 449.259 L 36.295 49.541 C 33.832 42.689 37.715 37.693 44.963 38.392 Z " fill="rgb(93,135,191)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB