comptime value-params

This commit is contained in:
2026-07-02 19:38:31 +02:00
parent b1ddecfc2e
commit 7cda126924
10 changed files with 579 additions and 56 deletions
+1
View File
@@ -123,6 +123,7 @@ Param :: struct {
name: symbol.Id,
span: source.Span,
type: Type_Syntax,
comptime_value: bool,
}
Stmt_Kind :: enum u8 {
+359 -51
View File
@@ -28,10 +28,17 @@ spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) {
Spec :: struct {
template: ast.Function_Id,
args: []types.Type,
comptime_values: []Comptime_Value,
result: types.Type,
hir_id: hir.Function_Id,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
}
Infer_Local :: struct {
name: symbol.Id,
type: types.Type,
@@ -169,6 +176,7 @@ Checker :: struct {
sink_symbol: symbol.Id,
current_result: types.Type,
current_build_ctx: ^Build_Ctx,
current_comptime_values: []Comptime_Value,
target: target.Target,
allocator: mem.Allocator,
}
@@ -187,6 +195,61 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
return types.name(value)
}
find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) {
for index := len(values) - 1; index >= 0; index -= 1 {
if values[index].name == name {
return values[index], true
}
}
return {}, false
}
current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) {
return find_comptime_value(checker.current_comptime_values, name)
}
function_has_comptime_params :: proc(function: ast.Function) -> bool {
for param in function.params {
if param.comptime_value {
return true
}
}
return false
}
runtime_param_count :: proc(function: ast.Function) -> int {
count := 0
for param in function.params {
if !param.comptime_value {
count += 1
}
}
return count
}
comptime_param_count :: proc(function: ast.Function) -> int {
count := 0
for param in function.params {
if param.comptime_value {
count += 1
}
}
return count
}
comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
if len(left) != len(right) {
return false
}
for value, index in left {
other := right[index]
if value.name != other.name || !types.equal(value.type, other.type) || value.value != other.value {
return false
}
}
return true
}
Constant_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
@@ -311,6 +374,11 @@ eval_integer_constant_in_context :: proc(
case .Integer:
return Constant{kind = .Value, value = i128(expr.integer)}
case .Name:
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
return Constant{kind = .Value, value = value.value}
}
}
target_pkg, available := expr_package(checker, expr, pkg, file, false)
if !available {
return Constant{kind = .Not_Constant}
@@ -767,7 +835,8 @@ function_signatures_equal :: proc(left, right: ast.Function) -> bool {
return false
}
for param, index in left.params {
if param.type != right.params[index].type {
if param.type != right.params[index].type ||
param.comptime_value != right.params[index].comptime_value {
return false
}
}
@@ -792,6 +861,79 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
return declared
}
collect_comptime_values :: proc(
checker: ^Checker,
function: ast.Function,
args: []ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
diagnose := false,
) -> ([]Comptime_Value, bool) {
if !function_has_comptime_params(function) {
return nil, true
}
values: [dynamic]Comptime_Value
values.allocator = checker.allocator
ok := true
for param, index in function.params {
if !param.comptime_value {
continue
}
span := param.span
if index < len(args) && args[index] != ast.INVALID_EXPR && int(args[index]) < len(checker.ast_module.exprs) {
span = checker.ast_module.exprs[args[index]].span
}
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
if !types.is_concrete_integer(declared) {
if diagnose {
source.addf(
checker.diagnostics,
param.span,
"comptime parameter '%s' requires a concrete integer type",
symbol_text(checker, param.name),
)
}
ok = false
continue
}
constant := Constant{kind = .Not_Constant}
if index < len(args) {
constant = eval_integer_constant_in_context(checker, args[index], pkg, file)
}
if constant.kind != .Value {
if diagnose {
source.addf(
checker.diagnostics,
span,
"argument for comptime parameter '%s' must be a compile-time integer expression",
symbol_text(checker, param.name),
)
}
ok = false
continue
}
if !fits_integer_type(constant.value, declared, checker.target) {
if diagnose {
source.addf(
checker.diagnostics,
span,
"integer constant %d does not fit in %s",
constant.value,
types.name(declared),
)
}
ok = false
continue
}
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
}
if !ok {
delete(values)
return nil, false
}
return values[:], true
}
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
if index < 0 || index >= int(function_item.field_count) {
return types.INVALID
@@ -818,6 +960,9 @@ function_value_signature :: proc(
if !function.c_abi || types.is_valid(function.error) {
return nil, types.INVALID, false
}
if function_has_comptime_params(function) {
return nil, types.INVALID, false
}
result = type_from_syntax(checker, function.result, function.pkg, function.file)
if !types.is_void(result) && !is_runtime_type(checker, result) {
return nil, types.INVALID, false
@@ -1027,13 +1172,33 @@ validate_declarations :: proc(checker: ^Checker) {
if len(function.unsupported_reason) > 0 {
continue
}
has_comptime := function_has_comptime_params(function)
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(checker, param.type, function.pkg, function.file));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
continue
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
if param.comptime_value {
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
param.span,
"comptime parameters require 'func', not 'c_func'",
)
}
if !types.is_concrete_integer(param_type) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"comptime parameter '%s' requires a concrete integer type",
symbol_text(checker, param.name),
)
}
} else if !has_comptime {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
continue
}
}
if param.type == types.VOID {
source.add(
@@ -1051,7 +1216,7 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
append(&locals, param.name)
if types.contains_c_struct_by_value(type_from_syntax(checker, param.type, function.pkg, function.file), &checker.module.types) {
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -1060,17 +1225,20 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(checker, function.result, function.pkg, function.file));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
}
if types.contains_c_struct_by_value(type_from_syntax(checker, function.result, function.pkg, function.file), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"C records cannot be returned by value from '%s'",
symbol_text(checker, function.name),
)
if !has_comptime {
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
}
if types.contains_c_struct_by_value(result_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"C records cannot be returned by value from '%s'",
symbol_text(checker, function.name),
)
}
}
if types.is_valid(function.error) {
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
@@ -1298,23 +1466,38 @@ find_infer_local_index :: proc(locals: []Infer_Local, name: symbol.Id) -> (int,
return -1, false
}
find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
find_spec :: proc(
checker: ^Checker,
template: ast.Function_Id,
actual_args: []types.Type,
comptime_values: []Comptime_Value = nil,
) -> Spec_Id {
function := checker.ast_module.functions[template]
for spec, index in checker.specs {
if spec.template != template || len(spec.args) != len(function.params) {
if spec.template != template || len(spec.args) != runtime_param_count(function) ||
!comptime_values_equal(spec.comptime_values, comptime_values) {
continue
}
matches := true
runtime_index := 0
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
for param, param_index in function.params {
if param.comptime_value {
continue
}
actual := types.INVALID
if param_index < len(actual_args) {
actual = actual_args[param_index]
}
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
if runtime_index >= len(spec.args) ||
!types.equal(spec.args[runtime_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
matches = false
break
}
runtime_index += 1
}
checker.current_comptime_values = previous_comptime
if matches {
return spec_id(index)
}
@@ -1340,8 +1523,19 @@ specialized_param_type :: proc(
return declared
}
can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool {
can_specialize :: proc(
checker: ^Checker,
function: ast.Function,
actual_args: []types.Type,
comptime_values: []Comptime_Value = nil,
) -> bool {
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
defer checker.current_comptime_values = previous_comptime
for param, index in function.params {
if param.comptime_value {
continue
}
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
@@ -1353,20 +1547,34 @@ can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: [
return true
}
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
if existing := find_spec(checker, template, actual_args); existing != INVALID_SPEC {
ensure_spec :: proc(
checker: ^Checker,
template: ast.Function_Id,
actual_args: []types.Type,
comptime_values: []Comptime_Value = nil,
) -> Spec_Id {
if existing := find_spec(checker, template, actual_args, comptime_values); existing != INVALID_SPEC {
return existing
}
function := checker.ast_module.functions[template]
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
defer checker.current_comptime_values = previous_comptime
signature: [dynamic]types.Type
signature.allocator = checker.allocator
for param, index in function.params {
if param.comptime_value {
continue
}
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
append(&signature, specialized_param_type(checker, param.type, actual, function.pkg, function.file))
}
comptime_signature: [dynamic]Comptime_Value
comptime_signature.allocator = checker.allocator
append(&comptime_signature, ..comptime_values)
result := function_channel_type(checker, function)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
!types.is_valid(function.error) {
@@ -1375,7 +1583,13 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
index := spec_id(len(checker.specs))
append(
&checker.specs,
Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION},
Spec{
template = template,
args = signature[:],
comptime_values = comptime_signature[:],
result = result,
hir_id = hir.INVALID_FUNCTION,
},
)
return index
}
@@ -1662,6 +1876,13 @@ infer_expr :: proc(
}
}
}
if !types.is_valid(last) {
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
last = value.type
}
}
}
if !types.is_valid(last) {
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
_, member_ok := find_enum_member(checker, enum_type, expr.name)
@@ -1859,13 +2080,16 @@ infer_expr :: proc(
}
}
}
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file)
defer delete(comptime_values, checker.allocator)
if valid_call_arity(function, len(expr.args)) &&
can_specialize(checker, function, stack[frame_index].args) {
comptime_ok &&
can_specialize(checker, function, stack[frame_index].args, comptime_values) {
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
} else {
spec = find_spec(checker, frame.template, stack[frame_index].args)
spec = find_spec(checker, frame.template, stack[frame_index].args, comptime_values)
mark_spec_demanded(checker, spec, demanded)
}
if spec != INVALID_SPEC {
@@ -2252,6 +2476,9 @@ infer_spec_locals_and_result :: proc(
) -> ([]types.Type, types.Type) {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = spec.comptime_values
defer checker.current_comptime_values = previous_comptime
declared := type_from_syntax(checker, function.result, function.pkg, function.file)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
declared = types.I32
@@ -2262,12 +2489,17 @@ infer_spec_locals_and_result :: proc(
locals.allocator = checker.allocator
defer delete(locals)
local_types := make([]types.Type, len(checker.ast_module.statements), checker.allocator)
runtime_index := 0
for param, index in function.params {
if param.comptime_value {
continue
}
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
if runtime_index < len(spec.args) {
param_type = spec.args[runtime_index]
}
append(&locals, Infer_Local{name=param.name, type=param_type, declared=param_type, statement=ast.INVALID_STMT})
runtime_index += 1
}
result := types.INVALID
@@ -2716,6 +2948,7 @@ prune_specs :: proc(checker: ^Checker) {
for spec in checker.specs {
if spec.hir_id == hir.INVALID_FUNCTION {
delete(spec.args, checker.allocator)
delete(spec.comptime_values, checker.allocator)
continue
}
checker.specs[retained] = spec
@@ -4090,6 +4323,22 @@ build_expr :: proc(
})
}
}
if last == hir.INVALID_EXPR {
if !symbol.is_valid(expr.qualifier) {
if value, ok := current_comptime_value(checker, expr.name); ok {
expected_type := value.type
if types.is_concrete_integer(frame.expected) || types.is_float(frame.expected, checker.target) {
expected_type = frame.expected
}
last = build_constant_expr(
checker,
expr,
Constant{kind=.Value, value=value.value},
expected_type,
)
}
}
}
if last == hir.INVALID_EXPR {
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
last = enum_member_hir(checker, enum_type, expr.name, expr.span)
@@ -4340,14 +4589,22 @@ build_expr :: proc(
continue
}
}
function := checker.ast_module.functions[frame.template]
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, diagnose=true)
defer delete(comptime_values, checker.allocator)
arg_violation := source.INVALID_DIAGNOSTIC
{
params := checker.ast_module.functions[frame.template].params
if comptime_ok {
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = comptime_values
params := function.params
for index in 0..<len(params) {
if index >= len(stack[frame_index].arg_types) {
break
}
declared := type_from_syntax(checker, params[index].type, checker.ast_module.functions[frame.template].pkg, checker.ast_module.functions[frame.template].file)
if params[index].comptime_value {
continue
}
declared := type_from_syntax(checker, params[index].type, function.pkg, function.file)
actual := stack[frame_index].arg_types[index]
if types.is_constraint(declared) && types.is_valid(actual) &&
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
@@ -4360,14 +4617,22 @@ build_expr :: proc(
break
}
}
checker.current_comptime_values = previous_comptime
}
spec := INVALID_SPEC
if comptime_ok {
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
}
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
if arg_violation != source.INVALID_DIAGNOSTIC {
if !comptime_ok || arg_violation != source.INVALID_DIAGNOSTIC {
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, arg_violation)
diagnostic := arg_violation
if diagnostic == source.INVALID_DIAGNOSTIC {
diagnostic = source.add(checker.diagnostics, expr.span, "invalid comptime argument")
}
last = invalid_hir_expr(checker, expr.span, diagnostic)
_ = pop(&stack)
continue
}
@@ -4384,23 +4649,35 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
fixed_count := len(checker.ast_module.functions[frame.template].params)
for index in 0..<fixed_count {
stack[frame_index].built_args[index] = coerce_expr(
source_args := stack[frame_index].built_args
runtime_count := runtime_param_count(function)
runtime_arg_count := runtime_count + max(0, len(source_args)-len(function.params))
runtime_args := make([]hir.Expr_Id, runtime_arg_count, checker.allocator)
runtime_index := 0
for param, source_index in function.params {
if param.comptime_value {
continue
}
arg := source_args[source_index]
runtime_args[runtime_index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
checker.specs[spec].args[index],
checker.module.exprs[stack[frame_index].built_args[index]].span,
arg,
checker.specs[spec].args[runtime_index],
checker.module.exprs[arg].span,
)
runtime_index += 1
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
for source_index in len(function.params)..<len(source_args) {
arg := source_args[source_index]
runtime_args[runtime_index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
runtime_index += 1
}
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
add_unique_function(calls, function_id)
@@ -4412,15 +4689,13 @@ build_expr :: proc(
"could not resolve result type for specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
delete(runtime_args, checker.allocator)
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.function_ref(function_id),
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=stack[frame_index].built_args, diagnostic = source.INVALID_DIAGNOSTIC,
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=runtime_args, diagnostic = source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
@@ -4568,6 +4843,15 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
strings.write_string(&builder, types.name(arg))
}
}
for value in spec.comptime_values {
strings.write_string(&builder, "__cv")
if value.value < 0 {
strings.write_string(&builder, "n")
fmt.sbprintf(&builder, "%d", -value.value)
} else {
fmt.sbprintf(&builder, "%d", value.value)
}
}
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
}
@@ -4860,6 +5144,21 @@ build_block :: proc(
}
continue
}
if _, is_comptime := current_comptime_value(checker, statement.name); is_comptime {
id := source.addf(
checker.diagnostics,
statement.span,
"cannot assign comptime parameter '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
local, found := find_build_local(ctx.locals^[:], statement.name)
if !found {
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
@@ -6901,6 +7200,9 @@ all_paths_exit :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
build_function :: proc(checker: ^Checker, id: Spec_Id) {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
previous_comptime := checker.current_comptime_values
checker.current_comptime_values = spec.comptime_values
defer checker.current_comptime_values = previous_comptime
signature_diagnostic := source.INVALID_DIAGNOSTIC
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
checker.specs[id].result = types.I64
@@ -6945,15 +7247,20 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
delete(demanded)
}
for param, index in function.params {
runtime_index := 0
for param in function.params {
if param.comptime_value {
continue
}
local_id := hir.local_id(len(hir_locals))
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
if runtime_index < len(spec.args) {
param_type = spec.args[runtime_index]
}
append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true})
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
append(&params, local_id)
runtime_index += 1
}
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
@@ -7526,6 +7833,7 @@ check :: proc(
defer {
for spec in checker.specs {
delete(spec.args, allocator)
delete(spec.comptime_values, allocator)
}
delete(checker.specs)
delete(checker.function_index, allocator)
+3
View File
@@ -201,6 +201,9 @@ lex :: proc(
case '@':
append_token(&stream, source_file, .At, cursor, cursor+1)
cursor += 1
case '$':
append_token(&stream, source_file, .Dollar, cursor, cursor+1)
cursor += 1
case '*':
start := cursor
cursor += 1
+7 -1
View File
@@ -1577,6 +1577,7 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
}
continue
}
_, comptime_value := allow(parser, .Dollar)
names: [dynamic]token.Token
names.allocator = parser.module.allocator
for {
@@ -1596,7 +1597,12 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
}
type_syntax := parse_type(parser)
for name in names {
append(&params, ast.Param{name=name.symbol, span=name.span, type=type_syntax})
append(&params, ast.Param{
name=name.symbol,
span=name.span,
type=type_syntax,
comptime_value=comptime_value,
})
}
delete(names)
skip_newlines(parser)
+1
View File
@@ -36,6 +36,7 @@ Kind :: enum u8 {
Range_Inclusive,
Ellipsis,
At,
Dollar,
Star,
Ampersand,
Caret,