diff options
| -rw-r--r-- | docs/c-interoperability.md | 6 | ||||
| -rw-r--r-- | examples/colorful/colorful.tm | 12 | ||||
| -rw-r--r-- | examples/coroutines/coroutines.tm | 20 | ||||
| -rw-r--r-- | examples/game/player.tm | 8 | ||||
| -rw-r--r-- | examples/http-server/http-server.tm | 18 | ||||
| -rw-r--r-- | examples/http/http.tm | 50 | ||||
| -rw-r--r-- | examples/ini/ini.tm | 14 | ||||
| -rw-r--r-- | examples/log/log.tm | 4 | ||||
| -rw-r--r-- | examples/wrap/wrap.tm | 8 | ||||
| -rw-r--r-- | lib/commands/commands.tm | 16 | ||||
| -rw-r--r-- | lib/json/json.tm | 2 | ||||
| -rw-r--r-- | lib/patterns/patterns.tm | 28 | ||||
| -rw-r--r-- | lib/pthreads/pthreads.tm | 57 | ||||
| -rw-r--r-- | lib/random/random.tm | 46 | ||||
| -rw-r--r-- | lib/shell/shell.tm | 2 | ||||
| -rw-r--r-- | lib/time/time.tm | 42 | ||||
| -rw-r--r-- | src/parse/text.c | 100 | ||||
| -rw-r--r-- | test/inline_c.tm | 6 | ||||
| -rw-r--r-- | test/text.tm | 11 |
19 files changed, 223 insertions, 227 deletions
diff --git a/docs/c-interoperability.md b/docs/c-interoperability.md index 3c0906ee..ae959220 100644 --- a/docs/c-interoperability.md +++ b/docs/c-interoperability.md @@ -23,12 +23,12 @@ without evaluating to anything: ```tomo # Inline C block: -C_code { +C_code ` printf("This is just a block that is executed without a return value\n"); -} +` # Inline C expression (you must specify a type) -val := C_code : Int32 (int x = 1; x + 1) +val := C_code : Int32 `int x = 1; x + 1` ``` Inline C expressions must specify a type and they can be [compound statement diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm index 5b01cfd5..8a1d46b0 100644 --- a/examples/colorful/colorful.tm +++ b/examples/colorful/colorful.tm @@ -45,18 +45,18 @@ func main(texts:[Text], files:[Path]=[], by_line=no) func _for_terminal(c:Colorful, state:_TermState -> Text) - return c.text.map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch) _add_ansi_sequences(m.captures[1], state)) + return c.text.map_pattern(recursive=no, $Pat"@(?)", func(m:PatternMatch) _add_ansi_sequences(m.captures[1], state)) enum _Color(Default, Bright(color:Int16), Color8Bit(color:Int16), Color24Bit(color:Int32)) func from_text(text:Text -> _Color?) - if text.matches_pattern($Pat/#{3-6 hex}/) + if text.matches_pattern($Pat"#{3-6 hex}") hex := text.from(2) return none unless hex.length == 3 or hex.length == 6 if hex.length == 3 hex = hex[1]++hex[1]++hex[2]++hex[2]++hex[3]++hex[3] n := Int32.parse("0x" ++ hex) or return none return Color24Bit(n) - else if text.matches_pattern($Pat/{1-3 digit}/) + else if text.matches_pattern($Pat"{1-3 digit}") n := Int16.parse(text) or return none if n >= 0 and n <= 255 return Color8Bit(n) else if text == "black" return _Color.Color8Bit(0) @@ -171,10 +171,10 @@ func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text) else if text == "rparen" return ")" else if text == "@" or text == "at" return "@" parts := ( - text.pattern_captures($Pat/{0+..}:{0+..}/) or + text.pattern_captures($Pat"{0+..}:{0+..}") or return "@("++_for_terminal(Colorful.from_text(text), prev_state)++")" ) - attributes := parts[1].split_pattern($Pat/{0+space},{0+space}/) + attributes := parts[1].split_pattern($Pat"{0+space},{0+space}") new_state := prev_state for attr in attributes if attr.starts_with("fg=") @@ -215,6 +215,6 @@ func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text) fail("Invalid attribute: '$attr'") result := prev_state.apply(new_state) - result ++= parts[2].map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch) _add_ansi_sequences(m.captures[1], new_state)) + result ++= parts[2].map_pattern(recursive=no, $Pat"@(?)", func(m:PatternMatch) _add_ansi_sequences(m.captures[1], new_state)) result ++= new_state.apply(prev_state) return result diff --git a/examples/coroutines/coroutines.tm b/examples/coroutines/coroutines.tm index b530a685..c4ef1a97 100644 --- a/examples/coroutines/coroutines.tm +++ b/examples/coroutines/coroutines.tm @@ -37,31 +37,31 @@ struct Coroutine(co:@aco_t) main_co := _main_co shared_stack := _shared_stack - aco_ptr := C_code:@aco_t( + aco_ptr := C_code:@aco_t ` aco_create(@main_co, @shared_stack, 0, (void*)@fn.fn, @fn.userdata) - ) + ` return Coroutine(aco_ptr) func is_finished(co:Coroutine->Bool; inline) - return C_code:Bool(((aco_t*)@co.co)->is_finished) + return C_code:Bool`((aco_t*)@co.co)->is_finished` func resume(co:Coroutine->Bool) if co.is_finished() return no - C_code { aco_resume(@co.co); } + C_code `aco_resume(@co.co);` return yes func _init() - C_code { + C_code ` aco_set_allocator(GC_malloc, NULL); aco_thread_init(aco_exit_fn); - } - _main_co = C_code:@aco_t(aco_create(NULL, NULL, 0, NULL, NULL)) + ` + _main_co = C_code:@aco_t`aco_create(NULL, NULL, 0, NULL, NULL)` - _shared_stack = C_code:@aco_shared_stack_t(aco_shared_stack_new(0)) + _shared_stack = C_code:@aco_shared_stack_t`aco_shared_stack_new(0)` func yield(; inline) - C_code { + C_code ` aco_yield(); - } + ` diff --git a/examples/game/player.tm b/examples/game/player.tm index 2e5e54f6..b34eadd0 100644 --- a/examples/game/player.tm +++ b/examples/game/player.tm @@ -10,12 +10,12 @@ struct Player(pos,prev_pos:Vector2) COLOR := Color(0x60, 0x60, 0xbF) func update(p:@Player) - target_x := C_code:Num32( + target_x := C_code:Num32` (Num32_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0)) - ) - target_y := C_code:Num32( + ` + target_y := C_code:Num32` (Num32_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0)) - ) + ` target_vel := Vector2(target_x, target_y).norm() * Player.WALK_SPEED vel := (p.pos - p.prev_pos)/World.DT diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm index 8e8aff7e..dbe57805 100644 --- a/examples/http-server/http-server.tm +++ b/examples/http-server/http-server.tm @@ -22,7 +22,7 @@ func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_th workers.insert(pthread_t.new(func() repeat connection := connections.dequeue() - request_text := C_code:Text( + request_text := C_code:Text``` Text_t request = EMPTY_TEXT; char buf[1024] = {}; for (ssize_t n; (n = read(@connection, buf, sizeof(buf) - 1)) > 0; ) { @@ -32,20 +32,20 @@ func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_th break; } request - ) + ``` request := HTTPRequest.from_text(request_text) or skip response := handler(request).bytes() - C_code { + C_code ` if (@response.stride != 1) List$compact(&@response, 1); write(@connection, @response.data, @response.length); close(@connection); - } + ` )) - sock := C_code:Int32( + sock := C_code:Int32 ` int s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) err(1, "Couldn't connect to socket!"); @@ -60,10 +60,10 @@ func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_th err(1, "Couldn't listen on socket"); s - ) + ` repeat - conn := C_code:Int32(accept(@sock, NULL, NULL)) + conn := C_code:Int32`accept(@sock, NULL, NULL)` stop if conn < 0 connections.enqueue(conn) @@ -77,8 +77,8 @@ struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Te method := m[1] path := m[2].replace_pattern($Pat'{2+ /}', '/') version := m[3] - rest := m[-1].pattern_captures($Pat/{..}{2 crlf}{0+ ..}/) or return none - headers := rest[1].split_pattern($Pat/{crlf}/) + rest := m[-1].pattern_captures($Pat'{..}{2 crlf}{0+ ..}') or return none + headers := rest[1].split_pattern($Pat'{crlf}') body := rest[-1] return HTTPRequest(method, path, version, headers, body) diff --git a/examples/http/http.tm b/examples/http/http.tm index 3fe41ae2..8d111904 100644 --- a/examples/http/http.tm +++ b/examples/http/http.tm @@ -10,73 +10,73 @@ enum _Method(GET, POST, PUT, PATCH, DELETE) func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse) chunks : @[Text] save_chunk := func(chunk:CString, size:Int64, n:Int64) - chunks.insert(C_code:Text(Text$from_strn(@chunk, @size*@n))) + chunks.insert(C_code:Text`Text$from_strn(@chunk, @size*@n)`) return n*size - C_code { + C_code ` CURL *curl = curl_easy_init(); struct curl_slist *chunk = NULL; curl_easy_setopt(curl, CURLOPT_URL, @(CString(url))); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, @save_chunk.fn); curl_easy_setopt(curl, CURLOPT_WRITEDATA, @save_chunk.userdata); - } + ` defer - C_code { + C_code ` if (chunk) curl_slist_free_all(chunk); curl_easy_cleanup(curl); - } + ` when method is POST - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_POST, 1L); - } + ` if posting := data - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(posting))); - } + ` is PUT - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - } + ` if putting := data - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(putting))); - } + ` is PATCH - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - } + ` if patching := data - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(patching))); - } + ` is DELETE - C_code { + C_code ` curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - } + ` else pass for header in headers - C_code { + C_code ` chunk = curl_slist_append(chunk, @(CString(header))); - } + ` - C_code { + C_code ` if (chunk) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); - } + ` code := Int64(0) - C_code { + C_code ``` CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &@code); - } + ``` return HTTPResponse(Int(code), "".join(chunks)) func get(url:Text, headers:[Text]=[] -> HTTPResponse) diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm index 4dc27725..576d273f 100644 --- a/examples/ini/ini.tm +++ b/examples/ini/ini.tm @@ -15,24 +15,24 @@ func parse_ini(path:Path -> {Text={Text=Text}}) current_section : @{Text=Text} # Line wraps: - text = text.replace_pattern($Pat/\\{1 nl}{0+space}/, " ") + text = text.replace_pattern($Pat`\\{1 nl}{0+space}`, " ") for line in text.lines() line = line.trim() skip if line.starts_with(";") or line.starts_with("#") - if line.matches_pattern($Pat/[?]/) - section_name := line.replace($Pat/[?]/, "@1").trim().lower() + if line.matches_pattern($Pat`[?]`) + section_name := line.replace($Pat`[?]`, "@1").trim().lower() current_section = @{} sections[section_name] = current_section - else if line.matches_pattern($Pat/{..}={..}/) - key := line.replace_pattern($Pat/{..}={..}/, "@1").trim().lower() - value := line.replace_pattern($Pat/{..}={..}/, "@2").trim() + else if line.matches_pattern($Pat`{..}={..}`) + key := line.replace_pattern($Pat`{..}={..}`, "@1").trim().lower() + value := line.replace_pattern($Pat`{..}={..}`, "@2").trim() current_section[key] = value return {k=v[] for k,v in sections[]} func main(path:Path, key:Text?) - keys := (key or "").split($|/|) + keys := (key or "").split(`/`) if keys.length > 2 exit(" Too many arguments! diff --git a/examples/log/log.tm b/examples/log/log.tm index 4b7893fd..2798b7ae 100644 --- a/examples/log/log.tm +++ b/examples/log/log.tm @@ -6,13 +6,13 @@ timestamp_format := CString("%F %T") logfiles : @|Path| func _timestamp(->Text) - c_str := C_code:CString( + c_str := C_code:CString` char *str = GC_MALLOC_ATOMIC(20); time_t t; time(&t); struct tm *tm_info = localtime(&t); strftime(str, 20, "%F %T", tm_info); str - ) + ` return c_str.as_text() func info(text:Text, newline=yes) diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm index bae01739..402d22af 100644 --- a/examples/wrap/wrap.tm +++ b/examples/wrap/wrap.tm @@ -1,3 +1,5 @@ +use patterns + HELP := " wrap: A tool for wrapping lines of text @@ -15,7 +17,7 @@ UNICODE_HYPHEN := "\{hyphen}" func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN -> Text) if preserve_paragraphs - paragraphs := text.split($/{2+ nl}/) + paragraphs := text.split_pattern($Pat"{2+ nl}") if paragraphs.length > 1 return "\n\n".join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs]) @@ -35,7 +37,7 @@ func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text) lines : @[Text] line := "" - for word in text.split($/{whitespace}/) + for word in text.split_pattern($Pat"{whitespace}") letters := word.split() skip if letters.length == 0 @@ -94,7 +96,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN first := yes wrapped_paragraphs : @[Text] - for paragraph in text.split($/{2+ nl}/) + for paragraph in text.split_pattern($Pat"{2+ nl}") wrapped_paragraphs.insert( wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen) ) diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm index 8a131042..b9b176af 100644 --- a/lib/commands/commands.tm +++ b/lib/commands/commands.tm @@ -57,22 +57,22 @@ struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}) errors : [Byte] status := run_command(command.command, command.args, command.env, input_bytes, &output, &errors) - if C_code:Bool(WIFEXITED(@status)) - return ProgramResult(output, errors, ExitType.Exited(C_code:Int32(WEXITSTATUS(@status)))) + if C_code:Bool`WIFEXITED(@status)` + return ProgramResult(output, errors, ExitType.Exited(C_code:Int32`WEXITSTATUS(@status)`)) - if C_code:Bool(WIFSIGNALED(@status)) - return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32(WTERMSIG(@status)))) + if C_code:Bool`WIFSIGNALED(@status)` + return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32`WTERMSIG(@status)`)) return ProgramResult(output, errors, ExitType.Failed) func run(command:Command, -> ExitType) status := run_command(command.command, command.args, command.env, none, none, none) - if C_code:Bool(WIFEXITED(@status)) - return ExitType.Exited(C_code:Int32(WEXITSTATUS(@status))) + if C_code:Bool`WIFEXITED(@status)` + return ExitType.Exited(C_code:Int32`WEXITSTATUS(@status)`) - if C_code:Bool(WIFSIGNALED(@status)) - return ExitType.Signaled(C_code:Int32(WTERMSIG(@status))) + if C_code:Bool`WIFSIGNALED(@status)` + return ExitType.Signaled(C_code:Int32`WTERMSIG(@status)`) return ExitType.Failed diff --git a/lib/json/json.tm b/lib/json/json.tm index 8127ce52..35a139c5 100644 --- a/lib/json/json.tm +++ b/lib/json/json.tm @@ -78,7 +78,7 @@ enum JSON( if esc := escapes[text[pos+1]] string ++= esc pos += 2 - else if m := text.matching_pattern($Pat/u{4 digit}/) + else if m := text.matching_pattern($Pat"u{4 digit}") string ++= Text.from_codepoints([Int32.parse(m.captures[1])!]) pos += 1 + m.text.length else diff --git a/lib/patterns/patterns.tm b/lib/patterns/patterns.tm index c5444b86..f62c6be0 100644 --- a/lib/patterns/patterns.tm +++ b/lib/patterns/patterns.tm @@ -4,7 +4,7 @@ struct PatternMatch(text:Text, index:Int, captures:[Text]) lang Pat convert(text:Text -> Pat) - return C_code:Pat(Pattern$escape_text(@text)) + return C_code:Pat`Pattern$escape_text(@text)` convert(n:Int -> Pat) return Pat.from_text("$n") @@ -12,45 +12,45 @@ lang Pat extend Text func matching_pattern(text:Text, pattern:Pat, pos:Int = 1 -> PatternMatch?) result : PatternMatch - if C_code:Bool(Pattern$match_at(@text, @pattern, @pos, (void*)&@result)) + if C_code:Bool`Pattern$match_at(@text, @pattern, @pos, (void*)&@result)` return result return none func matches_pattern(text:Text, pattern:Pat -> Bool) - return C_code:Bool(Pattern$matches(@text, @pattern)) + return C_code:Bool`Pattern$matches(@text, @pattern)` func pattern_captures(text:Text, pattern:Pat -> [Text]?) - return C_code:[Text]?(Pattern$captures(@text, @pattern)) + return C_code:[Text]?`Pattern$captures(@text, @pattern)` func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text) - return C_code:Text(Pattern$replace(@text, @pattern, @replacement, @backref, @recursive)) + return C_code:Text`Pattern$replace(@text, @pattern, @replacement, @backref, @recursive)` func translate_patterns(text:Text, replacements:{Pat=Text}, backref="@", recursive=yes -> Text) - return C_code:Text(Pattern$replace_all(@text, @replacements, @backref, @recursive)) + return C_code:Text`Pattern$replace_all(@text, @replacements, @backref, @recursive)` func has_pattern(text:Text, pattern:Pat -> Bool) - return C_code:Bool(Pattern$has(@text, @pattern)) + return C_code:Bool`Pattern$has(@text, @pattern)` func find_patterns(text:Text, pattern:Pat -> [PatternMatch]) - return C_code:[PatternMatch](Pattern$find_all(@text, @pattern)) + return C_code:[PatternMatch]`Pattern$find_all(@text, @pattern)` func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?)) - return C_code:func(->PatternMatch?)(Pattern$by_match(@text, @pattern)) + return C_code:func(->PatternMatch?)`Pattern$by_match(@text, @pattern)` func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes) - C_code { Pattern$each(@text, @pattern, @fn, @recursive); } + C_code `Pattern$each(@text, @pattern, @fn, @recursive);` func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text) - return C_code:Text(Pattern$map(@text, @pattern, @fn, @recursive)) + return C_code:Text`Pattern$map(@text, @pattern, @fn, @recursive)` func split_pattern(text:Text, pattern:Pat -> [Text]) - return C_code:[Text](Pattern$split(@text, @pattern)) + return C_code:[Text]`Pattern$split(@text, @pattern)` func by_pattern_split(text:Text, pattern:Pat -> func(->Text?)) - return C_code:func(->Text?)(Pattern$by_split(@text, @pattern)) + return C_code:func(->Text?)`Pattern$by_split(@text, @pattern)` func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text) - return C_code:Text(Pattern$trim(@text, @pattern, @left, @right)) + return C_code:Text`Pattern$trim(@text, @pattern, @left, @right)` func main() >> "Hello world".matching_pattern($Pat'{id}') diff --git a/lib/pthreads/pthreads.tm b/lib/pthreads/pthreads.tm index 8fca6bd6..c93df20a 100644 --- a/lib/pthreads/pthreads.tm +++ b/lib/pthreads/pthreads.tm @@ -3,66 +3,83 @@ use <pthread.h> struct pthread_mutex_t(; extern, opaque) func new(->@pthread_mutex_t) - return C_code : @pthread_mutex_t( + return C_code : @pthread_mutex_t ` pthread_mutex_t *mutex = GC_MALLOC(sizeof(pthread_mutex_t)); pthread_mutex_init(mutex, NULL); GC_register_finalizer(mutex, (void*)pthread_mutex_destroy, NULL, NULL, NULL); mutex - ) + ` func lock(m:&pthread_mutex_t) - fail("Failed to lock mutex") unless C_code:Int32(pthread_mutex_lock(@m)) == 0 + fail("Failed to lock mutex") unless C_code:Int32 `pthread_mutex_lock(@m)` == 0 func unlock(m:&pthread_mutex_t) - fail("Failed to unlock mutex") unless C_code:Int32(pthread_mutex_unlock(@m)) == 0 + fail("Failed to unlock mutex") unless C_code:Int32 `pthread_mutex_unlock(@m)` == 0 struct pthread_cond_t(; extern, opaque) func new(->@pthread_cond_t) - return C_code : @pthread_cond_t( + return C_code : @pthread_cond_t ` pthread_cond_t *cond = GC_MALLOC(sizeof(pthread_cond_t)); pthread_cond_init(cond, NULL); GC_register_finalizer(cond, (void*)pthread_cond_destroy, NULL, NULL, NULL); cond - ) + ` func wait(cond:&pthread_cond_t, mutex:&pthread_mutex_t) - fail("Failed to wait on condition") unless C_code:Int32(pthread_cond_wait(@cond, @mutex)) == 0 + fail("Failed to wait on condition") unless C_code:Int32 `pthread_cond_wait(@cond, @mutex)` == 0 func signal(cond:&pthread_cond_t) - fail("Failed to signal pthread_cond_t") unless C_code:Int32(pthread_cond_signal(@cond)) == 0 + fail("Failed to signal pthread_cond_t") unless C_code:Int32 `pthread_cond_signal(@cond)` == 0 func broadcast(cond:&pthread_cond_t) - fail("Failed to broadcast pthread_cond_t") unless C_code:Int32(pthread_cond_broadcast(@cond)) == 0 + fail("Failed to broadcast pthread_cond_t") unless C_code:Int32 `pthread_cond_broadcast(@cond)` == 0 struct pthread_rwlock_t(; extern, opaque) func new(->@pthread_rwlock_t) - return C_code : @pthread_rwlock_t ( + return C_code : @pthread_rwlock_t ` pthread_rwlock_t *lock = GC_MALLOC(sizeof(pthread_rwlock_t)); pthread_rwlock_init(lock, NULL); GC_register_finalizer(lock, (void*)pthread_rwlock_destroy, NULL, NULL, NULL); lock - ) + ` func read_lock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_rdlock(@lock); } + C_code ` + pthread_rwlock_rdlock(@lock); + ` func write_lock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_wrlock(@lock); } + C_code ` + pthread_rwlock_wrlock(@lock); + ` func unlock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_unlock(@lock); } + C_code ` + pthread_rwlock_unlock(@lock); + ` struct pthread_t(; extern, opaque) func new(fn:func() -> @pthread_t) - return C_code:@pthread_t( + return C_code:@pthread_t ` pthread_t *thread = GC_MALLOC(sizeof(pthread_t)); pthread_create(thread, NULL, @fn.fn, @fn.userdata); thread - ) - - func join(p:pthread_t) C_code { pthread_join(@p, NULL); } - func cancel(p:pthread_t) C_code { pthread_cancel(@p); } - func detatch(p:pthread_t) C_code { pthread_detach(@p); } + ` + + func join(p:pthread_t) + C_code ` + pthread_join(@p, NULL); + ` + + func cancel(p:pthread_t) + C_code ` + pthread_cancel(@p); + ` + + func detatch(p:pthread_t) + C_code ` + pthread_detach(@p); + ` struct IntQueue(_queue:@[Int], _mutex:@pthread_mutex_t, _cond:@pthread_cond_t) func new(initial:[Int]=[] -> IntQueue) diff --git a/lib/random/random.tm b/lib/random/random.tm index 107fad66..6d639c3a 100644 --- a/lib/random/random.tm +++ b/lib/random/random.tm @@ -6,7 +6,7 @@ use ./chacha.h struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; extern, secret) func from_seed(seed:[Byte]=[] -> chacha_ctx) - return C_code:chacha_ctx( + return C_code:chacha_ctx ` chacha_ctx ctx; uint8_t seed_bytes[KEYSZ + IVSZ] = {}; for (int64_t i = 0; i < (int64_t)sizeof(seed_bytes); i++) @@ -14,23 +14,24 @@ struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; e chacha_keysetup(&ctx, seed_bytes); chacha_ivsetup(&ctx, seed_bytes + KEYSZ); ctx - ) + ` random := RandomNumberGenerator.new() func _os_random_bytes(count:Int64 -> [Byte]) - return C_code:[Byte]( + return C_code:[Byte] ` uint8_t *random_bytes = GC_MALLOC_ATOMIC(@count); assert(getrandom(random_bytes, (size_t)@count, 0) == (size_t)@count); (List_t){.length=@count, .data=random_bytes, .stride=1, .atomic=1} - ) + ` + struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret) func new(seed:[Byte]?=none, -> @RandomNumberGenerator) ctx := chacha_ctx.from_seed(seed or _os_random_bytes(40)) return @RandomNumberGenerator(ctx, []) func _rekey(rng:&RandomNumberGenerator) - rng._random_bytes = C_code:[Byte]( + rng._random_bytes = C_code:[Byte] ` Byte_t new_keystream[KEYSZ + IVSZ] = {}; // Fill the buffer with the keystream chacha_encrypt_bytes(&@rng->_chacha, new_keystream, new_keystream, sizeof(new_keystream)); @@ -41,10 +42,10 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret memset(new_bytes.data, 0, new_bytes.length); chacha_encrypt_bytes(&@rng->_chacha, new_bytes.data, new_bytes.data, new_bytes.length); new_bytes - ) + ` func _fill_bytes(rng:&RandomNumberGenerator, dest:&Memory, needed:Int64) - C_code { + C_code ` while (@needed > 0) { if (@rng->_random_bytes.length == 0) @(rng._rekey()); @@ -60,13 +61,13 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret @dest += batch_size; @needed -= batch_size; } - } + ` func bytes(rng:&RandomNumberGenerator, count:Int -> [Byte]) count64 := Int64(count) - buf := C_code:@Memory(GC_MALLOC_ATOMIC(@count64)) + buf := C_code:@Memory `GC_MALLOC_ATOMIC(@count64)` rng._fill_bytes(buf, count64) - return C_code:[Byte]((List_t){.data=@buf, .stride=1, .atomic=1, .length=@count64}) + return C_code:[Byte] `(List_t){.data=@buf, .stride=1, .atomic=1, .length=@count64}` func byte(rng:&RandomNumberGenerator -> Byte) byte : &Byte @@ -87,7 +88,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if min == Int64.min and max == Int64.max return random_int64 - return C_code:Int64( + return C_code:Int64 ` uint64_t range = (uint64_t)@max - (uint64_t)@min + 1; uint64_t min_r = -range % range; uint64_t r; @@ -97,7 +98,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if (r >= min_r) break; } (int64_t)((uint64_t)@min + (r % range)) - ) + ` func int32(rng:&RandomNumberGenerator, min=Int32.min, max=Int32.max -> Int32) fail("Random minimum value $min is larger than the maximum value $max") if min > max @@ -107,7 +108,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if min == Int32.min and max == Int32.max return random_int32 - return C_code:Int32( + return C_code:Int32 ` uint32_t range = (uint32_t)@max - (uint32_t)@min + 1; uint32_t min_r = -range % range; uint32_t r; @@ -117,7 +118,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if (r >= min_r) break; } (int32_t)((uint32_t)@min + (r % range)) - ) + ` func int16(rng:&RandomNumberGenerator, min=Int16.min, max=Int16.max -> Int16) fail("Random minimum value $min is larger than the maximum value $max") if min > max @@ -127,7 +128,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if min == Int16.min and max == Int16.max return random_int16 - return C_code:Int16( + return C_code:Int16 ` uint16_t range = (uint16_t)@max - (uint16_t)@min + 1; uint16_t min_r = -range % range; uint16_t r; @@ -137,7 +138,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if (r >= min_r) break; } (int16_t)((uint16_t)@min + (r % range)) - ) + ` func int8(rng:&RandomNumberGenerator, min=Int8.min, max=Int8.max -> Int8) fail("Random minimum value $min is larger than the maximum value $max") if min > max @@ -147,7 +148,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if min == Int8.min and max == Int8.max return random_int8 - return C_code:Int8( + return C_code:Int8 ` uint8_t range = (uint8_t)@max - (uint8_t)@min + 1; uint8_t min_r = -range % range; uint8_t r; @@ -157,11 +158,11 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret if (r >= min_r) break; } (int8_t)((uint8_t)@min + (r % range)) - ) + ` func num(rng:&RandomNumberGenerator, min=0., max=1. -> Num) num_buf : &Num - return C_code:Num( + return C_code:Num ` if (@min > @max) fail("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")"); if (@min == @max) return @min; @@ -179,13 +180,13 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret r.num -= 1.0; (@min == 0.0 && @max == 1.0) ? r.num : ((1.0-r.num)*@min + r.num*@max) - ) + ` func num32(rng:&RandomNumberGenerator, min=Num32(0.), max=Num32(1.) -> Num32) return Num32(rng.num(Num(min), Num(max))) func int(rng:&RandomNumberGenerator, min:Int, max:Int -> Int) - return C_code:Int( + return C_code:Int ` if (likely(((@min.small & @max.small) & 1) != 0)) { int32_t r = @(rng.int32(Int32(min), Int32(max))); return I_small(r); @@ -217,8 +218,7 @@ struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret gmp_randclear(gmp_rng); Int$plus(@min, Int$from_mpz(r)) - ) - + ` func main() >> rng := RandomNumberGenerator.new() diff --git a/lib/shell/shell.tm b/lib/shell/shell.tm index f9476161..08b36f1a 100644 --- a/lib/shell/shell.tm +++ b/lib/shell/shell.tm @@ -2,7 +2,7 @@ use commands_v1.0 lang Shell convert(text:Text -> Shell) - return Shell.from_text("'" ++ text.replace($/'/, `'"'"'`) ++ "'") + return Shell.from_text("'" ++ text.replace(`'`, `'"'"'`) ++ "'") convert(texts:[Text] -> Shell) return Shell.from_text(" ".join([Shell(t).text for t in texts])) diff --git a/lib/time/time.tm b/lib/time/time.tm index d26d7218..cb7005cf 100644 --- a/lib/time/time.tm +++ b/lib/time/time.tm @@ -8,15 +8,15 @@ struct TimeInfo(year,month,day,hour,minute,second,nanosecond:Int, weekday:Weekda struct Time(tv_sec:Int64, tv_usec:Int64; extern) func now(->Time) - return C_code : Time ( + return C_code : Time ` struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) != 0) fail("Couldn't get the time!"); (Time){.tv_sec=ts.tv_sec, .tv_usec=ts.tv_nsec/1000} - ) + ` func local_timezone(->Text) - C_code { + C_code ` if (_local_timezone.length < 0) { static char buf[PATH_MAX]; ssize_t len = readlink("/etc/localtime", buf, sizeof(buf)); @@ -29,18 +29,18 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) else fail("Could not resolve local tz!"); } - } - return C_code:Text(_local_timezone) + ` + return C_code:Text`_local_timezone` func set_local_timezone(timezone:Text) - C_code { + C_code ` setenv("TZ", @(CString(timezone)), 1); _local_timezone = @timezone; tzset(); - } + ` func format(t:Time, format="%c", timezone=Time.local_timezone() -> Text) - return C_code : Text ( + return C_code : Text ` struct tm result; time_t time = @t.tv_sec; struct tm *final_info; @@ -48,10 +48,10 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) static char buf[256]; size_t len = strftime(buf, sizeof(buf), String(@format), final_info); Text$from_strn(buf, len) - ) + ` func new(year,month,day:Int, hour=0, minute=0, second=0.0, timezone=Time.local_timezone() -> Time) - return C_code : Time( + return C_code : Time` struct tm info = { .tm_min=Int32$from_int(@minute, false), .tm_hour=Int32$from_int(@hour, false), @@ -64,13 +64,13 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) time_t t; WITH_TIMEZONE(@timezone, t = mktime(&info)); (Time){.tv_sec=t + (time_t)@second, .tv_usec=(suseconds_t)(fmod(@second, 1.0) * 1e9)} - ) + ` func unix_timestamp(t:Time -> Int64) - return C_code:Int64((int64_t)@t.tv_sec) + return C_code:Int64`(int64_t)@t.tv_sec` func from_unix_timestamp(timestamp:Int64 -> Time) - return C_code:Time((Time){.tv_sec=@timestamp};) + return C_code:Time`(Time){.tv_sec=@timestamp};` func seconds_till(t:Time, target:Time -> Num) seconds := Num(target.tv_sec - t.tv_sec) @@ -84,7 +84,7 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) return t.seconds_till(target)/3600. func relative(t:Time, relative_to=Time.now(), timezone=Time.local_timezone() -> Text) - C_code { + C_code ` struct tm info = {}; struct tm relative_info = {}; WITH_TIMEZONE(@timezone, { @@ -112,7 +112,7 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) else return num_format((long)(second_diff), "second"); } - } + ` fail("Unreachable") func time(t:Time, seconds=no, am_pm=yes, timezone=Time.local_timezone() -> Text) @@ -131,7 +131,7 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) func info(t:Time, timezone=Time.local_timezone() -> TimeInfo) ret : TimeInfo - C_code { + C_code ` struct tm info = {}; WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info)); @ret.year = I(info.tm_year + 1900); @@ -144,11 +144,11 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) @ret.weekday = info.tm_wday + 1; @ret.day_of_year = I(info.tm_yday); @ret.timezone = @timezone; - } + ` return ret func after(t:Time, seconds=0.0, minutes=0.0, hours=0.0, days=0, weeks=0, months=0, years=0, timezone=Time.local_timezone() -> Time) - return C_code : Time ( + return C_code : Time ` double offset = @seconds + 60.*@minutes + 3600.*@hours ; @t.tv_sec += (time_t)offset; @@ -164,11 +164,11 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) .tv_sec=t, .tv_usec=@t.tv_usec + (suseconds_t)(fmod(offset, 1.0) * 1e9), } - ) + ` func parse(text:Text, format="%Y-%m-%dT%H:%M:%S%z", timezone=Time.local_timezone() -> Time?) ret : Time? - C_code { + C_code ` struct tm info = {.tm_isdst=-1}; const char *str = Text$as_c_string(@text); const char *fmt = Text$as_c_string(@format); @@ -185,7 +185,7 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern) WITH_TIMEZONE(@timezone, t = mktime(&info)); @ret.value.tv_sec = t + offset - info.tm_gmtoff; } - } + ` return ret func _run_tests() diff --git a/src/parse/text.c b/src/parse/text.c index c554273f..8897fd34 100644 --- a/src/parse/text.c +++ b/src/parse/text.c @@ -17,17 +17,33 @@ #include "types.h" #include "utils.h" -static const char closing[128] = {['('] = ')', ['['] = ']', ['<'] = '>', ['{'] = '}'}; - -static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, char open_quote, char close_quote, - char open_interp, bool allow_escapes) { +static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos) { const char *pos = *out_pos; + int64_t starting_indent = get_indent(ctx, pos); int64_t string_indent = starting_indent + SPACES_PER_INDENT; + + const char *quote, *interp; + bool allow_escapes = true; + if (match(&pos, "\"\"\"")) { // Triple double quote + quote = "\"\"\"", interp = "$", allow_escapes = false; + } else if (match(&pos, "'''")) { // Triple single quote + quote = "'''", interp = "$", allow_escapes = false; + } else if (match(&pos, "```")) { // Triple backtick + quote = "```", interp = "@", allow_escapes = false; + } else if (match(&pos, "\"")) { // Double quote + quote = "\"", interp = "$", allow_escapes = true; + } else if (match(&pos, "'")) { // Single quote + quote = "'", interp = "$", allow_escapes = true; + } else if (match(&pos, "`")) { // Backtick + quote = "`", interp = "@", allow_escapes = true; + } else { + parser_err(ctx, pos, pos, "I expected a valid text here"); + } + ast_list_t *chunks = NULL; Text_t chunk = EMPTY_TEXT; const char *chunk_start = pos; - int depth = 1; bool leading_newline = false; int64_t plain_span_len = 0; #define FLUSH_PLAIN_SPAN() \ @@ -37,38 +53,30 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, ch plain_span_len = 0; \ } \ } while (0) - for (const char *end = ctx->file->text + ctx->file->len; pos < end && depth > 0;) { + + for (const char *end = ctx->file->text + ctx->file->len; pos < end;) { const char *after_indentation = pos; - if (*pos == open_interp) { // Interpolation + const char *interp_start = pos; + if (interp != NULL && strncmp(pos, interp, strlen(interp)) == 0) { // Interpolation FLUSH_PLAIN_SPAN(); - const char *interp_start = pos; if (chunk.length > 0) { ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .text = chunk); chunks = new (ast_list_t, .ast = literal, .next = chunks); chunk = EMPTY_TEXT; } - ++pos; - ast_t *interp; + pos += strlen(interp); if (*pos == ' ' || *pos == '\t') parser_err(ctx, pos, pos + 1, "Whitespace is not allowed before an interpolation here"); - interp = expect(ctx, interp_start, &pos, parse_term_no_suffix, "I expected an interpolation term here"); - chunks = new (ast_list_t, .ast = interp, .next = chunks); + ast_t *value = + expect(ctx, interp_start, &pos, parse_term_no_suffix, "I expected an interpolation term here"); + chunks = new (ast_list_t, .ast = value, .next = chunks); chunk_start = pos; } else if (allow_escapes && *pos == '\\') { FLUSH_PLAIN_SPAN(); const char *c = unescape(ctx, &pos); chunk = Texts(chunk, Text$from_str(c)); - } else if (!leading_newline && *pos == open_quote && closing[(int)open_quote]) { // Nested pair begin - if (get_indent(ctx, pos) == starting_indent) { - ++depth; - } - plain_span_len += 1; - ++pos; - } else if (!leading_newline && *pos == close_quote) { // Nested pair end - if (get_indent(ctx, pos) == starting_indent) { - --depth; - if (depth == 0) break; - } + } else if (!leading_newline && strncmp(pos, quote, strlen(quote)) == 0) { // Nested pair end + if (get_indent(ctx, pos) == starting_indent) break; plain_span_len += 1; ++pos; } else if (newline_with_indentation(&after_indentation, string_indent)) { // Newline @@ -82,7 +90,7 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, ch } else if (newline_with_indentation(&after_indentation, starting_indent)) { // Line continuation (..) FLUSH_PLAIN_SPAN(); pos = after_indentation; - if (*pos == close_quote) { + if (strncmp(pos, quote, strlen(quote)) == 0) { break; } else if (some_of(&pos, ".") >= 2) { // Multi-line split @@ -103,6 +111,8 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, ch FLUSH_PLAIN_SPAN(); #undef FLUSH_PLAIN_SPAN + expect_closing(ctx, &pos, quote, "I was expecting a ", quote, " to finish this string"); + if (chunk.length > 0) { ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .text = chunk); chunks = new (ast_list_t, .ast = literal, .next = chunks); @@ -110,8 +120,6 @@ static ast_list_t *_parse_text_helper(parse_ctx_t *ctx, const char **out_pos, ch } REVERSE_LIST(chunks); - char close_str[2] = {close_quote, 0}; - expect_closing(ctx, &pos, close_str, "I was expecting a ", close_quote, " to finish this string"); *out_pos = pos; return chunks; } @@ -122,36 +130,14 @@ ast_t *parse_text(parse_ctx_t *ctx, const char *pos) { const char *start = pos; const char *lang = NULL; - char open_quote, close_quote, open_interp = '$'; - if (match(&pos, "\"")) { // Double quote - open_quote = '"', close_quote = '"', open_interp = '$'; - } else if (match(&pos, "`")) { // Backtick - open_quote = '`', close_quote = '`', open_interp = '$'; - } else if (match(&pos, "'")) { // Single quote - open_quote = '\'', close_quote = '\'', open_interp = '$'; - } else if (match(&pos, "$")) { // Customized strings + if (match(&pos, "$")) { lang = get_id(&pos); - // $"..." or $@"...." - static const char *interp_chars = "~!@#$%^&*+=\\?"; - if (match(&pos, "$")) { // Disable interpolation with $$ - open_interp = '\x03'; - } else if (strchr(interp_chars, *pos)) { - open_interp = *pos; - ++pos; - } - static const char *quote_chars = "\"'`|/;([{<"; - if (!strchr(quote_chars, *pos)) - parser_err(ctx, pos, pos + 1, - "This is not a valid string quotation character. Valid characters are: \"'`|/;([{<"); - open_quote = *pos; - ++pos; - close_quote = closing[(int)open_quote] ? closing[(int)open_quote] : open_quote; - } else { - return NULL; + if (lang == NULL) parser_err(ctx, start, pos, "I expected a language name after the `$`"); } - bool allow_escapes = (open_quote != '`'); - ast_list_t *chunks = _parse_text_helper(ctx, &pos, open_quote, close_quote, open_interp, allow_escapes); + if (!(*pos == '"' || *pos == '\'' || *pos == '`')) return NULL; + + ast_list_t *chunks = _parse_text_helper(ctx, &pos); bool colorize = match(&pos, "~") && match_word(&pos, "colorized"); return NewAST(ctx->file, start, pos, TextJoin, .lang = lang, .children = chunks, .colorize = colorize); } @@ -166,17 +152,15 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) { if (match(&pos, ":")) { type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this C_code code"); spaces(&pos); - if (!match(&pos, "(")) parser_err(ctx, start, pos, "I expected a '(' here"); - chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("({")), - .next = _parse_text_helper(ctx, &pos, '(', ')', '@', false)); + chunks = _parse_text_helper(ctx, &pos); if (type) { + chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("({")), .next = chunks); REVERSE_LIST(chunks); chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("; })")), .next = chunks); REVERSE_LIST(chunks); } } else { - if (!match(&pos, "{")) parser_err(ctx, start, pos, "I expected a '{' here"); - chunks = _parse_text_helper(ctx, &pos, '{', '}', '@', false); + chunks = _parse_text_helper(ctx, &pos); } return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type); diff --git a/test/inline_c.tm b/test/inline_c.tm index 77b56249..a9a7d6fc 100644 --- a/test/inline_c.tm +++ b/test/inline_c.tm @@ -1,8 +1,8 @@ func main() - >> C_code:Int32(int x = 1 + 2; x) + >> C_code:Int32`int x = 1 + 2; x` = Int32(3) - >> C_code { + >> C_code ` say(Text("Inline C code works!"), true); - } + ` diff --git a/test/text.tm b/test/text.tm index ff55555d..02f24de7 100644 --- a/test/text.tm +++ b/test/text.tm @@ -104,17 +104,10 @@ func main() = "A 3" >> "A \$(1+2)" = "A \$(1+2)" - >> `A $(1+2)` + >> 'A $(1+2)' = "A 3" - - >> $"A $(1+2)" - = "A 3" - >> $$"A $(1+2)" - = "A \$(1+2)" - >> $="A =(1+2)" + >> `A @(1+2)` = "A 3" - >> ${one {nested} two $(1+2)} - = "one {nested} two 3" c := "É̩" >> c.codepoint_names() |
