Add support for Objective-C

Closes #600

Co-authored-by: Nick Moore <nick@pilotmoon.com>
pull/619/head
Wilfred Hughes 2024-01-07 12:38:28 +07:00
parent 6338c3b314
commit db86b28a28
12 changed files with 809 additions and 0 deletions

@ -1,5 +1,9 @@
## 0.55 (unreleased)
### Parsing
Added support for Objective-C.
## 0.54 (released 7th January 2024)
### Parsing

@ -241,6 +241,11 @@ fn main() {
src_dir: "vendored_parsers/tree-sitter-nix-src",
extra_files: vec!["scanner.c"],
},
TreeSitterParser {
name: "tree-sitter-objc",
src_dir: "vendored_parsers/tree-sitter-objc-src",
extra_files: vec![],
},
TreeSitterParser {
name: "tree-sitter-ocaml",
src_dir: "vendored_parsers/tree-sitter-ocaml-src/ocaml/src",

@ -36,6 +36,7 @@ with `difft --list-languages`.
| Lua | [nvim-treesitter/tree-sitter-lua](https://github.com/nvim-treesitter/tree-sitter-lua) |
| Make | [alemuller/tree-sitter-make](https://github.com/alemuller/tree-sitter-make) |
| Nix | [cstrahan/tree-sitter-nix](https://github.com/cstrahan/tree-sitter-nix) |
| Objective-C | [amaanq/tree-sitter-objc](https://github.com/amaanq/tree-sitter-objc) |
| OCaml | [tree-sitter/tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml) |
| Perl | [ganezdragon/tree-sitter-perl](https://github.com/ganezdragon/tree-sitter-perl) |
| PHP | [tree-sitter/tree-sitter-php](https://github.com/tree-sitter/tree-sitter-php) |

@ -163,6 +163,12 @@ e00b95a4cf3fa3edf994155d8656063f -
sample_files/nullable_before.kt sample_files/nullable_after.kt
66da628a2c20e18059b8669aaa14a163 -
sample_files/objc_header_before.h sample_files/objc_header_after.h
f65feb21b25bb7f2ba31ee8f49977193 -
sample_files/objc_module_before.m sample_files/objc_module_after.m
c04cdebd2da077cf28b53307ad0a3b02 -
sample_files/ocaml_before.ml sample_files/ocaml_after.ml
2113c6c7959b8099f678d13953f7f44a -

@ -0,0 +1,32 @@
//
// HttpServer.h after file
//
// Created by Nicholas Moore on 20/09/2022.
// A line added to the header.
//
// Added to difftastic test suite by the author.
// This source file is released by the author into the public domain.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NSDictionary *_Nullable (^PopHttpRequestHandler)(NSURL *url,
NSString *method,
NSDictionary *headers,
NSData *body);
@interface PopHttpServer : NSObjectqq
@property(readonly) uint16_t port; // comment
@property(readonly) NSString *host;
@property(readonly) NSString *lastError;
@property(readonly, getter=isListening) BOOL listening;
- (id)initWithPort:(uint16_t)port;
- (BOOL)start;
- (void)stop;
- (void)registerHandler:(NSString *_Nonnull)pathPrefix
block:(PopHttpRequestHandler)myblock
added:(BOOL *_Nullable)added;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,25 @@
//
// HttpServer.h
//
// Created by Nicholas Moore on 20/09/2022.
//
// Added to difftastic test suite by the author.
// This source file is released by the author into the public domain.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NSDictionary *_Nullable(^PopHttpRequestHandler)(NSURL *url, NSString *method, NSDictionary *headers, NSData *body);
@interface PopHttpServer : NSObject
@property (readonly) uint16_t port;
@property (readonly) NSString *lastError;
@property (readonly, getter=isListening) BOOL listening;
- (id)initWithPort:(uint16_t)port;
- (BOOL)start;
- (void)stop;
- (void)registerHandler:(NSString *)pathPrefix block:(PopHttpRequestHandler)myblock;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,346 @@
//
// HttpServer.m
// Simple HTTP server.
// by Nicholas Moore 2023
// inspired by http://www.cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html
//
// Added to difftastic test suite by the author.
// This source file is released by the author into the public domain.
#import "PopHttpServer.h"
#import "NMKit.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
@interface PopHttpServer ()
@property (readonly) CFSocketRef socket;
@property (readonly) NSFileHandle *listenHandle;
@property (readonly) NSMapTable<NSFileHandle *, NSObject *> *httpMessages;
@property (readonly) NSMutableDictionary<NSString *, PopHttpRequestHandler> *handlers;
@property NSString *lastError;
@end
const NSArray *expressions=@[@NO, @7, @(YES), @3.15, @(9), @-11, @"Goodbye"];
@implementation PopHttpServer
- (id)initWithPort:(uint16_t)port
{
self=[super init];
if (self) {
self->_port=port;
self->_httpMessages=[NSMapTable weakToStrongObjectsMapTable];
self->_handlers=[NSMutableDictionary dictionary];
}
return self;
}
// warning: handlers will be called on a background thread!!
- (void)registerHandler:(NSString *)pathPrefix block:(PopHttpRequestHandler)myblock
{
[self.handlers setObject:myblock forKey:pathPrefix];
}
- (void)newHttpMessageForHandle:(NSFileHandle *const)connectionHandle
{
[self.httpMessages setObject:CFBridgingRelease(CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE))
forKey:connectionHandle];
}
- (BOOL)start
{
NMLogFine(@"Attempting to start HTTP server on port %@", @(self.port));
// create socket
self->_socket = CFSocketCreate(kCFAllocatorDefault,
PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 6, NULL
);
if (!self.socket)
{
self.lastError=@"Unable to create socket";
return NO;
}
// set reuse flag
// "This lets us reclaim the port if it is open but idle (a common occurrence if we restart the program immediately after a crash or killing the application)."
const int reuse = true;
const int fileDescriptor = CFSocketGetNative(self.socket);
if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(int)) != 0)
{
self.lastError=@"Unable to set socket options.";
[self stop];
return NO;
}
// create address and port
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(self.port);
NSData *const addressData = [NSData dataWithBytes:&address length:sizeof(address)];
if (CFSocketSetAddress(self.socket, (__bridge CFDataRef)addressData) != kCFSocketSuccess)
{
self.lastError=@"Unable to bind socket.";
[self stop]; return NO;
}
// add listener for connections
self->_listenHandle = [[NSFileHandle alloc]
initWithFileDescriptor:fileDescriptor
closeOnDealloc:YES];
// register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveIncomingConnectionNotification:)
name:NSFileHandleConnectionAcceptedNotification
object:self.listenHandle];
[self.listenHandle acceptConnectionInBackgroundAndNotify];
NMLogFine(@"HTTP server started on port %@", @(self.port));
return YES;
}
// Undo everything that was done in start
- (void)stop
{
// close down all open connections
for (NSFileHandle *connectionHandle in [self.httpMessages keyEnumerator]) {
NMLogFine(@"Closing connection %@", connectionHandle);
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle closeFile];
}
if (self.listenHandle)
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleConnectionAcceptedNotification
object:self.listenHandle];
[self.listenHandle closeFile];
self->_listenHandle=nil;
}
if (self.socket)
{
CFSocketInvalidate(self.socket);
CFRelease(self.socket);
self->_socket=nil;
}
NMLogFine(@"HTTP server on port %@ stopped", @(self.port));
}
- (BOOL)isListening
{
return self.socket;
}
// notification sent by the listenHandle
- (void)receiveIncomingConnectionNotification:(NSNotification *)notification
{
NSFileHandle *const connectionHandle=[[notification userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
if(connectionHandle)
{
[self newHttpMessageForHandle:connectionHandle];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveIncomingDataNotification:)
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle waitForDataInBackgroundAndNotify];
}
NMLogFine(@"Incoming connection %@", connectionHandle);
// accept another connection
[self.listenHandle acceptConnectionInBackgroundAndNotify];
}
typedef NS_ENUM(NSUInteger, DataOutcome) {
DataOutcomeContinue,
DataOutcomeClose,
DataOutcomeKeepAlive,
DataOutcomeUnknown,
};
- (void)receiveIncomingDataNotification:(NSNotification *)notification
{
NSFileHandle *const connectionHandle=[notification object];
// get our stored partial http message for this connection
const CFHTTPMessageRef httpMessage=(__bridge CFHTTPMessageRef)[self.httpMessages objectForKey:connectionHandle];
// perform remaining processing in backgroumd thread
NMRunAsyncInBackground(^{
const DataOutcome outcome=[self processHttpDataForMessage:httpMessage
connection:connectionHandle];
NMRunAsyncOnMainThread(^{
switch (outcome) {
case DataOutcomeClose:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle closeFile];
NMLogFine(@"Closed connection %@; there are %@", connectionHandle, @(self.httpMessages.count));
break;
case DataOutcomeKeepAlive:
[self newHttpMessageForHandle:connectionHandle];
NMLogFine(@"Ready for new message on connection %@; there are %@", connectionHandle, @(self.httpMessages.count));
// here we deliberately fall through to the Continue case
case DataOutcomeContinue:
default:
NMLogFine(@"Awaiting more data on connection %@", connectionHandle);
[connectionHandle waitForDataInBackgroundAndNotify];
break;
}
});
});
}
- (DataOutcome)processHttpDataForMessage:(CFHTTPMessageRef)httpMessage connection:(NSFileHandle *)connectionHandle
{
// get the data (if any)
NSData *data=nil;
@try {
data=[connectionHandle availableData];
}
@catch(NSException *e) {
// Ignore the exception, it normally just means the client
// closed the connection from the other end.
}
NMLogFine(@"Data: %@ bytes received on connection %@", @(data.length), connectionHandle);
// close if EOF or error parsing into http message
if (!data.length || httpMessage ||
!CFHTTPMessageAppendBytes(httpMessage, data.bytes, data.length)) {
return DataOutcomeClose;
}
// continue if we don't have the full header yet
if (!CFHTTPMessageIsHeaderComplete(httpMessage)) {
return DataOutcomeContinue;
}
// get the Content-Length value
NSInteger contentLength=0;
CFStringRef contentLengthStr = CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Content-Length"));
if (contentLengthStr) {
contentLength = CFStringGetIntValue(contentLengthStr);
CFRelease(contentLengthStr);
}
// get the Connection header
BOOL closeWhenDone=NO;
CFStringRef connectionStr = CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Connection"));
if (connectionStr) {
closeWhenDone = (CFStringCompare(connectionStr, CFSTR("close"), kCFCompareCaseInsensitive)==kCFCompareEqualTo);
CFRelease(connectionStr);
}
// get received data length
NSInteger receivedLength;
CFDataRef bodyData = CFHTTPMessageCopyBody(httpMessage);
if (bodyData) {
receivedLength = CFDataGetLength(bodyData);
CFRelease(bodyData);
}
// check if full body is recieved
if (receivedLength>=contentLength) {
NMLogFine(@"Entire message received, responding on connection %@", connectionHandle);
[self respondToHttpMessage:httpMessage withConnection:connectionHandle];
return closeWhenDone?DataOutcomeClose:DataOutcomeKeepAlive;
} else {
return DataOutcomeContinue;
}
}
- (void)respondToHttpMessage:(CFHTTPMessageRef)httpMessage withConnection:(NSFileHandle *)connectionHandle
{
#ifdef DEBUG
NSMutableArray *const logMessage=[NSMutableArray array];
void(^log)(NSString *, ...) = ^(NSString *format, ...) {
va_list args;
va_start(args, format);
[logMessage addObject:[[NSString alloc] initWithFormat:format arguments:args]];
va_end(args);
};
#endif
// get request info
NSURL *const url=CFBridgingRelease(CFHTTPMessageCopyRequestURL(httpMessage));
NSString *const method=CFBridgingRelease(CFHTTPMessageCopyRequestMethod(httpMessage));
NSDictionary *const headers=CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(httpMessage));
NSData *const body=CFBridgingRelease(CFHTTPMessageCopyBody(httpMessage));
#ifdef DEBUG
log(@"%@ value is %@", method, url.absoluteURL);
log(@"Body: %@", body);
#endif
// my very basic router
NSString *const firstPathPart=[[[url.path stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]] componentsSeparatedByString:@"/"] safeFirstObject];
NSDictionary *res=nil;
PopHttpRequestHandler handler=[self.handlers objectForKey:firstPathPart];
if (handler) {
#ifdef DEBUG
log(@"Route: %@", firstPathPart);
#endif
res=handler(url, method, headers, body);
}
#ifdef DEBUG
log(@"Response: %@", res);
#endif
NMLogFine(@"%@", [logMessage componentsJoinedByString:@"\n"]);
// note: prevously was wrapping this in WarpToMain, but seems unnecessary
if (res) {
[self respondWithBody:res[@"body"] status:res[@"status"] contentType:res[@"contentType"] headers:res[@"headers"] handle:connectionHandle];
} else {
// redo
}
}
- (void)respondWithBody:(NSObject *)bodyObj status:(NSNumber *)status contentType:(NSString *)contentType headers:(NSDictionary<NSString *, NSString *> *)headers handle:(NSFileHandle *)connectionHandle
{
NSData *bodyData=nil;
if ([bodyObj isKindOfClass:[NSString class]]) {
bodyData=[(NSString *)bodyObj dataUsingEncoding:NSUTF8StringEncoding];
contentType=[contentType stringByAppendingString:@"; charset=utf-8"];
}
else if ([bodyObj isKindOfClass:[NSData class]]) {
bodyData=(NSData *)bodyObj;
}
else {
NMLogError(@"Bad bodyObj: %@", bodyObj);
bodyData=[NSData data];
}
// create response message
CFHTTPMessageRef response=CFHTTPMessageCreateResponse(kCFAllocatorDefault, [status integerValue], NULL, kCFHTTPVersion1_1);
[headers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull val, BOOL * _Nonnull stop) {
CFHTTPMessageSetHeaderFieldValue(response, (__bridge CFStringRef)key, (__bridge CFStringRef)val);
}];
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("content-type"), (__bridge CFStringRef)contentType);
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("content-length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%@", @(bodyData.length)]);
CFHTTPMessageSetBody(response, (__bridge CFDataRef)bodyData);
CFDataRef messageData = CFHTTPMessageCopySerializedMessage(response);
@try
{
[connectionHandle writeData:(__bridge NSData *)messageData];
}
@catch (NSException *exception)
{
// Ignore the exception, it normally just means the client
// closed the connection from the other end.
}
@finally
{
CFRelease(messageData);
CFRelease(response);
}
}
@end

