Commit 41f07888 authored by Roman Alifanov's avatar Roman Alifanov

Add ..= string concat operator, fix constructor and resolver bugs

Add string concatenation assignment operator (..=) across the full pipeline: lexer, parser, and backend codegen for variables, this.field, dict fields, and object fields. Fix __ct_last_instance set after construct call instead of before. Fix IR builder treating field symbols as known function calls. Fix resolver _current_func corruption during constructor resolution.
parent aa00c122
......@@ -54,13 +54,13 @@ def _emit_constructor_func(cls: IRClass, ctx: 'EmitContext') -> None:
# Generate unique instance ID
ctx.emit(f'local __ct_id="{cls.name}_$$_$RANDOM"')
ctx.emit(f'__ct_obj_class["${{__ct_id}}"]={cls.name!r}')
ctx.emit(f'__ct_last_instance="$__ct_id"')
# Initialise fields
for f in cls.fields:
_init_field(cls.name, f, ctx)
# Call user-defined construct if present, passing all args
if cls.constructor:
ctx.emit(f'{CLASS_FUNC_PREFIX}{cls.name}_construct "${{__ct_id}}" "$@"')
ctx.emit(f'__ct_last_instance="$__ct_id"')
ctx.emit('}')
ctx.emit('')
......
......@@ -236,6 +236,13 @@ def _assign(node: IRAssign, ctx: 'EmitContext') -> None:
ctx.emit(f'export {target}={val}')
return
# String concatenation assignment
if op == '..=':
val = _expr(value, ctx)
val_inner = val.strip('"')
ctx.emit(f'{target}="${{{target}}}{val_inner}"')
return
# Augmented assignment with arithmetic
if op in ('+=', '-=', '*=', '/=', '%='):
val = _expr(value, ctx)
......@@ -285,7 +292,11 @@ def _field_assign(node: IRFieldAssign, ctx: 'EmitContext') -> None:
if isinstance(node.receiver, IRThis):
key = f'$__ct_this.{node.field_name}'
if op in ('+=', '-=', '*=', '/='):
if op == '..=':
existing = f'${{{OBJ_STORE}["$__ct_this.{node.field_name}"]}}'
val_inner = val.strip('"')
ctx.emit(f'{OBJ_STORE}["$__ct_this.{node.field_name}"]="{existing}{val_inner}"')
elif op in ('+=', '-=', '*=', '/='):
existing = f'"${{{OBJ_STORE}["$__ct_this.{node.field_name}"]}}"'
ctx.emit(f'{OBJ_STORE}["$__ct_this.{node.field_name}"]="$(( {existing} {op[0]} {val} ))"')
else:
......@@ -296,13 +307,21 @@ def _field_assign(node: IRFieldAssign, ctx: 'EmitContext') -> None:
recv_name = recv.strip('"').strip('${}')
# Dict-as-struct: if receiver is a known dict var, use dict subscript syntax
if recv_name in ctx.dict_vars or node.receiver.type.kind == 'dict':
if op in ('+=', '-=', '*=', '/='):
if op == '..=':
existing = f'${{{recv_name}["{node.field_name}"]}}'
val_inner = val.strip('"')
ctx.emit(f'{recv_name}["{node.field_name}"]="{existing}{val_inner}"')
elif op in ('+=', '-=', '*=', '/='):
existing = f'"${{{recv_name}["{node.field_name}"]}}"'
ctx.emit(f'{recv_name}["{node.field_name}"]="$(( {existing} {op[0]} {val} ))"')
else:
ctx.emit(f'{recv_name}["{node.field_name}"]={val}')
return
if op in ('+=', '-=', '*=', '/='):
if op == '..=':
existing = f'${{{OBJ_STORE}["${{{recv_name}}}.{node.field_name}"]}}'
val_inner = val.strip('"')
ctx.emit(f'{OBJ_STORE}["${{{recv_name}}}.{node.field_name}"]="{existing}{val_inner}"')
elif op in ('+=', '-=', '*=', '/='):
existing = f'"${{{OBJ_STORE}["${{{recv_name}}}.{node.field_name}"]}}"'
ctx.emit(f'{OBJ_STORE}["${{{recv_name}}}.{node.field_name}"]="$(( {existing} {op[0]} {val} ))"')
else:
......
......@@ -597,7 +597,7 @@ class IRBuilder:
name = callee.name
sym = self._resolve_name(name)
if sym is not None:
if sym is not None and sym.kind != 'field':
return IRCall(callee=sym, callee_name=name, args=args,
is_shell_cmd=False, source=loc)
......
......@@ -328,12 +328,16 @@ class Lexer:
tokens.append(self._tok(TokenType.SLASH_ASSIGN, '/=', line, col))
continue
# --- Dot variants (order: ... > .. > .) ---
# --- Dot variants (order: ... > ..= > .. > .) ---
if ch == '.':
if self._ch(1) == '.' and self._ch(2) == '.':
self._advance(); self._advance(); self._advance()
tokens.append(self._tok(TokenType.DOTDOTDOT, '...', line, col))
self._dot_at_eol = False
elif self._ch(1) == '.' and self._ch(2) == '=':
self._advance(); self._advance(); self._advance()
tokens.append(self._tok(TokenType.DOTDOT_ASSIGN, '..=', line, col))
self._dot_at_eol = False
elif self._ch(1) == '.':
self._advance(); self._advance()
tokens.append(self._tok(TokenType.DOTDOT, '..', line, col))
......
......@@ -67,6 +67,7 @@ class TokenType(Enum):
MINUS_ASSIGN = auto()
STAR_ASSIGN = auto()
SLASH_ASSIGN = auto()
DOTDOT_ASSIGN = auto()
DOT = auto()
DOTDOT = auto()
DOTDOTDOT = auto()
......
......@@ -336,7 +336,10 @@ class Resolver:
# Resolve constructor
if node.constructor:
saved_func = self._current_func
self._current_func = cls_sym
self._resolve_constructor(node.constructor, cls_scope)
self._current_func = saved_func
# Resolve methods
for method in node.methods:
......
......@@ -703,7 +703,7 @@ class Parser:
if self._check(TokenType.ASSIGN, TokenType.PLUS_ASSIGN,
TokenType.MINUS_ASSIGN, TokenType.STAR_ASSIGN,
TokenType.SLASH_ASSIGN):
TokenType.SLASH_ASSIGN, TokenType.DOTDOT_ASSIGN):
op = self._advance().value
value = self.parse_expression()
return Assignment(target=expr, type_annotation=type_annotation,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment