Commit 4115df74 authored by Roman Alifanov's avatar Roman Alifanov

Initial commit

parents
This diff is collapsed. Click to expand it.
# ContenT
**ContenT** is a DSL compiler that transforms `.ct` files into optimized Bash scripts. The language syntax is a hybrid of Python, Go, and Vala.
[Русская версия](README_ru.md)
## Features
- **Clean syntax** — Python-like readability with Go/Vala influences
- **Classes & inheritance** — OOP with constructors and method calls
- **Lambdas**`x => x * 2`, `(a, b) => a + b`, multiline blocks
- **Decorators**`@retry`, `@log`, `@cache`, `@awk`
- **Pipe operator** — shell-like `|` for command chaining and functional composition
- **Error handling**`try/except/finally/throw/defer`
- **String interpolation**`"Hello, {name}!"`
- **@awk functions** — compile to AWK for ~300x speedup on string/numeric operations
- **Optimized output** - no unnecessary subshells, inlined methods
## Installation
```bash
git clone https://gitlab.eterfund.ru/ximperlinux/ContenT.git
cd content
```
Requires Python 3.8+ (bootstrap compiler).
## Quick Start
```bash
# Compile .ct to .sh
python3 content build main.ct
# Compile and run
python3 content run main.ct
# Compile with linting (ShellCheck)
python3 content build main.ct --lint
```
## Syntax Overview
### Variables & Strings
```
name = "World"
count = 42
message = "Hello, {name}!" # interpolation
```
### Functions
```
func greet (name, greeting = "Hello") {
print ("{greeting}, {name}!")
}
func sum (numbers...) { # variadic
total = 0
foreach n in numbers {
total += n
}
return total
}
```
### Classes
```
class Logger {
level = "INFO"
construct (level = "INFO") {
this.level = level
}
func log (msg) {
print ("[{this.level}] {msg}")
}
}
logger = Logger ("DEBUG")
logger.log ("Starting...")
```
### Pipe Operator
```
# Shell-like pipe — native bash pipes
files = shell.exec ("ls -la") | shell.exec ("grep txt") | shell.exec ("wc -l")
# Functional pipe — chain function calls
result = 5 | double | add_ten # = add_ten(double(5))
```
### Decorators
```
@retry (attempts = 3, delay = 1)
func fetch_data (url) {
return http.get (url)
}
@awk
func fast_sum (text) {
total = 0
n = text.split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
return total
}
```
### Control Flow
```
if count > 10 {
print ("Many")
} else if count > 0 {
print ("Some")
} else {
print ("None")
}
foreach item in items {
process (item)
}
for i in range (0, 10) {
print (i)
}
when value {
1 { print ("one") }
2, 3 { print ("two or three") }
4..10 { print ("four to ten") }
else { print ("other") }
}
```
### Error Handling
```
try {
data = fs.read (path)
return json.parse (data)
} except FileNotFound e {
print ("Not found: {e}")
return {}
} finally {
cleanup ()
}
```
## Standard Library
| Module | Functions |
|--------|-----------|
| **I/O** | `print()`, `exit()` |
| **HTTP** | `http.get/post/put/delete` |
| **Filesystem** | `fs.read/write/append/exists/remove/mkdir/list` |
| **JSON** | `json.parse/stringify` |
| **Strings** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **Arrays** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()` |
| **Dicts** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
| **Regex** | `regex.match/extract` |
| **Shell** | `shell.exec/capture/source` |
| **Math** | `math.add/sub/mul/div/mod/min/max/abs` |
| **Time** | `time.now/ms` |
| **Random** | `random()`, `random_range()` |
| **Args** | `args.count/get` |
| **Logger** | `logger.info/warn/error/debug` |
## CLI Commands
```bash
# Build
content build main.ct # -> main.sh
content build main.ct -o app.sh # custom output
content build lib.ct main.ct -o app.sh # multi-file
content build . -o app.sh # all .ct in directory
# Run
content run main.ct
content run main.ct -- arg1 arg2 # with arguments
# Build library
content --build-lib MyLib.ct # -> MyLib.sh
# Lint
content build main.ct --lint # run ShellCheck
```
## @awk — High-Performance Functions
The `@awk` decorator compiles functions to AWK instead of Bash, providing **100-1000x speedup** on numeric and string operations.
### Benchmark Results
Heavy string benchmark (`examples/bench_heavy_*.ct`):
| Version | Time | Speedup |
|---------|------|---------|
| Bash | 57.8s | 1x |
| AWK | 0.09s | **~640x** |
Test includes: string concatenation, pattern search, CSV generation, word splitting (5000-10000 iterations each).
### Usage
```
# Same syntax — just add @awk
@awk
func fast_sum (n) {
total = 0
for i in range (1, n + 1) {
total += i
}
return total
}
# String processing
@awk
func count_words (text) {
n = text.split (" ")
return n
}
# Works on class methods too
class Calculator {
@awk
func compute (data) {
# fast computation
}
}
```
### Supported in @awk
- Variables, assignments (`=`, `+=`, `-=`, `*=`, `/=`, `%=`)
- Loops: `for i in range()`, `foreach`, `while`
- Conditions: `if/else if/else`, `when`
- Operators: `+`, `-`, `*`, `/`, `%`, `^`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`
- String methods: `.len()`, `.upper()`, `.lower()`, `.trim()`, `.split()`, `.substr()`, `.contains()`, `.replace()`
- Array methods: `.len()`, `.get()`, `.set()`, `.push()`, `.pop()`
- Math: `math.sin()`, `math.cos()`, `math.sqrt()`, `math.log()`, `math.exp()`
- `break`, `continue`, `return`
### Limitations
- No access to `this` in class methods — pass data via parameters
- Arrays are associative (AWK limitation)
- No `ord()`/`chr()` (gawk-only)
Uses `mawk` (preferred) or `gawk` for maximum speed.
## Documentation
- [Language Specification](LANGUAGE_SPEC.md)
## Project Structure
```
bootstrap/ # Bootstrap compiler (Python)
├── main.py # CLI entry point
├── lexer.py # Tokenizer
├── parser.py # AST generation
├── codegen.py # Bash code generator
├── awk_codegen.py # AWK generator for @awk
└── stdlib.py # Standard library
examples/ # Example .ct programs
```
## License
[AGPL-3.0](LICENSE)
# ContenT
**ContenT** — DSL-компилятор, преобразующий `.ct` файлы в оптимизированные Bash-скрипты. Синтаксис языка — гибрид Python, Go и Vala.
[English version](README.md)
## Возможности
- **Чистый синтаксис** — читаемость Python с влиянием Go/Vala
- **Классы и наследование** — ООП с конструкторами и вызовами методов
- **Лямбды**`x => x * 2`, `(a, b) => a + b`, многострочные блоки
- **Декораторы**`@retry`, `@log`, `@cache`, `@awk`
- **Pipe-оператор** — shell-like `|` для цепочек команд и функциональной композиции
- **Обработка ошибок**`try/except/finally/throw/defer`
- **Строковая интерполяция**`"Привет, {name}!"`
- **@awk функции** — компиляция в AWK для ускорения ~300x на строковых/числовых операциях
- **Оптимизированный вывод** — без лишних subshell, инлайнинг методов
## Установка
```bash
git clone https://gitlab.eterfund.ru/ximperlinux/ContenT.git
cd content
```
Требуется Python 3.8+ (bootstrap-компилятор).
## Быстрый старт
```bash
# Компиляция .ct в .sh
python3 content build main.ct
# Компиляция и запуск
python3 content run main.ct
# Компиляция с линтингом (ShellCheck)
python3 content build main.ct --lint
```
## Обзор синтаксиса
### Переменные и строки
```
name = "Мир"
count = 42
message = "Привет, {name}!" # интерполяция
```
### Функции
```
func greet (name, greeting = "Привет") {
print ("{greeting}, {name}!")
}
func sum (numbers...) { # variadic
total = 0
foreach n in numbers {
total += n
}
return total
}
```
### Классы
```
class Logger {
level = "INFO"
construct (level = "INFO") {
this.level = level
}
func log (msg) {
print ("[{this.level}] {msg}")
}
}
logger = Logger ("DEBUG")
logger.log ("Запуск...")
```
### Pipe-оператор
```
# Shell-like pipe — нативные bash-пайпы
files = shell.exec ("ls -la") | shell.exec ("grep txt") | shell.exec ("wc -l")
# Функциональный pipe — цепочка вызовов функций
result = 5 | double | add_ten # = add_ten(double(5))
```
### Декораторы
```
@retry (attempts = 3, delay = 1)
func fetch_data (url) {
return http.get (url)
}
@awk
func fast_sum (text) {
total = 0
n = text.split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
return total
}
```
### Управление потоком
```
if count > 10 {
print ("Много")
} else if count > 0 {
print ("Немного")
} else {
print ("Ничего")
}
foreach item in items {
process (item)
}
for i in range (0, 10) {
print (i)
}
when value {
1 { print ("один") }
2, 3 { print ("два или три") }
4..10 { print ("от четырёх до десяти") }
else { print ("другое") }
}
```
### Обработка ошибок
```
try {
data = fs.read (path)
return json.parse (data)
} except FileNotFound e {
print ("Не найдено: {e}")
return {}
} finally {
cleanup ()
}
```
## Стандартная библиотека
| Модуль | Функции |
|--------|---------|
| **Ввод/вывод** | `print()`, `exit()` |
| **HTTP** | `http.get/post/put/delete` |
| **Файловая система** | `fs.read/write/append/exists/remove/mkdir/list` |
| **JSON** | `json.parse/stringify` |
| **Строки** | `.len()`, `.upper()`, `.lower()`, `.trim()`, `.contains()`, `.replace()`, `.split()`, `.substr()` |
| **Массивы** | `.push()`, `.pop()`, `.shift()`, `.len()`, `.get()`, `.set()`, `.join()`, `.slice()` |
| **Словари** | `.get()`, `.set()`, `.has()`, `.del()`, `.keys()` |
| **Regex** | `regex.match/extract` |
| **Shell** | `shell.exec/capture/source` |
| **Математика** | `math.add/sub/mul/div/mod/min/max/abs` |
| **Время** | `time.now/ms` |
| **Случайные числа** | `random()`, `random_range()` |
| **Аргументы** | `args.count/get` |
| **Логгер** | `logger.info/warn/error/debug` |
## Команды CLI
```bash
# Сборка
content build main.ct # -> main.sh
content build main.ct -o app.sh # свой выходной файл
content build lib.ct main.ct -o app.sh # несколько файлов
content build . -o app.sh # все .ct в директории
# Запуск
content run main.ct
content run main.ct -- arg1 arg2 # с аргументами
# Сборка библиотеки
content --build-lib MyLib.ct # -> MyLib.sh
# Линтинг
content build main.ct --lint # запуск ShellCheck
```
## @awk — Высокопроизводительные функции
Декоратор `@awk` компилирует функции в AWK вместо Bash, обеспечивая **ускорение в 100-1000 раз** на числовых и строковых операциях.
### Результаты бенчмарка
Тяжёлый строковый бенчмарк (`examples/bench_heavy_*.ct`):
| Версия | Время | Ускорение |
|--------|-------|-----------|
| Bash | 57.8с | 1x |
| AWK | 0.09с | **~640x** |
Тест включает: конкатенацию строк, поиск паттернов, генерацию CSV, разбиение слов (5000-10000 итераций каждый).
### Использование
```
# Тот же синтаксис — просто добавьте @awk
@awk
func fast_sum (n) {
total = 0
for i in range (1, n + 1) {
total += i
}
return total
}
# Обработка строк
@awk
func count_words (text) {
n = text.split (" ")
return n
}
# Работает и на методах классов
class Calculator {
@awk
func compute (data) {
# быстрые вычисления
}
}
```
### Поддерживается в @awk
- Переменные, присваивания (`=`, `+=`, `-=`, `*=`, `/=`, `%=`)
- Циклы: `for i in range()`, `foreach`, `while`
- Условия: `if/else if/else`, `when`
- Операторы: `+`, `-`, `*`, `/`, `%`, `^`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`
- Методы строк: `.len()`, `.upper()`, `.lower()`, `.trim()`, `.split()`, `.substr()`, `.contains()`, `.replace()`
- Методы массивов: `.len()`, `.get()`, `.set()`, `.push()`, `.pop()`
- Математика: `math.sin()`, `math.cos()`, `math.sqrt()`, `math.log()`, `math.exp()`
- `break`, `continue`, `return`
### Ограничения
- Нет доступа к `this` в методах классов — передавайте данные через параметры
- Массивы ассоциативные (ограничение AWK)
- Нет `ord()`/`chr()` (только в gawk)
Использует `mawk` (предпочтительно) или `gawk` для максимальной скорости.
## Документация
- [Спецификация языка](LANGUAGE_SPEC.md)
## Структура проекта
```
bootstrap/ # Bootstrap-компилятор (Python)
├── main.py # CLI точка входа
├── lexer.py # Токенизатор
├── parser.py # Генерация AST
├── codegen.py # Генератор Bash-кода
├── awk_codegen.py # AWK-генератор для @awk
└── stdlib.py # Стандартная библиотека
examples/ # Примеры .ct программ
```
## Лицензия
[AGPL-3.0](LICENSE)
__version__ = "0.1.0"
from dataclasses import dataclass, field
from typing import List, Optional, Any, Union
@dataclass
class SourceLocation:
line: int
column: int
filename: str = "<stdin>"
@dataclass
class ASTNode:
pass
@dataclass
class Expression (ASTNode):
pass
@dataclass
class IntegerLiteral (Expression):
value: int = 0
location: Optional[SourceLocation] = None
@dataclass
class FloatLiteral (Expression):
value: float = 0.0
location: Optional[SourceLocation] = None
@dataclass
class StringLiteral (Expression):
value: str = ""
has_interpolation: bool = False
location: Optional[SourceLocation] = None
@dataclass
class BoolLiteral (Expression):
value: bool = False
location: Optional[SourceLocation] = None
@dataclass
class NilLiteral (Expression):
location: Optional[SourceLocation] = None
@dataclass
class Identifier (Expression):
name: str = ""
location: Optional[SourceLocation] = None
@dataclass
class ArrayLiteral (Expression):
elements: List[Expression] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class DictLiteral (Expression):
pairs: List[tuple] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class BinaryOp (Expression):
left: Optional[Expression] = None
operator: str = ""
right: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class UnaryOp (Expression):
operator: str = ""
operand: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class CallExpr (Expression):
callee: Optional[Expression] = None
arguments: List[Expression] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class MemberAccess (Expression):
object: Optional[Expression] = None
member: str = ""
location: Optional[SourceLocation] = None
@dataclass
class IndexAccess (Expression):
object: Optional[Expression] = None
index: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class Lambda (Expression):
params: List[str] = field (default_factory=list)
body: Union['Block', Expression, None] = None
location: Optional[SourceLocation] = None
@dataclass
class ThisExpr (Expression):
location: Optional[SourceLocation] = None
@dataclass
class BaseCall (Expression):
arguments: List[Expression] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class NewExpr (Expression):
class_name: str = ""
arguments: List[Expression] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class Statement (ASTNode):
pass
@dataclass
class Block (Statement):
statements: List[Statement] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class ExpressionStmt (Statement):
expression: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class Assignment (Statement):
target: Optional[Expression] = None
operator: str = "="
value: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class ReturnStmt (Statement):
value: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class BreakStmt (Statement):
location: Optional[SourceLocation] = None
@dataclass
class ContinueStmt (Statement):
location: Optional[SourceLocation] = None
@dataclass
class IfStmt (Statement):
condition: Optional[Expression] = None
then_branch: Optional[Block] = None
elif_branches: List[tuple] = field (default_factory=list)
else_branch: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class WhileStmt (Statement):
condition: Optional[Expression] = None
body: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class ForStmt (Statement):
variable: str = ""
iterable: Optional[Expression] = None
body: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class ForeachStmt (Statement):
variables: List[str] = field (default_factory=list)
iterable: Optional[Expression] = None
body: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class TryStmt (Statement):
try_block: Optional[Block] = None
except_clauses: List[tuple] = field (default_factory=list)
finally_block: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class ThrowStmt (Statement):
expression: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class DeferStmt (Statement):
expression: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class ImportStmt (Statement):
path: str = ""
location: Optional[SourceLocation] = None
@dataclass
class RangePattern (Expression):
"""Range pattern for when branches: 1..10"""
start: Optional[Expression] = None
end: Optional[Expression] = None
location: Optional[SourceLocation] = None
@dataclass
class WhenBranch:
"""Single branch of a when statement"""
patterns: List[Expression] = field (default_factory=list) # values, ranges, or 'else'
is_else: bool = False
body: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class WhenStmt (Statement):
"""When statement (pattern matching)"""
value: Optional[Expression] = None
branches: List[WhenBranch] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class Declaration (ASTNode):
pass
@dataclass
class Parameter:
name: str = ""
default: Optional[Expression] = None
is_variadic: bool = False
@dataclass
class Decorator:
name: str = ""
arguments: List[tuple] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class FunctionDecl (Declaration):
name: str = ""
params: List[Parameter] = field (default_factory=list)
body: Optional[Block] = None
decorators: List[Decorator] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class ClassDecl (Declaration):
name: str = ""
parent: Optional[str] = None
fields: List[tuple] = field (default_factory=list)
constructor: Optional['ConstructorDecl'] = None
methods: List[FunctionDecl] = field (default_factory=list)
location: Optional[SourceLocation] = None
@dataclass
class ConstructorDecl (Declaration):
params: List[Parameter] = field (default_factory=list)
body: Optional[Block] = None
location: Optional[SourceLocation] = None
@dataclass
class Program (ASTNode):
statements: List[Union[Statement, Declaration]] = field (default_factory=list)
location: Optional[SourceLocation] = None
from typing import List, Dict, Optional, Set
from .ast_nodes import *
from .errors import ErrorCollector
from .stdlib import StdlibMixin
from .awk_codegen import AwkCodegenMixin
from .expr_codegen import ExprMixin
from .stmt_codegen import StmtMixin
from .class_codegen import ClassMixin
from .decorator_codegen import DecoratorMixin
from .dispatch_codegen import DispatchMixin
from .cse_codegen import CseMixin
class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
ClassMixin, DecoratorMixin, DispatchMixin, CseMixin):
"""
Main code generator class.
Combines mixins:
- StdlibMixin: stdlib function generation
- AwkCodegenMixin: @awk decorator compilation
- ExprMixin: expression generation
- StmtMixin: statement generation
- ClassMixin: class and method generation
- DecoratorMixin: decorator wrapper generation
- DispatchMixin: method dispatch and assignments
- CseMixin: common subexpression elimination
"""
def __init__(self):
self.output: List[str] = []
self.indent_level = 0
self.errors = ErrorCollector()
self.current_class: Optional[str] = None
self.current_class_fields: Set[str] = set()
self.in_class_method = False
self.in_function = False
self.classes: Dict[str, ClassDecl] = {}
self.functions: Dict[str, FunctionDecl] = {}
self.inlineable_methods: Dict[tuple, str] = {}
self.array_vars: Set[str] = set()
self.dict_vars: Set[str] = set()
self.class_field_types: Dict[tuple, str] = {}
self.local_vars: Set[str] = set()
self.global_vars: Set[str] = {
'L_SRC', 'L_POS', 'L_LEN', 'L_LINE', 'L_COL', 'L_FILE',
'T_TYPES', 'T_VALUES', 'T_LINES', 'T_COUNT',
'P_POS', 'P_STACK',
'A_TYPES', 'A_V1', 'A_V2', 'A_V3', 'A_V4', 'A_V5', 'A_V6', 'A_COUNT',
'G_OUT', 'G_INDENT', 'G_IN_FUNC', 'G_IN_CLASS', 'G_CUR_CLASS',
'G_DEFERRED', 'G_CLASSES', 'G_FUNCS', 'G_ARRAY_VARS', 'G_LOCAL_VARS', 'G_GLOBAL_VARS',
'G_TEMP_CTR', 'G_LAMBDA_CTR',
'G_CSE_KEYS', 'G_CSE_VALS', 'G_CSE_REGEN', 'G_CSE_CALLS',
'G_AWK_LINES', 'G_AWK_INDENT',
'__CT_RET',
}
self.lambda_counter = 0
self.temp_counter = 0
self.deferred_calls: List[str] = []
def indent(self) -> str:
return " " * self.indent_level
def emit(self, line: str = ""):
if line:
self.output.append(f"{self.indent()}{line}")
else:
self.output.append("")
def emit_raw(self, line: str):
self.output.append(line)
def emit_var_assign(self, var_name: str, value: str, is_array: bool = False):
"""Emit variable assignment with proper local declaration."""
if '.' in var_name or '[' in var_name:
self.emit(f'{var_name}="{value}"')
return
if self.in_function and var_name not in self.local_vars and var_name not in self.global_vars:
self.local_vars.add(var_name)
if is_array:
self.emit(f'local -a {var_name}=("{value}")')
else:
self.emit(f'local {var_name}="{value}"')
else:
self.emit(f'{var_name}="{value}"')
def new_temp(self) -> str:
self.temp_counter += 1
return f"__ct_tmp_{self.temp_counter}"
def new_lambda_name(self) -> str:
self.lambda_counter += 1
return f"__ct_lambda_{self.lambda_counter}"
def generate(self, program: Program) -> str:
"""Generate code for a single program."""
return self.generate_multi([program])
def generate_multi(self, programs: list) -> str:
"""Generate code for multiple programs (multi-file compilation)."""
self.emit_raw("#!/usr/bin/env bash")
self.emit_raw("# Generated by ContenT compiler")
self.emit_raw("set -euo pipefail")
self.emit()
self.emit_stdlib()
for program in programs:
for stmt in program.statements:
if isinstance(stmt, ClassDecl):
self.classes[stmt.name] = stmt
elif isinstance(stmt, FunctionDecl):
self.functions[stmt.name] = stmt
elif isinstance(stmt, Assignment):
if isinstance(stmt.target, Identifier):
self.global_vars.add(stmt.target.name)
for program in programs:
for stmt in program.statements:
self.generate_statement(stmt)
return "\n".join(self.output)
from .ast_nodes import *
class CseMixin:
"""Mixin for CSE optimization."""
def collect_method_calls(self, expr: Expression, calls: list):
"""Collect this.method() calls from an expression."""
if isinstance(expr, CallExpr):
if isinstance(expr.callee, MemberAccess) and isinstance(expr.callee.object, ThisExpr):
calls.append(expr)
for arg in expr.arguments:
self.collect_method_calls(arg, calls)
elif isinstance(expr, BinaryOp):
self.collect_method_calls(expr.left, calls)
self.collect_method_calls(expr.right, calls)
elif isinstance(expr, UnaryOp):
self.collect_method_calls(expr.operand, calls)
elif isinstance(expr, MemberAccess):
self.collect_method_calls(expr.object, calls)
def collect_all_calls(self, expr: Expression, calls: list):
"""Collect ALL function calls from an expression."""
if isinstance(expr, CallExpr):
calls.append(expr)
for arg in expr.arguments:
self.collect_all_calls(arg, calls)
elif isinstance(expr, BinaryOp):
self.collect_all_calls(expr.left, calls)
self.collect_all_calls(expr.right, calls)
elif isinstance(expr, UnaryOp):
self.collect_all_calls(expr.operand, calls)
elif isinstance(expr, MemberAccess):
self.collect_all_calls(expr.object, calls)
def precompute_condition_calls(self, condition: Expression) -> tuple:
"""Pre-compute method calls in condition."""
calls = []
self.collect_method_calls(condition, calls)
seen = {}
mapping = {}
regen_code = []
for call in calls:
method = call.callee.member
args = [self.generate_expr(arg) for arg in call.arguments]
args_str = " ".join([f'"{a}"' for a in args])
key = f"this.{method}({args_str})"
if key not in seen:
temp = self.new_temp()
call_line = f'__ct_class_{self.current_class}_{method} "$this" {args_str} >/dev/null'
assign_line = f'{temp}="$__CT_RET"'
self.emit(call_line)
self.emit(assign_line)
seen[key] = temp
regen_code.append((call_line, assign_line))
mapping[id(call)] = seen[key]
return mapping, regen_code
def precompute_all_calls(self, condition: Expression) -> tuple:
"""Pre-compute all function calls in condition."""
calls = []
self.collect_all_calls(condition, calls)
seen = {}
mapping = {}
regen_code = []
for call in calls:
if isinstance(call.callee, MemberAccess):
if isinstance(call.callee.object, ThisExpr):
method = call.callee.member
args = [self.generate_expr(arg) for arg in call.arguments]
args_str = " ".join([f'"{a}"' for a in args])
key = f"this.{method}({args_str})"
if key not in seen:
temp = self.new_temp()
call_line = f'__ct_class_{self.current_class}_{method} "$this" {args_str} >/dev/null'
assign_line = f'{temp}="$__CT_RET"'
self.emit(call_line)
self.emit(assign_line)
seen[key] = temp
regen_code.append((call_line, assign_line))
mapping[id(call)] = seen[key]
elif isinstance(call.callee.object, Identifier):
obj_name = call.callee.object.name
method = call.callee.member
args = [self.generate_expr(arg) for arg in call.arguments]
args_str = " ".join([f'"{a}"' for a in args])
key = f"{obj_name}.{method}({args_str})"
if key not in seen:
temp = self.new_temp()
call_expr = self.generate_expr(call)
if call_expr.startswith('$'):
call_line = f'{temp}="{call_expr}"'
else:
call_line = f'{temp}="$({call_expr})"'
self.emit(call_line)
seen[key] = temp
regen_code.append((call_line, ""))
mapping[id(call)] = seen[key]
elif isinstance(call.callee, Identifier):
func_name = call.callee.name
args = [self.generate_expr(arg) for arg in call.arguments]
args_str = " ".join([f'"{a}"' for a in args])
key = f"{func_name}({args_str})"
if key not in seen:
temp = self.new_temp()
call_line = f'{func_name} {args_str} >/dev/null'
assign_line = f'{temp}="$__CT_RET"'
self.emit(call_line)
self.emit(assign_line)
seen[key] = temp
regen_code.append((call_line, assign_line))
mapping[id(call)] = seen[key]
return mapping, regen_code
def generate_condition_with_precompute(self, expr: Expression, mapping: dict) -> str:
"""Generate condition using pre-computed values."""
if isinstance(expr, BinaryOp):
left = self.generate_expr_with_precompute(expr.left, mapping)
right = self.generate_expr_with_precompute(expr.right, mapping)
op = expr.operator
if op == "==":
return f'[[ "{left}" == "{right}" ]]'
elif op == "!=":
return f'[[ "{left}" != "{right}" ]]'
elif op == "<":
if self.is_string_comparison(expr.left, expr.right):
return f'[[ "{left}" < "{right}" ]]'
return f'[[ {left} -lt {right} ]]'
elif op == ">":
if self.is_string_comparison(expr.left, expr.right):
return f'[[ "{left}" > "{right}" ]]'
return f'[[ {left} -gt {right} ]]'
elif op == "<=":
if self.is_string_comparison(expr.left, expr.right):
return f'[[ ! "{left}" > "{right}" ]]'
return f'[[ {left} -le {right} ]]'
elif op == ">=":
if self.is_string_comparison(expr.left, expr.right):
return f'[[ ! "{left}" < "{right}" ]]'
return f'[[ {left} -ge {right} ]]'
elif op == "&&":
l = self.generate_condition_with_precompute(expr.left, mapping)
r = self.generate_condition_with_precompute(expr.right, mapping)
return f'{{ {l} && {r}; }}'
elif op == "||":
l = self.generate_condition_with_precompute(expr.left, mapping)
r = self.generate_condition_with_precompute(expr.right, mapping)
return f'{{ {l} || {r}; }}'
if isinstance(expr, UnaryOp) and expr.operator == "!":
inner = self.generate_condition_with_precompute(expr.operand, mapping)
return f'! {inner}'
if isinstance(expr, Identifier):
return f'[[ "${expr.name}" == "true" ]]'
if isinstance(expr, BoolLiteral):
return "true" if expr.value else "false"
return self.generate_condition(expr)
def generate_expr_with_precompute(self, expr: Expression, mapping: dict) -> str:
"""Generate expression using pre-computed values."""
if isinstance(expr, CallExpr) and id(expr) in mapping:
return f'${mapping[id(expr)]}'
if isinstance(expr, MemberAccess):
if isinstance(expr.object, CallExpr) and id(expr.object) in mapping:
temp = mapping[id(expr.object)]
return f'${{__CT_OBJ["${temp}.{expr.member}"]}}'
return self.generate_expr(expr)
return self.generate_expr(expr)
from typing import List
from .ast_nodes import *
class DecoratorMixin:
"""Mixin for decorator wrapper generation."""
def generate_decorator_wrapper(self, decorator: Decorator, wrapped_name: str,
wrapper_name: str, params: List[Parameter]):
"""Generate decorator wrapper for standalone function."""
if decorator.name == "retry":
self._generate_retry_wrapper(decorator, wrapped_name, wrapper_name, params, is_method=False)
elif decorator.name == "log":
self._generate_log_wrapper(wrapped_name, wrapper_name, params, is_method=False)
elif decorator.name == "cache":
self._generate_cache_wrapper(decorator, wrapped_name, wrapper_name, params, is_method=False)
else:
self._generate_passthrough_wrapper(wrapped_name, wrapper_name, params, is_method=False)
def generate_method_decorator_wrapper(self, decorator: Decorator, wrapped_name: str,
wrapper_name: str, params: List[Parameter]):
"""Generate decorator wrapper for class method."""
if decorator.name == "retry":
self._generate_retry_wrapper(decorator, wrapped_name, wrapper_name, params, is_method=True)
elif decorator.name == "log":
self._generate_log_wrapper(wrapped_name, wrapper_name, params, is_method=True)
elif decorator.name == "cache":
self._generate_cache_wrapper(decorator, wrapped_name, wrapper_name, params, is_method=True)
else:
self._generate_passthrough_wrapper(wrapped_name, wrapper_name, params, is_method=True)
self.current_class = None
self.current_class_fields = set()
def _generate_retry_wrapper(self, decorator: Decorator, wrapped_name: str,
wrapper_name: str, params: List[Parameter], is_method: bool):
attempts = 3
delay = 1
for arg_name, arg_val in decorator.arguments:
if arg_name == "attempts":
attempts = self.generate_expr(arg_val)
elif arg_name == "delay":
delay = self.generate_expr(arg_val)
self.emit(f"{wrapper_name} () {{")
self.indent_level += 1
if is_method:
self.emit('local this="$1"')
self.emit('shift')
self.emit(f"local __attempts={attempts}")
self.emit(f"local __delay={delay}")
self.emit("local __i")
self.emit("for __i in $(seq 1 $__attempts); do")
self.indent_level += 1
params_str = " ".join([f'"${{{i + 1}}}"' for i in range(len(params))])
if is_method:
self.emit(f'if {wrapped_name} "$this" {params_str}; then')
else:
self.emit(f'if {wrapped_name} {params_str}; then')
self.indent_level += 1
self.emit("return 0")
self.indent_level -= 1
self.emit("fi")
self.emit('sleep "$__delay"')
self.indent_level -= 1
self.emit("done")
self.emit("return 1")
self.indent_level -= 1
self.emit("}")
self.emit()
def _generate_log_wrapper(self, wrapped_name: str, wrapper_name: str,
params: List[Parameter], is_method: bool):
self.emit(f"{wrapper_name} () {{")
self.indent_level += 1
if is_method:
self.emit('local this="$1"')
self.emit('shift')
self.emit(f'echo "[LOG] Calling {wrapped_name}" >&2')
params_str = " ".join([f'"${{{i + 1}}}"' for i in range(len(params))])
if is_method:
self.emit(f'{wrapped_name} "$this" {params_str}')
else:
self.emit(f'{wrapped_name} {params_str}')
self.emit('local __ret=$?')
self.emit(f'echo "[LOG] {wrapped_name} returned $__ret" >&2')
self.emit('return $__ret')
self.indent_level -= 1
self.emit("}")
self.emit()
def _generate_cache_wrapper(self, decorator: Decorator, wrapped_name: str,
wrapper_name: str, params: List[Parameter], is_method: bool):
ttl = 60
for arg_name, arg_val in decorator.arguments:
if arg_name == "ttl":
ttl = self.generate_expr(arg_val)
self.emit(f"declare -gA __ct_cache_{wrapper_name}=()")
self.emit(f"declare -g __ct_cache_time_{wrapper_name}=0")
self.emit()
self.emit(f"{wrapper_name} () {{")
self.indent_level += 1
if is_method:
self.emit('local this="$1"')
self.emit('shift')
self.emit(f'local __key="$this:$*"')
else:
self.emit(f'local __key="$*"')
self.emit(f'local __now=$(date +%s)')
self.emit(f'local __cache_age=$((__now - __ct_cache_time_{wrapper_name}))')
self.emit(f'if [[ $__cache_age -lt {ttl} ]] && [[ -n "${{__ct_cache_{wrapper_name}[$__key]:-}}" ]]; then')
self.indent_level += 1
self.emit(f'__CT_RET="${{__ct_cache_{wrapper_name}[$__key]}}"')
self.emit('echo "$__CT_RET"')
self.emit("return 0")
self.indent_level -= 1
self.emit("fi")
params_str = " ".join([f'"${{{i + 1}}}"' for i in range(len(params))])
if is_method:
self.emit(f'local __result=$({wrapped_name} "$this" {params_str})')
else:
self.emit(f'local __result=$({wrapped_name} {params_str})')
self.emit(f'__CT_RET="$__result"')
self.emit(f'__ct_cache_{wrapper_name}["$__key"]="$__result"')
self.emit(f'__ct_cache_time_{wrapper_name}=$__now')
self.emit('echo "$__result"')
self.indent_level -= 1
self.emit("}")
self.emit()
def _generate_passthrough_wrapper(self, wrapped_name: str, wrapper_name: str,
params: List[Parameter], is_method: bool):
self.emit(f"{wrapper_name} () {{")
self.indent_level += 1
if is_method:
self.emit('local this="$1"')
self.emit('shift')
params_str = " ".join([f'"${{{i + 1}}}"' for i in range(len(params))])
if is_method:
self.emit(f'{wrapped_name} "$this" {params_str}')
else:
self.emit(f'{wrapped_name} {params_str}')
self.indent_level -= 1
self.emit("}")
self.emit()
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class CompileError:
message: str
filename: str
line: int
column: int
hint: Optional[str] = None
def __str__ (self):
result = f"Error: {self.message}\n --> {self.filename}:{self.line}:{self.column}"
if self.hint:
result += f"\n Hint: {self.hint}"
return result
class ErrorCollector:
def __init__ (self):
self.errors: List[CompileError] = []
def add (self, error: CompileError):
self.errors.append (error)
def add_error (self, message: str, filename: str, line: int, column: int, hint: str = None):
self.errors.append (CompileError (
message=message,
filename=filename,
line=line,
column=column,
hint=hint
))
def has_errors (self) -> bool:
return len (self.errors) > 0
def print_errors (self):
for error in self.errors:
print (str (error))
print ()
def clear (self):
self.errors = []
from typing import List, Optional
from .tokens import Token, TokenType, KEYWORDS
from .errors import CompileError
class Lexer:
def __init__ (self, source: str, filename: str = "<stdin>"):
self.source = source
self.filename = filename
self.pos = 0
self.line = 1
self.column = 1
self.tokens: List[Token] = []
self.errors: List[CompileError] = []
def current (self) -> Optional[str]:
if self.pos >= len (self.source):
return None
return self.source[self.pos]
def peek (self, offset: int = 1) -> Optional[str]:
pos = self.pos + offset
if pos >= len (self.source):
return None
return self.source[pos]
def advance (self) -> Optional[str]:
ch = self.current ()
if ch is None:
return None
self.pos += 1
if ch == '\n':
self.line += 1
self.column = 1
else:
self.column += 1
return ch
def skip_whitespace (self):
while self.current () in (' ', '\t', '\r'):
self.advance ()
def add_token (self, type: TokenType, value=None, line=None, column=None):
self.tokens.append (Token (
type=type,
value=value,
line=line or self.line,
column=column or self.column
))
def error (self, message: str):
self.errors.append (CompileError (
message=message,
filename=self.filename,
line=self.line,
column=self.column
))
def read_string (self) -> str:
start_line = self.line
start_col = self.column
quote = self.advance ()
result = []
while True:
ch = self.current ()
if ch is None:
self.error ("Unterminated string")
break
if ch == quote:
self.advance ()
break
if ch == '\\':
self.advance ()
escaped = self.current ()
if escaped is None:
self.error ("Unterminated escape sequence")
break
escape_map = {
'n': '\n',
't': '\t',
'r': '\r',
'\\': '\\',
'{': '\x00LBRACE\x00',
'}': '\x00RBRACE\x00',
'$': '\x00DOLLAR\x00',
'"': '"',
"'": "'",
}
result.append (escape_map.get (escaped, escaped))
self.advance ()
else:
result.append (ch)
self.advance ()
return ''.join (result)
def read_number (self) -> Token:
start_col = self.column
result = []
is_float = False
while True:
ch = self.current ()
if ch is None:
break
if ch.isdigit ():
result.append (ch)
self.advance ()
elif ch == '.' and not is_float:
if self.peek () and self.peek ().isdigit ():
is_float = True
result.append (ch)
self.advance ()
else:
break
else:
break
value = ''.join (result)
if is_float:
return Token (TokenType.FLOAT, float (value), self.line, start_col)
return Token (TokenType.INTEGER, int (value), self.line, start_col)
def read_identifier (self) -> str:
result = []
while True:
ch = self.current ()
if ch is None:
break
if ch.isalnum () or ch == '_':
result.append (ch)
self.advance ()
else:
break
return ''.join (result)
def tokenize (self) -> List[Token]:
while True:
self.skip_whitespace ()
ch = self.current ()
if ch is None:
self.add_token (TokenType.EOF)
break
start_line = self.line
start_col = self.column
if ch == '#':
while self.current () and self.current () != '\n':
self.advance ()
continue
if ch == '\n':
self.add_token (TokenType.NEWLINE, line=start_line, column=start_col)
self.advance ()
continue
if ch in ('"', "'"):
value = self.read_string ()
self.add_token (TokenType.STRING, value, start_line, start_col)
continue
if ch.isdigit ():
token = self.read_number ()
self.tokens.append (token)
continue
if ch.isalpha () or ch == '_':
value = self.read_identifier ()
token_type = KEYWORDS.get (value, TokenType.IDENTIFIER)
self.add_token (token_type, value, start_line, start_col)
continue
if ch == '=' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.EQ, '==', start_line, start_col)
continue
if ch == '!' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.NEQ, '!=', start_line, start_col)
continue
if ch == '<' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.LTE, '<=', start_line, start_col)
continue
if ch == '>' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.GTE, '>=', start_line, start_col)
continue
if ch == '&' and self.peek () == '&':
self.advance ()
self.advance ()
self.add_token (TokenType.AND, '&&', start_line, start_col)
continue
if ch == '|' and self.peek () == '|':
self.advance ()
self.advance ()
self.add_token (TokenType.OR, '||', start_line, start_col)
continue
if ch == '=' and self.peek () == '>':
self.advance ()
self.advance ()
self.add_token (TokenType.ARROW, '=>', start_line, start_col)
continue
if ch == '+' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.PLUS_ASSIGN, '+=', start_line, start_col)
continue
if ch == '-' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.MINUS_ASSIGN, '-=', start_line, start_col)
continue
if ch == '*' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.STAR_ASSIGN, '*=', start_line, start_col)
continue
if ch == '/' and self.peek () == '=':
self.advance ()
self.advance ()
self.add_token (TokenType.SLASH_ASSIGN, '/=', start_line, start_col)
continue
if ch == '.' and self.peek () == '.' and self.peek (2) == '.':
self.advance ()
self.advance ()
self.advance ()
self.add_token (TokenType.DOTDOTDOT, '...', start_line, start_col)
continue
if ch == '.' and self.peek () == '.':
self.advance ()
self.advance ()
self.add_token (TokenType.DOTDOT, '..', start_line, start_col)
continue
single_char_tokens = {
'+': TokenType.PLUS,
'-': TokenType.MINUS,
'*': TokenType.STAR,
'/': TokenType.SLASH,
'%': TokenType.PERCENT,
'=': TokenType.ASSIGN,
'<': TokenType.LT,
'>': TokenType.GT,
'!': TokenType.NOT,
'.': TokenType.DOT,
':': TokenType.COLON,
'(': TokenType.LPAREN,
')': TokenType.RPAREN,
'{': TokenType.LBRACE,
'}': TokenType.RBRACE,
'[': TokenType.LBRACKET,
']': TokenType.RBRACKET,
',': TokenType.COMMA,
'@': TokenType.AT,
'|': TokenType.PIPE,
}
if ch in single_char_tokens:
self.advance ()
self.add_token (single_char_tokens[ch], ch, start_line, start_col)
continue
if ch == ';':
self.advance ()
continue
self.error (f"Unexpected character: '{ch}'")
self.advance ()
return self.tokens
import sys
import os
import argparse
import tempfile
import subprocess
from pathlib import Path
from .lexer import Lexer
from .parser import Parser
from .codegen import CodeGenerator
def parse_file (source_path: str):
"""Parse a .ct file and return AST or None on error."""
try:
with open (source_path, "r", encoding="utf-8") as f:
source = f.read ()
except FileNotFoundError:
print (f"Error: File not found: {source_path}", file=sys.stderr)
return None
except Exception as e:
print (f"Error reading file: {e}", file=sys.stderr)
return None
filename = os.path.basename (source_path)
lexer = Lexer (source, filename)
tokens = lexer.tokenize ()
if lexer.errors:
for error in lexer.errors:
print (str (error), file=sys.stderr)
return None
parser = Parser (tokens, filename)
ast = parser.parse ()
if parser.errors.has_errors ():
parser.errors.print_errors ()
return None
return ast
def compile_file (source_path: str, output_path: str = None) -> tuple[bool, str]:
"""Compile a single .ct file to bash."""
return compile_files ([source_path])
def compile_files (source_paths: list) -> tuple[bool, str]:
"""Compile multiple .ct files to bash (Vala-style multi-file)."""
asts = []
for source_path in source_paths:
ast = parse_file (source_path)
if ast is None:
return False, ""
asts.append (ast)
codegen = CodeGenerator ()
output = codegen.generate_multi (asts)
if codegen.errors.has_errors ():
codegen.errors.print_errors ()
return False, ""
return True, output
def find_ct_files (directory: str = ".") -> list:
"""Find all .ct files in directory, sorted (main.ct last for proper ordering)."""
import glob
files = sorted (glob.glob (os.path.join (directory, "*.ct")))
main_files = [f for f in files if os.path.basename (f) == "main.ct"]
other_files = [f for f in files if os.path.basename (f) != "main.ct"]
return other_files + main_files
def cmd_build (args):
"""Build command - compile .ct to .sh (supports multiple files)"""
source_paths = args.sources
if source_paths == ["."]:
source_paths = find_ct_files (".")
if not source_paths:
print ("Error: No .ct files found in current directory", file=sys.stderr)
return 1
for source_path in source_paths:
if not source_path.endswith (".ct"):
print (f"Error: Source file must have .ct extension: {source_path}", file=sys.stderr)
return 1
if args.output:
output_path = args.output
else:
output_path = source_paths[0].replace (".ct", ".sh")
success, output = compile_files (source_paths)
if not success:
return 1
try:
with open (output_path, "w", encoding="utf-8") as f:
f.write (output)
os.chmod (output_path, 0o755)
if len (source_paths) == 1:
print (f"Compiled: {source_paths[0]} -> {output_path}")
else:
print (f"Compiled: {', '.join (source_paths)} -> {output_path}")
except Exception as e:
print (f"Error writing output: {e}", file=sys.stderr)
return 1
if args.lint:
print ("Running ShellCheck...")
result = subprocess.run (
["shellcheck", output_path],
capture_output=True,
text=True
)
if result.returncode != 0:
print (result.stdout)
print (result.stderr, file=sys.stderr)
else:
print ("ShellCheck: OK")
return 0
def cmd_run (args):
"""Run command - compile and execute (supports multiple files)"""
all_args = args.sources_and_args
if "--" in all_args:
sep_idx = all_args.index ("--")
source_paths = all_args[:sep_idx]
script_args = all_args[sep_idx + 1:]
else:
source_paths = all_args
script_args = []
if source_paths == ["."]:
source_paths = find_ct_files (".")
if not source_paths:
print ("Error: No .ct files found in current directory", file=sys.stderr)
return 1
for source_path in source_paths:
if not source_path.endswith (".ct"):
print (f"Error: Source file must have .ct extension: {source_path}", file=sys.stderr)
return 1
success, output = compile_files (source_paths)
if not success:
return 1
try:
with tempfile.NamedTemporaryFile (
mode="w",
suffix=".sh",
delete=False,
encoding="utf-8"
) as f:
f.write (output)
temp_path = f.name
os.chmod (temp_path, 0o755)
try:
result = subprocess.run (
["bash", temp_path] + script_args,
check=False
)
return result.returncode
finally:
try:
os.unlink (temp_path)
except:
pass
except Exception as e:
print (f"Error: {e}", file=sys.stderr)
return 1
def cmd_build_lib (args):
"""Build library command"""
source_path = args.source
if not source_path.endswith (".ct"):
print (f"Error: Source file must have .ct extension", file=sys.stderr)
return 1
output_path = source_path.replace (".ct", ".sh")
success, output = compile_file (source_path)
if not success:
return 1
lib_name = Path (source_path).stem
metadata = f'''
'''
footer = '''
'''
output = metadata + output + footer
try:
with open (output_path, "w", encoding="utf-8") as f:
f.write (output)
os.chmod (output_path, 0o644)
print (f"Library built: {source_path} -> {output_path}")
except Exception as e:
print (f"Error writing output: {e}", file=sys.stderr)
return 1
return 0
def main ():
parser = argparse.ArgumentParser (
prog="content",
description="ContenT Compiler - Compile ContenT to Bash"
)
parser.add_argument (
"--version", "-v",
action="version",
version="ContenT 0.1.0 (bootstrap)"
)
subparsers = parser.add_subparsers (dest="command", help="Commands")
build_parser = subparsers.add_parser ("build", help="Compile .ct to .sh")
build_parser.add_argument ("sources", nargs="+", help="Source files (.ct)")
build_parser.add_argument ("-o", "--output", help="Output file (.sh)")
build_parser.add_argument ("--lint", action="store_true", help="Run ShellCheck")
run_parser = subparsers.add_parser ("run", help="Compile and run (use -- to separate script args)")
run_parser.add_argument ("sources_and_args", nargs="+", help="Source files (.ct) [-- script args]")
lib_parser = subparsers.add_parser ("build-lib", help="Build a library")
lib_parser.add_argument ("source", help="Source file (.ct)")
parser.add_argument ("--build-lib", metavar="FILE", help="Build a library")
args = parser.parse_args ()
if args.build_lib:
class LibArgs:
source = args.build_lib
return cmd_build_lib (LibArgs ())
if args.command == "build":
return cmd_build (args)
elif args.command == "run":
return cmd_run (args)
elif args.command == "build-lib":
return cmd_build_lib (args)
else:
parser.print_help ()
return 0
if __name__ == "__main__":
sys.exit (main ())
from enum import Enum, auto
from dataclasses import dataclass
from typing import Any
class TokenType (Enum):
INTEGER = auto ()
FLOAT = auto ()
STRING = auto ()
TRUE = auto ()
FALSE = auto ()
NIL = auto ()
IDENTIFIER = auto ()
FUNC = auto ()
CLASS = auto ()
CONSTRUCT = auto ()
THIS = auto ()
BASE = auto ()
RETURN = auto ()
IF = auto ()
ELSE = auto ()
FOREACH = auto ()
FOR = auto ()
IN = auto ()
WHILE = auto ()
BREAK = auto ()
CONTINUE = auto ()
IMPORT = auto ()
TRY = auto ()
EXCEPT = auto ()
FINALLY = auto ()
THROW = auto ()
DEFER = auto ()
RANGE = auto ()
WHEN = auto ()
NEW = auto ()
PLUS = auto ()
MINUS = auto ()
STAR = auto ()
SLASH = auto ()
PERCENT = auto ()
ASSIGN = auto ()
EQ = auto ()
NEQ = auto ()
LT = auto ()
GT = auto ()
LTE = auto ()
GTE = auto ()
AND = auto ()
OR = auto ()
PIPE = auto ()
NOT = auto ()
ARROW = auto ()
PLUS_ASSIGN = auto ()
MINUS_ASSIGN = auto ()
STAR_ASSIGN = auto ()
SLASH_ASSIGN = auto ()
DOT = auto ()
DOTDOT = auto ()
DOTDOTDOT = auto ()
COLON = auto ()
LPAREN = auto ()
RPAREN = auto ()
LBRACE = auto ()
RBRACE = auto ()
LBRACKET = auto ()
RBRACKET = auto ()
COMMA = auto ()
NEWLINE = auto ()
AT = auto ()
COMMENT = auto ()
EOF = auto ()
KEYWORDS = {
'func': TokenType.FUNC,
'class': TokenType.CLASS,
'construct': TokenType.CONSTRUCT,
'this': TokenType.THIS,
'base': TokenType.BASE,
'return': TokenType.RETURN,
'if': TokenType.IF,
'else': TokenType.ELSE,
'foreach': TokenType.FOREACH,
'for': TokenType.FOR,
'in': TokenType.IN,
'while': TokenType.WHILE,
'break': TokenType.BREAK,
'continue': TokenType.CONTINUE,
'import': TokenType.IMPORT,
'try': TokenType.TRY,
'except': TokenType.EXCEPT,
'finally': TokenType.FINALLY,
'throw': TokenType.THROW,
'defer': TokenType.DEFER,
'range': TokenType.RANGE,
'when': TokenType.WHEN,
'new': TokenType.NEW,
'true': TokenType.TRUE,
'false': TokenType.FALSE,
'nil': TokenType.NIL,
}
@dataclass
class Token:
type: TokenType
value: Any
line: int
column: int
def __repr__ (self):
return f"Token({self.type.name}, {self.value!r}, {self.line}:{self.column})"
#!/usr/bin/env python3
"""
ContenT Compiler Entry Point
"""
import sys
import os
# Add bootstrap to path
sys.path.insert (0, os.path.dirname (os.path.abspath (__file__)))
from bootstrap.main import main
if __name__ == "__main__":
sys.exit (main ())
# Simple benchmark: @awk vs bash
# Test pure computation speed
# ========== AWK versions ==========
@awk
func awk_fib (n) {
a = 0
b = 1
for i in range (n) {
tmp = a
a = b
b = tmp + b
}
return a
}
@awk
func awk_sum_to_n (n) {
total = 0
for i in range (1, n + 1) {
total += i
}
return total
}
@awk
func awk_factorial (n) {
result = 1
for i in range (1, n + 1) {
result *= i
}
return result
}
@awk
func awk_count_primes (max) {
count = 0
for n in range (2, max) {
is_prime = 1
for i in range (2, n) {
if n % i == 0 {
is_prime = 0
break
}
}
if is_prime == 1 {
count += 1
}
}
return count
}
# ========== Bash versions ==========
func bash_fib (n) {
a = 0
b = 1
for i in range (n) {
tmp = a
a = b
b = tmp + b
}
return a
}
func bash_sum_to_n (n) {
total = 0
for i in range (1, n + 1) {
total += i
}
return total
}
func bash_factorial (n) {
result = 1
for i in range (1, n + 1) {
result *= i
}
return result
}
func bash_count_primes (max) {
count = 0
for n in range (2, max) {
is_prime = 1
for i in range (2, n) {
if n % i == 0 {
is_prime = 0
break
}
}
if is_prime == 1 {
count += 1
}
}
return count
}
# ========== Main ==========
print ("ContenT @awk vs Bash - Simple Benchmark")
print ("=======================================")
print ("")
# Verify correctness
print ("Verification (should be identical):")
print (" fib(20): AWK={awk_fib (20)} Bash={bash_fib (20)}")
print (" sum_to_n(100): AWK={awk_sum_to_n (100)} Bash={bash_sum_to_n (100)}")
print (" factorial(10): AWK={awk_factorial (10)} Bash={bash_factorial (10)}")
print (" primes(<50): AWK={awk_count_primes (50)} Bash={bash_count_primes (50)}")
print ("")
print ("Run with 'time' to compare performance:")
print (" time bash script.sh awk")
print (" time bash script.sh bash")
# Heavy string benchmark - bash should take ~1 minute
@awk
func awk_process_lines (n) {
result = ""
for i in range (n) {
line = "item_" .. i .. "_data_" .. (i * 7) .. "_end"
upper = line.upper ()
if upper.contains ("DATA") {
result = result .. upper.len () .. ","
}
}
return result.len ()
}
@awk
func awk_word_stats (n) {
total_len = 0
word_count = 0
for i in range (n) {
words = "alpha beta gamma delta epsilon zeta eta theta"
num = words.split (" ")
for j in range (1, num + 1) {
word = __split_arr[j]
total_len += word.len ()
word_count += 1
}
}
return total_len
}
@awk
func awk_build_csv (rows, cols) {
result = ""
for r in range (rows) {
for c in range (cols) {
val = (r * cols + c) * 13 % 1000
result = result .. val
if c < cols - 1 {
result = result .. ","
}
}
result = result .. "\n"
}
return result.len ()
}
@awk
func awk_search_pattern (n) {
count = 0
for i in range (n) {
text = "error_log_" .. i .. "_warning_" .. (i % 100) .. "_info"
if text.contains ("warning") {
count += 1
}
if text.contains ("error") {
count += 1
}
if text.contains ("info") {
count += 1
}
}
return count
}
@awk
func awk_concat_heavy (n) {
s = ""
for i in range (n) {
s = s .. "x"
}
return s.len ()
}
# === BASH versions ===
func bash_process_lines (n) {
result = ""
for i in range (n) {
line = "item_" .. i .. "_data_" .. (i * 7) .. "_end"
upper = line.upper ()
if upper.contains ("DATA") {
result = result .. upper.len () .. ","
}
}
return result.len ()
}
func bash_word_stats (n) {
total_len = 0
word_count = 0
for i in range (n) {
words = "alpha beta gamma delta epsilon zeta eta theta"
foreach word in words.split (" ") {
total_len = total_len + word.len ()
word_count = word_count + 1
}
}
return total_len
}
func bash_build_csv (rows, cols) {
result = ""
for r in range (rows) {
for c in range (cols) {
val = (r * cols + c) * 13 % 1000
result = result .. val
if c < cols - 1 {
result = result .. ","
}
}
result = result .. "\n"
}
return result.len ()
}
func bash_search_pattern (n) {
count = 0
for i in range (n) {
text = "error_log_" .. i .. "_warning_" .. (i % 100) .. "_info"
if text.contains ("warning") {
count = count + 1
}
if text.contains ("error") {
count = count + 1
}
if text.contains ("info") {
count = count + 1
}
}
return count
}
func bash_concat_heavy (n) {
s = ""
for i in range (n) {
s = s .. "x"
}
return s.len ()
}
# Main
print ("Heavy String Benchmark")
print ("======================")
print ("")
mode = "both"
if mode == "awk" {
print ("Running AWK version...")
print (" process_lines(5000): {awk_process_lines (5000)}")
print (" word_stats(3000): {awk_word_stats (3000)}")
print (" build_csv(200,50): {awk_build_csv (200, 50)}")
print (" search_pattern(10000): {awk_search_pattern (10000)}")
print (" concat_heavy(10000): {awk_concat_heavy (10000)}")
} else if mode == "bash" {
print ("Running BASH version...")
print (" process_lines(5000): {bash_process_lines (5000)}")
print (" word_stats(3000): {bash_word_stats (3000)}")
print (" build_csv(200,50): {bash_build_csv (200, 50)}")
print (" search_pattern(10000): {bash_search_pattern (10000)}")
print (" concat_heavy(10000): {bash_concat_heavy (10000)}")
} else {
print ("Running both versions...")
print ("")
print ("AWK:")
print (" process_lines(5000): {awk_process_lines (5000)}")
print (" word_stats(3000): {awk_word_stats (3000)}")
print (" build_csv(200,50): {awk_build_csv (200, 50)}")
print (" search_pattern(10000): {awk_search_pattern (10000)}")
print (" concat_heavy(10000): {awk_concat_heavy (10000)}")
print ("")
print ("BASH:")
print (" process_lines(5000): {bash_process_lines (5000)}")
print (" word_stats(3000): {bash_word_stats (3000)}")
print (" build_csv(200,50): {bash_build_csv (200, 50)}")
print (" search_pattern(10000): {bash_search_pattern (10000)}")
print (" concat_heavy(10000): {bash_concat_heavy (10000)}")
}
print ("")
print ("Done!")
# Heavy string benchmark - AWK only
@awk
func awk_process_lines (n) {
result = ""
for i in range (n) {
line = "item_" .. i .. "_data_" .. (i * 7) .. "_end"
upper = line.upper ()
if upper.contains ("DATA") {
result = result .. upper.len () .. ","
}
}
return result.len ()
}
@awk
func awk_word_stats (n) {
total_len = 0
word_count = 0
for i in range (n) {
words = "alpha beta gamma delta epsilon zeta eta theta"
num = words.split (" ")
for j in range (1, num + 1) {
word = __split_arr[j]
total_len += word.len ()
word_count += 1
}
}
return total_len
}
@awk
func awk_build_csv (rows, cols) {
result = ""
for r in range (rows) {
for c in range (cols) {
val = (r * cols + c) * 13 % 1000
result = result .. val
if c < cols - 1 {
result = result .. ","
}
}
result = result .. "\n"
}
return result.len ()
}
@awk
func awk_search_pattern (n) {
count = 0
for i in range (n) {
text = "error_log_" .. i .. "_warning_" .. (i % 100) .. "_info"
if text.contains ("warning") {
count += 1
}
if text.contains ("error") {
count += 1
}
if text.contains ("info") {
count += 1
}
}
return count
}
@awk
func awk_concat_heavy (n) {
s = ""
for i in range (n) {
s = s .. "x"
}
return s.len ()
}
print ("=== AWK String Benchmark ===")
print ("process_lines(5000): {awk_process_lines (5000)}")
print ("word_stats(3000): {awk_word_stats (3000)}")
print ("build_csv(200,50): {awk_build_csv (200, 50)}")
print ("search_pattern(10000): {awk_search_pattern (10000)}")
print ("concat_heavy(10000): {awk_concat_heavy (10000)}")
print ("Done!")
# Heavy string benchmark - BASH only
func bash_process_lines (n) {
result = ""
for i in range (n) {
line = "item_" .. i .. "_data_" .. (i * 7) .. "_end"
upper = line.upper ()
if upper.contains ("DATA") {
result = result .. upper.len () .. ","
}
}
return result.len ()
}
func bash_word_stats (n) {
total_len = 0
word_count = 0
for i in range (n) {
words = "alpha beta gamma delta epsilon zeta eta theta"
foreach word in words.split (" ") {
total_len = total_len + word.len ()
word_count = word_count + 1
}
}
return total_len
}
func bash_build_csv (rows, cols) {
result = ""
for r in range (rows) {
for c in range (cols) {
val = (r * cols + c) * 13 % 1000
result = result .. val
if c < cols - 1 {
result = result .. ","
}
}
result = result .. "\n"
}
return result.len ()
}
func bash_search_pattern (n) {
count = 0
for i in range (n) {
text = "error_log_" .. i .. "_warning_" .. (i % 100) .. "_info"
if text.contains ("warning") {
count = count + 1
}
if text.contains ("error") {
count = count + 1
}
if text.contains ("info") {
count = count + 1
}
}
return count
}
func bash_concat_heavy (n) {
s = ""
for i in range (n) {
s = s .. "x"
}
return s.len ()
}
print ("=== BASH String Benchmark ===")
print ("process_lines(5000): {bash_process_lines (5000)}")
print ("word_stats(3000): {bash_word_stats (3000)}")
print ("build_csv(200,50): {bash_build_csv (200, 50)}")
print ("search_pattern(10000): {bash_search_pattern (10000)}")
print ("concat_heavy(10000): {bash_concat_heavy (10000)}")
print ("Done!")
# Speed benchmark: @awk vs bash
# Measure actual execution time
@awk
func awk_fib (n) {
a = 0
b = 1
for i in range (n) {
tmp = a
a = b
b = tmp + b
}
return a
}
@awk
func awk_sum (n) {
total = 0
for i in range (1, n + 1) {
total += i
}
return total
}
@awk
func awk_primes (max) {
count = 0
for n in range (2, max) {
is_prime = 1
for i in range (2, n) {
if n % i == 0 {
is_prime = 0
break
}
}
if is_prime == 1 {
count += 1
}
}
return count
}
# Main - run AWK benchmarks multiple times
print ("@awk Benchmark Results:")
print ("=======================")
# Warmup
awk_fib (10)
awk_sum (10)
# Run tests
iterations = 100
print ("fib(30) x {iterations}:")
for i in range (iterations) {
awk_fib (30)
}
print (" Done")
print ("sum(1000) x {iterations}:")
for i in range (iterations) {
awk_sum (1000)
}
print (" Done")
print ("primes(100) x 10:")
for i in range (10) {
awk_primes (100)
}
print (" Done")
print ("")
print ("Results verification:")
print (" fib(30) = {awk_fib (30)}")
print (" sum(1000) = {awk_sum (1000)}")
print (" primes(100) = {awk_primes (100)}")
# Speed benchmark: pure Bash (no @awk)
# Measure actual execution time
func bash_fib (n) {
a = 0
b = 1
for i in range (n) {
c = a + b
a = b
b = c
}
return a
}
func bash_sum (n) {
total = 0
for i in range (1, n + 1) {
total = total + i
}
return total
}
func bash_primes (max) {
count = 0
for n in range (2, max) {
is_prime = 1
for i in range (2, n) {
if n % i == 0 {
is_prime = 0
break
}
}
if is_prime == 1 {
count = count + 1
}
}
return count
}
# Main - run Bash benchmarks multiple times
print ("Pure Bash Benchmark Results:")
print ("============================")
# Warmup
bash_fib (10)
bash_sum (10)
# Run tests
iterations = 100
print ("fib(30) x {iterations}:")
for i in range (iterations) {
bash_fib (30)
}
print (" Done")
print ("sum(1000) x {iterations}:")
for i in range (iterations) {
bash_sum (1000)
}
print (" Done")
print ("primes(100) x 10:")
for i in range (10) {
bash_primes (100)
}
print (" Done")
print ("")
print ("Results verification:")
print (" fib(30) = {bash_fib (30)}")
print (" sum(1000) = {bash_sum (1000)}")
print (" primes(100) = {bash_primes (100)}")
# Benchmark: @awk vs pure bash string operations
# ========== AWK versions ==========
@awk
func awk_count_words (text) {
count = 0
n = text.split (" ")
for i in range (1, n + 1) {
count += 1
}
return count
}
@awk
func awk_sum_numbers (text) {
total = 0
n = text.split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
return total
}
@awk
func awk_transform (text) {
result = ""
n = text.split (" ")
for i in range (1, n + 1) {
word = __split_arr[i]
upper = word.upper ()
result = result .. upper
if i < n {
result = result .. "-"
}
}
return result
}
@awk
func awk_stats (text) {
total = 0
count = 0
min = 999999999
max = -999999999
n = text.split (" ")
for i in range (1, n + 1) {
val = __split_arr[i]
total += val
count += 1
if val < min {
min = val
}
if val > max {
max = val
}
}
avg = total / count
return sprintf ("sum=%d count=%d min=%d max=%d avg=%.2f", total, count, min, max, avg)
}
# ========== Bash versions ==========
func bash_count_words (text) {
count = 0
foreach word in text.split (" ") {
count += 1
}
return count
}
func bash_sum_numbers (text) {
total = 0
foreach num in text.split (" ") {
total += num
}
return total
}
func bash_transform (text) {
result = ""
words = text.split (" ")
foreach i, word in words {
result = result .. word.upper ()
if i < words.len () - 1 {
result = result .. "-"
}
}
return result
}
func bash_stats (text) {
total = 0
count = 0
min = 999999999
max = -999999999
foreach num in text.split (" ") {
total += num
count += 1
if num < min {
min = num
}
if num > max {
max = num
}
}
avg = total / count
return "sum={total} count={count} min={min} max={max} avg={avg}"
}
# ========== Generate test data ==========
func generate_words (n) {
words = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape"]
result = ""
for i in range (n) {
idx = i % 7
result = result .. words[idx]
if i < n - 1 {
result = result .. " "
}
}
return result
}
func generate_numbers (n) {
result = ""
for i in range (n) {
num = (i * 17 + 31) % 1000
result = result .. num
if i < n - 1 {
result = result .. " "
}
}
return result
}
# ========== Benchmark runner ==========
func benchmark (name, iterations) {
print ("=== {name} ===")
print ("Iterations: {iterations}")
print ("")
}
# Main
print ("ContenT @awk vs Bash Benchmark")
print ("==============================")
print ("")
# Generate test data
word_data = generate_words (100)
num_data = generate_numbers (100)
print ("Test data: 100 words, 100 numbers")
print ("")
# Warmup
awk_count_words (word_data)
bash_count_words (word_data)
# Run benchmarks
iterations = 50
print ("Running {iterations} iterations of each function...")
print ("")
# Count words benchmark
print ("1. Count Words:")
print (" AWK: Run 'time' externally to measure")
print (" Bash: Run 'time' externally to measure")
# Show sample output
print ("")
print ("Sample outputs:")
print (" awk_count_words: {awk_count_words (word_data)}")
print (" bash_count_words: {bash_count_words (word_data)}")
print (" awk_sum_numbers: {awk_sum_numbers (num_data)}")
print (" bash_sum_numbers: {bash_sum_numbers (num_data)}")
print (" awk_transform (5 words): {awk_transform (generate_words (5))}")
print (" bash_transform (5 words): {bash_transform (generate_words (5))}")
print (" awk_stats: {awk_stats (num_data)}")
print (" bash_stats: {bash_stats (num_data)}")
# Class method decorators example
class Calculator {
value = 0
construct (initial = 0) {
this.value = initial
}
@log
func add (x) {
this.value += x
return this.value
}
@cache (ttl = 60)
func expensiveCompute (x) {
print ("Computing {x}...")
return x * x
}
@retry (attempts = 3, delay = 1)
func unreliableOp () {
print ("Trying unreliable operation...")
return "success"
}
# @awk method - no access to 'this', pass data as params
@awk
func fastSum (numbers) {
total = 0
n = numbers.split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
return total
}
}
# Test
calc = Calculator (10)
print ("Testing @log decorator on method:")
result = calc.add (5)
print ("Result: {result}")
print ("")
print ("Testing @cache decorator on method:")
result = calc.expensiveCompute (7)
print ("First call: {result}")
result = calc.expensiveCompute (7)
print ("Cached call: {result}")
print ("")
print ("Testing @awk decorator on method:")
result = calc.fastSum ("1 2 3 4 5")
print ("Sum of 1-5: {result}")
# Classes example
class Logger {
level = "INFO"
prefix = ""
construct (level = "INFO") {
this.level = level
}
func log (msg) {
print ("[{this.level}] {msg}")
}
func setLevel (level) {
this.level = level
}
}
# Create instance
logger = Logger ("DEBUG")
logger.log ("Application started")
logger.setLevel ("INFO")
logger.log ("Running...")
# main.ct - Example app using class-based cli.ct library
# Similar to urfave/cli v3 style
# Create app with built-in help and version flags
app = new_app ("myapp", "A sample CLI application")
app.with_version ("1.0.0")
# Add custom flags
name_flag = new_string_flag ("name", "World", "Name to greet")
name_flag.with_short ("n")
app.add_flag (name_flag)
count_flag = new_int_flag ("count", "1", "Number of times to greet")
count_flag.with_short ("c")
app.add_flag (count_flag)
loud_flag = new_bool_flag ("loud", "Use uppercase")
loud_flag.with_short ("l")
app.add_flag (loud_flag)
# Create commands
greet_cmd = new_command ("greet", "Greet someone")
app.add_command (greet_cmd)
calc_cmd = new_command ("calc", "Simple calculator")
calc_cmd.with_category ("math")
app.add_command (calc_cmd)
add_cmd = new_command ("add", "Add two numbers")
add_cmd.with_category ("math")
app.add_command (add_cmd)
# Run and get matched command
cmd = app.run ()
# Handle commands
when cmd {
"greet" {
name = app.string ("name")
count = app.int ("count")
loud = app.bool ("loud")
i = 0
while i < count {
if loud {
print ("HELLO, {name}!")
} else {
print ("Hello, {name}!")
}
i = i + 1
}
}
"calc" {
if calc_cmd.narg () < 3 {
print ("Usage: myapp calc <num1> <op> <num2>")
print ("Example: myapp calc 10 + 5")
} else {
# Args are in the subcommand
a = calc_cmd.arg (0)
op = calc_cmd.arg (1)
b = calc_cmd.arg (2)
when op {
"+" {
result = a + b
print ("{a} + {b} = {result}")
}
"-" {
result = a - b
print ("{a} - {b} = {result}")
}
"x" {
result = a * b
print ("{a} * {b} = {result}")
}
"/" {
result = a / b
print ("{a} / {b} = {result}")
}
else {
print ("Unknown operator: {op}")
}
}
}
}
"add" {
if add_cmd.narg () < 2 {
print ("Usage: myapp add <num1> <num2>")
} else {
a = add_cmd.arg (0)
b = add_cmd.arg (1)
result = a + b
print ("{a} + {b} = {result}")
}
}
"" {
# No command - show help
app.help ()
}
else {
print ("Unknown command: {cmd}")
app.help ()
}
}
# Decorators example
@retry (attempts = 3, delay = 1)
func fetchData (url) {
print ("Fetching: {url}")
return http.get (url)
}
@log
func processData (data) {
print ("Processing: {data}")
return data
}
@cache (ttl = 60)
func expensiveOperation (x) {
print ("Computing for {x}...")
return x
}
# Test decorators
result = expensiveOperation (42)
print ("Result: {result}")
result = expensiveOperation (42)
print ("Cached result: {result}")
# Defer example
func processFile (path) {
print ("Opening file: {path}")
f = fs.read (path)
defer print ("Cleanup: closing file")
defer print ("Cleanup: releasing resources")
print ("Processing file content...")
print ("File content: {f}")
return "done"
}
# Test
result = processFile ("/etc/hostname")
print ("Result: {result}")
# Exception handling example
# Simple error handling demonstration
print ("=== Exception Handling Demo ===")
print ("")
# Example 1: Direct try/except (not in function)
print ("Test 1: Reading non-existent file")
try {
data = fs.read ("/etc/myapp/nonexistent.json")
print ("Data: {data}")
} except e {
print ("Caught error: {e}")
}
print ("")
print ("Test 2: Reading existing file")
try {
data = fs.read ("/etc/hostname")
print ("Hostname: {data}")
} except e {
print ("Error: {e}")
}
print ("")
print ("Test 3: Finally block")
try {
print ("In try block")
x = 1
} except e {
print ("This should not print")
} finally {
print ("Finally always runs")
}
print ("")
print ("=== Done ===")
# Hello World example
name = "World"
print ("Hello, {name}!")
# Function example
func greet (name, greeting = "Hello") {
print ("{greeting}, {name}!")
}
greet ("Alice")
greet ("Bob", "Hi")
# Loop example
foreach i in range (5) {
print ("Count: {i}")
}
# HTTP API example
func fetchUser (id) {
url = "https://jsonplaceholder.typicode.com/users/{id}"
response = http.get (url)
return response
}
func createPost (title, body, userId) {
data = json.stringify ({
"title": title,
"body": body,
"userId": userId
})
response = http.post ("https://jsonplaceholder.typicode.com/posts", data)
return response
}
# Fetch user
print ("Fetching user 1...")
user = fetchUser (1)
print ("User data:")
print (user)
# Create post
print ("Creating new post...")
post = createPost ("My Post", "This is content", 1)
print ("Created post:")
print (post)
# Lambdas example
# Single parameter lambda
double = x => x * 2
# Multiple parameters lambda
add = (a, b) => a + b
# Lambda with block
process = item => {
print ("Processing: {item}")
result = item * 10
return result
}
# Test lambdas
print ("double(5) = ")
print (double (5))
print ("add(3, 4) = ")
print (add (3, 4))
print ("process(7) = ")
print (process (7))
# main.ct - Main program (uses utils.ct)
# Use functions from utils.ct
greet ("World")
greet ("ContenT")
result = add (10, 20)
print ("10 + 20 = {result}")
# Use class from utils.ct
counter = Counter (5)
print ("Initial: {counter.get ()}")
counter.increment ()
counter.increment ()
print ("After 2 increments: {counter.get ()}")
# utils.ct - Utility functions
func greet (name) {
print ("Hello, {name}!")
}
func add (a, b) {
return a + b
}
class Counter {
value = 0
construct (initial = 0) {
this.value = initial
}
func increment () {
this.value += 1
return this.value
}
func get () {
return this.value
}
}
# Test shell-like pipe
print ("=== Shell-like pipe test ===")
files = shell.exec ("echo -e 'file1.txt\nfile2.txt\nfile3.ct'") | shell.exec ("grep ct")
print ("Files with .ct: {files}")
# Test multiple pipes
count = shell.exec ("echo -e 'a\nb\nc\nd\ne'") | shell.exec ("grep -v a") | shell.exec ("wc -l")
print ("Count (should be 4): {count}")
# Test functional pipe
func double (x) {
return x * 2
}
func add_ten (x) {
return x + 10
}
# 5 | double | add_ten = add_ten(double(5)) = add_ten(10) = 20
result = 5 | double | add_ten
print ("5 | double | add_ten = {result}")
print ("=== Done ===")
# Test when statement
func testWhen (value) {
when value {
1 {
print ("one")
}
2, 3 {
print ("two or three")
}
else {
print ("other")
}
}
}
func testWhenRange (value) {
when value {
1 {
print ("one")
}
2..5 {
print ("two to five")
}
6, 7, 8 {
print ("six, seven or eight")
}
9..20 {
print ("nine to twenty")
}
else {
print ("out of range")
}
}
}
# Test simple when (no ranges - uses case)
print ("Testing simple when:")
testWhen (1)
testWhen (2)
testWhen (3)
testWhen (5)
print ("")
print ("Testing when with ranges:")
testWhenRange (1)
testWhenRange (3)
testWhenRange (7)
testWhenRange (15)
testWhenRange (100)
# Test when statement in @awk functions
@awk
func categorize (value) {
when value {
1 {
return "one"
}
2, 3 {
return "few"
}
4..10 {
return "several"
}
else {
return "many"
}
}
}
print (categorize (1))
print (categorize (2))
print (categorize (7))
print (categorize (100))
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