@ -0,0 +1,341 @@
//
// HttpServer.m
// Simple HTTP server.
// by Nicholas Moore 2023
// inspired by http://www.cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html
//
// Added to difftastic test suite by the author.
// This source file is released by the author into the public domain.
#import "PopHttpServer.h"
#import "NMKit.h"
#include <sys/socket.h>
#include <netinet/in.h>
@interface PopHttpServer ()
@property (readonly) CFSocketRef socket;
@property (readonly) NSFileHandle *listenHandle;
@property (readonly) NSMapTable<NSFileHandle *, id> *httpMessages;
@property (readonly) NSMutableDictionary<NSString *, PopHttpRequestHandler> *handlers;
@property NSString *lastError;
@end
const NSArray *expressions=@[@YES, @6, @(NO), @3.14, @(-9), @-10, @"Hello"];
@implementation PopHttpServer
- (id)initWithPort:(uint16_t)port
{
self=[super init];
if (self) {
self->_port=port;
self->_httpMessages=[NSMapTable weakToStrongObjectsMapTable];
self->_handlers=[NSMutableDictionary dictionary];
}
return self;
}
// warning: handlers will be called on a background thread
- (void)registerHandler:(NSString *)pathPrefix block:(PopHttpRequestHandler)myblock
{
[self.handlers setObject:myblock forKey:pathPrefix];
}
- (void)newHttpMessageForHandle:(NSFileHandle *)connectionHandle
{
[self.httpMessages setObject:CFBridgingRelease(CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE)) forKey:connectionHandle];
}
- (BOOL)start
{
NMLogFine(@"Attempting to start HTTP server on port %@", @(self.port));
// create socket
self->_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL);
if (!self.socket)
{
self.lastError=@"Unable to create socket";
return NO;
}
// set reuse flag
// "This lets us reclaim the port if it is open but idle (a common occurrence if we restart the program immediately after a crash or killing the application)."
const int reuse = true;
const int fileDescriptor = CFSocketGetNative(self.socket);
if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(int)) != 0)
{
self.lastError=@"Unable to set socket options.";
[self stop];
return NO;
}
// create address and port
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(self.port);
NSData *const addressData = [NSData dataWithBytes:&address length:sizeof(address)];
if (CFSocketSetAddress(self.socket, (__bridge CFDataRef)addressData) != kCFSocketSuccess)
{
self.lastError=@"Unable to bind socket to address.";
[self stop];
return NO;
}
// add listener for connections
self->_listenHandle = [[NSFileHandle alloc]
initWithFileDescriptor:fileDescriptor
closeOnDealloc:YES];
// register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveIncomingConnectionNotification:)
name:NSFileHandleConnectionAcceptedNotification
object:self.listenHandle];
[self.listenHandle acceptConnectionInBackgroundAndNotify];
NMLogFine(@"HTTP server started on port %@", @(self.port));
return YES;
}
// Undo everything in start
- (void)stop
{
// close down all open connections
for (NSFileHandle *connectionHandle in [self.httpMessages keyEnumerator]) {
NMLogFine(@"Closing connection %@", connectionHandle);
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle closeFile];
}
if (self.listenHandle)
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleConnectionAcceptedNotification
object:self.listenHandle];
[self.listenHandle closeFile];
self->_listenHandle=nil;
}
if (self.socket)
{
CFSocketInvalidate(self.socket);
CFRelease(self.socket);
self->_socket=nil;
}
NMLogFine(@"HTTP server on port %@ stopped", @(self.port));
}
- (BOOL)isListening
{
return self.socket;
}
// notification sent by the listenHandle
- (void)receiveIncomingConnectionNotification:(NSNotification *)notification
{
NSFileHandle *const connectionHandle=[[notification userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
if(connectionHandle)
{
[self newHttpMessageForHandle:connectionHandle];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveIncomingDataNotification:)
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle waitForDataInBackgroundAndNotify];
}
NMLogFine(@"Incoming connection %@", connectionHandle);
// accept another connection
[self.listenHandle acceptConnectionInBackgroundAndNotify];
}
typedef NS_ENUM(NSUInteger, DataOutcome) {
DataOutcomeContinue,
DataOutcomeClose,
DataOutcomeKeepAlive,
};
- (void)receiveIncomingDataNotification:(NSNotification *)notification
{
NSFileHandle *const connectionHandle=[notification object];
// get our stored partial http message for this connection
const CFHTTPMessageRef httpMessage=(__bridge CFHTTPMessageRef)[self.httpMessages objectForKey:connectionHandle];
// perform remaining processing in backgroumd thread
NMRunAsyncInBackground(^{
const DataOutcome outcome=[self processHttpDataForMessage:httpMessage connection:connectionHandle];
NMRunAsyncOnMainThread(^{
switch (outcome) {
case DataOutcomeClose:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSFileHandleDataAvailableNotification
object:connectionHandle];
[connectionHandle closeFile];
NMLogFine(@"Closed connection %@; there are %@", connectionHandle, @(self.httpMessages.count));
break;
case DataOutcomeKeepAlive:
[self newHttpMessageForHandle:connectionHandle];
NMLogFine(@"Ready for new message on connection %@; there are %@", connectionHandle, @(self.httpMessages.count));
// here we deliberately fall through to the Continue case
case DataOutcomeContinue:
default:
NMLogFine(@"Awaiting more data on connection %@", connectionHandle);
[connectionHandle waitForDataInBackgroundAndNotify];
break;
}
});
});
}
- (DataOutcome)processHttpDataForMessage:(CFHTTPMessageRef)httpMessage connection:(NSFileHandle *)connectionHandle
{
// get the data (if any)
NSData *data=nil;
@try {
data=[connectionHandle availableData];
}
@catch(NSException *e) {
// Ignore the exception, it normally just means the client
// closed the connection from the other end.
}
NMLogFine(@"Data: %@ bytes received on connection %@", @(data.length), connectionHandle);
// close if EOF or error parsing into http message
if (!data.length ||
!httpMessage ||
!CFHTTPMessageAppendBytes(httpMessage, data.bytes, data.length)) {
return DataOutcomeClose;
}
// continue if we don't have the full header yet
if (!CFHTTPMessageIsHeaderComplete(httpMessage)) {
return DataOutcomeContinue;
}
// get the Content-Length value
NSInteger contentLength=0;
CFStringRef contentLengthStr = CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Content-Length"));
if (contentLengthStr) {
contentLength = CFStringGetIntValue(contentLengthStr);
CFRelease(contentLengthStr);
}
// get the Connection header
BOOL closeWhenDone=NO;
CFStringRef connectionStr = CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Connection"));
if (connectionStr) {
closeWhenDone = (CFStringCompare(connectionStr, CFSTR("close"), kCFCompareCaseInsensitive)==kCFCompareEqualTo);
CFRelease(connectionStr);
}
// get received data length
NSInteger receivedLength=0;
CFDataRef bodyData = CFHTTPMessageCopyBody(httpMessage);
if (bodyData) {
receivedLength = CFDataGetLength(bodyData);
CFRelease(bodyData);
}
// check if full body is recieved
if (receivedLength>=contentLength) {
NMLogFine(@"Entire message received, responding on connection %@", connectionHandle);
[self respondToHttpMessage:httpMessage withConnection:connectionHandle];
return closeWhenDone?DataOutcomeClose:DataOutcomeKeepAlive;
} else {
return DataOutcomeContinue;
}
}
- (void)respondToHttpMessage:(CFHTTPMessageRef)httpMessage withConnection:(NSFileHandle *)connectionHandle
{
#ifdef DEBUG
NSMutableArray *const logMessage=[NSMutableArray array];
void(^log)(NSString *, ...) = ^(NSString *format, ...) {
va_list args;
va_start(args, format);
[logMessage addObject:[[NSString alloc] initWithFormat:format arguments:args]];
va_end(args);
};
#endif
// get request info
NSURL *const url=CFBridgingRelease(CFHTTPMessageCopyRequestURL(httpMessage));
NSString *const method=CFBridgingRelease(CFHTTPMessageCopyRequestMethod(httpMessage));
NSDictionary *const headers=CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(httpMessage));
NSData *const body=CFBridgingRelease(CFHTTPMessageCopyBody(httpMessage));
#ifdef DEBUG
log(@"%@ %@", method, url.absoluteURL);
log(@"Body: %@", body);
#endif
// my very basic router
NSString *const firstPathPart=[[[url.path stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]] componentsSeparatedByString:@"/"] safeFirstObject];
NSDictionary *res=nil;
PopHttpRequestHandler handler=[self.handlers objectForKey:firstPathPart];
if (handler) {
#ifdef DEBUG
log(@"Route: %@", firstPathPart);
#endif
res=handler(url, method, headers, body);
}
#ifdef DEBUG
log(@"Response: %@", res);
#endif
NMLogFine(@"%@", [logMessage componentsJoinedByString:@"\n"]);
// note: prevously was wrapping this in WarpToMain, but seems unnecessary
if (res) {
[self respondWithBody:res[@"body"] status:res[@"status"] contentType:res[@"contentType"] headers:res[@"headers"] handle:connectionHandle];
} else {
[self respondWithBody:@"Not found\n" status:@(404) contentType:@"text/plain" headers:@{} handle:connectionHandle];
}
}
- (void)respondWithBody:(NSObject *)bodyObj status:(NSNumber *)status contentType:(NSString *)contentType headers:(NSDictionary<NSString *, NSString *> *)headers handle:(NSFileHandle *)connectionHandle
{
NSData *bodyData=nil;
if ([bodyObj isKindOfClass:[NSString class]]) {
bodyData=[(NSString *)bodyObj dataUsingEncoding:NSUTF8StringEncoding];
contentType=[contentType stringByAppendingString:@"; charset=utf-8"];
}
else if ([bodyObj isKindOfClass:[NSData class]]) {
bodyData=(NSData *)bodyObj;
}
else {
NMLogError(@"Bad bodyObj: %@", bodyObj);
bodyData=[NSData data];
}
// create response message
CFHTTPMessageRef response=CFHTTPMessageCreateResponse(kCFAllocatorDefault, [status integerValue], NULL, kCFHTTPVersion1_1);
[headers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull val, BOOL * _Nonnull stop) {
CFHTTPMessageSetHeaderFieldValue(response, (__bridge CFStringRef)key, (__bridge CFStringRef)val);
}];
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Type"), (__bridge CFStringRef)contentType);
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%@", @(bodyData.length)]);
CFHTTPMessageSetBody(response, (__bridge CFDataRef)bodyData);
CFDataRef messageData = CFHTTPMessageCopySerializedMessage(response);
@try
{
[connectionHandle writeData:(__bridge NSData *)messageData];
}
@catch (NSException *exception)
{
// Ignore the exception, it normally just means the client
// closed the connection from the other end.
}
@finally
{
CFRelease(messageData);
CFRelease(response);
}
}
@end

@ -53,6 +53,7 @@ pub(crate) enum Language {
Make,
Newick,
Nix,
ObjC,
OCaml,
OCamlInterface,
Pascal,
@ -140,6 +141,7 @@ pub(crate) fn language_name(language: Language) -> &'static str {
Make => "Make",
Newick => "Newick",
Nix => "Nix",
ObjC => "Objective-C",
OCaml => "OCaml",
OCamlInterface => "OCaml Interface",
Pascal => "Pascal",
@ -317,6 +319,7 @@ pub(crate) fn language_globs(language: Language) -> Vec<glob::Pattern> {
],
Newick => &["*.nhx", "*.nwk", "*.nh"],
Nix => &["*.nix"],
ObjC => &["*.m"],
OCaml => &["*.ml"],
OCamlInterface => &["*.mli"],
Pascal => &["*.pas", "*.dfm", "*.dpr", "*.lpr", "*.pascal"],
@ -391,6 +394,24 @@ fn looks_like_hacklang(path: &Path, src: &str) -> bool {
false
}
/// Use a heuristic to determine if a '.h' file looks like Objective-C.
/// We look for a line starting with '#import', '@interface' or '@protocol'
/// near the top of the file. These keywords are not valid C or C++, so this
/// should not produce false positives.
fn looks_like_objc(path: &Path, src: &str) -> bool {
if let Some(extension) = path.extension() {
if extension == "h" {
return src.lines().take(100).any(|line| {
["#import", "@interface", "@protocol"]
.iter()
.any(|keyword| line.starts_with(keyword))
});
}
}
false
}
pub(crate) fn guess(
path: &Path,
src: &str,
@ -421,6 +442,9 @@ pub(crate) fn guess(
if looks_like_hacklang(path, src) {
return Some(Language::Hack);
}
if looks_like_objc(path, src) {
return Some(Language::ObjC);
}
if let Some(lang) = from_glob(path) {
return Some(lang);
}
@ -468,6 +492,7 @@ fn from_emacs_mode_header(src: &str) -> Option<Language> {
"js" | "js2" => Some(JavaScript),
"lisp" => Some(CommonLisp),
"nxml" => Some(Xml),
"objc" => Some(ObjC),
"perl" => Some(Perl),
"python" => Some(Python),
"racket" => Some(Racket),

@ -94,6 +94,7 @@ extern "C" {
fn tree_sitter_make() -> ts::Language;
fn tree_sitter_newick() -> ts::Language;
fn tree_sitter_nix() -> ts::Language;
fn tree_sitter_objc() -> ts::Language;
fn tree_sitter_ocaml() -> ts::Language;
fn tree_sitter_ocaml_interface() -> ts::Language;
fn tree_sitter_pascal() -> ts::Language;
@ -739,6 +740,27 @@ pub(crate) fn from_language(language: guess::Language) -> TreeSitterConfig {
sub_languages: vec![],
}
}
ObjC => {
let language = unsafe { tree_sitter_objc() };
TreeSitterConfig {
language,
atom_nodes: vec!["string_literal"].into_iter().collect(),
delimiter_tokens: vec![
("(", ")"),
("{", "}"),
("[", "]"),
("@(", ")"),
("@{", "}"),
("@[", "]"),
],
highlight_query: ts::Query::new(
language,
include_str!("../../vendored_parsers/highlights/objc.scm"),
)
.unwrap(),
sub_languages: vec![],
}
}
OCaml => {
let language = unsafe { tree_sitter_ocaml() };
TreeSitterConfig {

@ -0,0 +1 @@
../tree-sitter-objc/queries/highlights.scm

@ -0,0 +1 @@
tree-sitter-objc/src